From 4e5bf2fd93a175f64aa1ca2e4b35dcf853f7f828 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 23 Feb 2005 21:06:32 +0000 Subject: * command-ssh.c (get_passphrase): Removed. (ssh_identity_register): Partly rewritten. (open_control_file, search_control_file, add_control_entry): New. (ssh_handler_request_identities): Return only files listed in our control file. * findkey.c (unprotect): Check for allocation error. * agent.h (opt): Add fields to record the startup terminal settings. * gpg-agent.c (main): Record them and do not force keep display with --enable-ssh-support. * command-ssh.c (start_command_handler_ssh): Use them here. * gpg-agent.c: Renamed option --ssh-support to --enable-ssh-support. * command.c (cmd_readkey): New. (register_commands): Register new command "READKEY". * command-ssh.c (ssh_request_process): Improved logging. * findkey.c (agent_write_private_key): Always use plain open. Don't depend on an umask for permissions. (agent_key_from_file): Factored file reading code out to .. (read_key_file): .. new function. (agent_public_key_from_file): New. --- agent/ChangeLog | 40 +++++- agent/agent.h | 12 ++ agent/command-ssh.c | 363 +++++++++++++++++++++++++++++++++++++++++----------- agent/command.c | 54 +++++++- agent/findkey.c | 330 +++++++++++++++++++++++++++++++++++++++-------- agent/gpg-agent.c | 30 +++-- agent/keyformat.txt | 6 + agent/protect.c | 2 +- agent/query.c | 2 +- 9 files changed, 690 insertions(+), 149 deletions(-) (limited to 'agent') diff --git a/agent/ChangeLog b/agent/ChangeLog index 420dc6368..47ca2debf 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,33 @@ +2005-02-23 Werner Koch + + * command-ssh.c (get_passphrase): Removed. + (ssh_identity_register): Partly rewritten. + (open_control_file, search_control_file, add_control_entry): New. + (ssh_handler_request_identities): Return only files listed in our + control file. + + * findkey.c (unprotect): Check for allocation error. + + * agent.h (opt): Add fields to record the startup terminal + settings. + * gpg-agent.c (main): Record them and do not force keep display + with --enable-ssh-support. + * command-ssh.c (start_command_handler_ssh): Use them here. + + * gpg-agent.c: Renamed option --ssh-support to + --enable-ssh-support. + + * command.c (cmd_readkey): New. + (register_commands): Register new command "READKEY". + + * command-ssh.c (ssh_request_process): Improved logging. + + * findkey.c (agent_write_private_key): Always use plain open. + Don't depend on an umask for permissions. + (agent_key_from_file): Factored file reading code out to .. + (read_key_file): .. new function. + (agent_public_key_from_file): New. + 2005-02-22 Werner Koch * command-ssh.c (stream_read_string): Removed call to abort on @@ -1092,21 +1122,21 @@ Mon Aug 21 17:59:17 CEST 2000 Werner Koch - * gpg-agent.c (passphrase_dialog): Cleanup the window and added the + * gpg-agent.c (passphrase_dialog): Cleanup the window and added the user supplied text to the window. (main): Fixed segv in gtk_init when used without a command to start. - * gpg-agent.c: --flush option. + * gpg-agent.c: --flush option. (req_flush): New. (req_clear_passphrase): Implemented. Fri Aug 18 14:27:14 CEST 2000 Werner Koch - * gpg-agent.c: New. - * Makefile.am: New. + * gpg-agent.c: New. + * Makefile.am: New. - Copyright 2001, 2002 Free Software Foundation, Inc. + Copyright 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. This file is free software; as a special exception the author gives unlimited permission to copy and/or distribute it, with or without diff --git a/agent/agent.h b/agent/agent.h index a1196bc0b..0661cc4ad 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -53,6 +53,15 @@ struct { int dry_run; /* Don't change any persistent data */ int batch; /* Batch mode */ const char *homedir; /* Configuration directory name */ + + /* Environment setting gathred at program start. */ + const char *startup_display; + const char *startup_ttyname; + const char *startup_ttytype; + const char *startup_lc_ctype; + const char *startup_lc_messages; + + const char *pinentry_program; /* Filename of the program to start as pinentry. */ const char *scdaemon_program; /* Filename of the program to handle @@ -150,6 +159,9 @@ gpg_error_t agent_key_from_file (ctrl_t ctrl, const unsigned char *grip, unsigned char **shadow_info, int ignore_cache, gcry_sexp_t *result); +gpg_error_t agent_public_key_from_file (ctrl_t ctrl, + const unsigned char *grip, + gcry_sexp_t *result); int agent_key_available (const unsigned char *grip); /*-- query.c --*/ diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 1719602f2..8ea042e19 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -23,13 +23,14 @@ #include +#include #include #include #include #include #include #include -#include +#include #include "agent.h" @@ -63,7 +64,22 @@ #define SSH_DSA_SIGNATURE_ELEMS 2 #define SPEC_FLAG_USE_PKCS1V2 (1 << 0) - + +/* The blurb we put into the header of a newly created control file. */ +static const char sshcontrolblurb[] = +"# List of allowed ssh keys. Only keys present in this file are used\n" +"# in the SSH protocol. The ssh-add tool may add new entries to this\n" +"# file to enable them; you may also add them manually. Comment\n" +"# lines, like this one, as well as empty lines are ignored. Lines do\n" +"# have a certain length limit but this is not serious limitation as\n" +"# the format of the entries is fixed and checked by gpg-agent. A\n" +"# non-comment line starts with optional white spaces, followed by the\n" +"# keygrip of the key given as 40 hex digits, optionally followed by a\n" +"# the caching TTL in seconds and another optional field for arbitrary\n" +"# flags. Prepend the keygrip with an '!' mark to disable it.\n" +"\n"; + + /* Macros. */ @@ -626,6 +642,155 @@ file_to_buffer (const char *filename, unsigned char **buffer, size_t *buffer_n) } + + +/* Open the ssh control file and create it if not available. With + APPEND passed as true the file will be opened in append mode, + otherwise in read only mode. On success a file pointer is stored + at the address of R_FP. */ +static gpg_error_t +open_control_file (FILE **r_fp, int append) +{ + gpg_error_t err; + char *fname; + FILE *fp; + + /* Note: As soon as we start to use non blocking functions here + (i.e. where Pth might switch threads) we need to employ a + mutex. */ + *r_fp = NULL; + fname = make_filename (opt.homedir, "sshcontrol.txt", NULL); + fp = fopen (fname, append? "a+":"r"); + if (!fp && errno == ENOENT) + { + /* Fixme: "x" is a GNU extension. We might want to use the es_ + functions here. */ + fp = fopen (fname, "wx"); + if (!fp) + { + err = gpg_error (gpg_err_code_from_errno (errno)); + log_error (_("can't create `%s': %s\n"), fname, gpg_strerror (err)); + xfree (fname); + return err; + } + fputs (sshcontrolblurb, fp); + fclose (fp); + fp = fopen (fname, append? "a+":"r"); + } + + if (!fp) + { + err = gpg_error (gpg_err_code_from_errno (errno)); + log_error (_("can't open `%s': %s\n"), fname, gpg_strerror (err)); + xfree (fname); + return err; + } + + *r_fp = fp; + + return 0; +} + + +/* Search the file at stream FP from the beginning until a matching + HEXGRIP is found; return success in this case and store true at + DISABLED if the found key has been disabled. */ +static gpg_error_t +search_control_file (FILE *fp, const char *hexgrip, int *disabled) +{ + int c, i; + char *p, line[256]; + + assert (strlen (hexgrip) == 40 ); + + rewind (fp); + *disabled = 0; + next_line: + do + { + if (!fgets (line, DIM(line)-1, fp) ) + { + if (feof (fp)) + return gpg_error (GPG_ERR_EOF); + return gpg_error (gpg_err_code_from_errno (errno)); + } + + if (!*line || line[strlen(line)-1] != '\n') + { + /* Eat until end of line */ + while ( (c=getc (fp)) != EOF && c != '\n') + ; + return gpg_error (*line? GPG_ERR_LINE_TOO_LONG + : GPG_ERR_INCOMPLETE_LINE); + } + + /* Allow for empty lines and spaces */ + for (p=line; spacep (p); p++) + ; + } + while (!*p || *p == '\n' || *p == '#'); + + *disabled = 0; + if (*p == '!') + { + *disabled = 1; + for (p++; spacep (p); p++) + ; + } + + for (i=0; hexdigitp (p) && i < 40; p++, i++) + if (hexgrip[i] != (*p >= 'a'? (*p & 0xdf): *p)) + goto next_line; + if (i != 40 || !(spacep (p) || *p == '\n')) + { + log_error ("invalid formatted line in ssh control file\n"); + return gpg_error (GPG_ERR_BAD_DATA); + } + + /* Fixme: Get TTL and flags. */ + + return 0; /* Okay: found it. */ +} + + + +/* Add an entry to the control file to mark the key with the keygrip + HEXGRIP as usable for SSH; i.e. it will be returned when ssh asks + for it. This function is in general used to add a key received + through the ssh-add function. We can assume that the user wants to + allow ssh using this key. */ +static gpg_error_t +add_control_entry (ctrl_t ctrl, const char *hexgrip, int ttl) +{ + gpg_error_t err; + FILE *fp; + int disabled; + + err = open_control_file (&fp, 1); + if (err) + return err; + + err = search_control_file (fp, hexgrip, &disabled); + if (err && gpg_err_code(err) == GPG_ERR_EOF) + { + struct tm *tp; + time_t atime = time (NULL); + + /* Not yet in the file - add it. Becuase the file has been + opened in append mode, we simply need to write to it. */ + tp = localtime (&atime); + fprintf (fp, "# Key added on %04d-%02d-%02d %02d:%02d:%02d\n%s %d\n", + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, + tp->tm_hour, tp->tm_min, tp->tm_sec, + hexgrip, ttl); + + } + fclose (fp); + return 0; +} + + + /* @@ -1377,6 +1542,7 @@ ssh_handler_request_identities (ctrl_t ctrl, gpg_error_t err; gpg_error_t ret_err; int ret; + FILE *ctrl_fp = NULL; /* Prepare buffer stream. */ @@ -1427,6 +1593,19 @@ ssh_handler_request_identities (ctrl_t ctrl, /* FIXME: make sure that buffer gets deallocated properly. */ + /* Fixme: We should better iterate over the control file and check + whether the key file is there. This is better in resepct to + performance if tehre are a lot of key sin our key storage. */ + + err = open_control_file (&ctrl_fp, 0); + if (err) + goto out; + +#warning Really need to fix this fixme. + /* + FIXME: First check whether a key is currently available in the card reader - this should be allowed even without being listed in sshcontrol.txt. + */ + while (1) { dir_entry = readdir (dir); @@ -1435,6 +1614,19 @@ ssh_handler_request_identities (ctrl_t ctrl, if ((strlen (dir_entry->d_name) == 44) && (! strncmp (dir_entry->d_name + 40, ".key", 4))) { + char hexgrip[41]; + int disabled; + + /* We do only want to return keys listed in our control + file. */ + strncpy (hexgrip, dir_entry->d_name, 40); + hexgrip[40] = 0; + if ( strlen (hexgrip) != 40 ) + continue; + if (search_control_file (ctrl_fp, hexgrip, &disabled) + || disabled) + continue; + strncpy (key_path + key_directory_n + 1, dir_entry->d_name, 40); /* Read file content. */ @@ -1522,6 +1714,9 @@ ssh_handler_request_identities (ctrl_t ctrl, if (dir) closedir (dir); + if (ctrl_fp) + fclose (ctrl_fp); + free (key_directory); xfree (key_path); xfree (buffer); @@ -1802,43 +1997,6 @@ ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response) return ret_err; } -static gpg_error_t -get_passphrase (ctrl_t ctrl, - const char *description, size_t passphrase_n, char *passphrase) -{ - struct pin_entry_info_s *pi; - gpg_error_t err; - - err = 0; - pi = gcry_calloc_secure (1, sizeof (*pi) + passphrase_n + 1); - if (! pi) - { - err = gpg_error (GPG_ERR_ENOMEM); - goto out; - } - - pi->min_digits = 0; /* We want a real passphrase. */ - pi->max_digits = 8; - pi->max_tries = 1; - pi->failed_tries = 0; - pi->check_cb = NULL; - pi->check_cb_arg = NULL; - pi->cb_errtext = NULL; - pi->max_length = 100; - - err = agent_askpin (ctrl, description, NULL, pi); - if (err) - goto out; - - memcpy (passphrase, pi->pin, passphrase_n); - passphrase[passphrase_n] = 0; - - out: - - xfree (pi); - - return err; -} static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **comment) @@ -1929,76 +2087,100 @@ ssh_key_to_buffer (gcry_sexp_t key, const char *passphrase, return err; } + + +/* Store the ssh KEY into our local key storage and protect him after + asking for a passphrase. Cache that passphrase. TTL is the + maximum caching time for that key. If the key already exists in + our key storage, don't do anything. When entering a new key also + add an entry to the sshcontrol file. */ static gpg_error_t ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl) { + gpg_error_t err; unsigned char key_grip_raw[21]; - unsigned char *buffer; - unsigned int buffer_n; - char passphrase[100]; - char *description; char key_grip[41]; - char *comment; - gpg_error_t err; + unsigned char *buffer = NULL; + unsigned int buffer_n; + char *description = NULL; + char *comment = NULL; unsigned int i; - int ret; - - description = NULL; - comment = NULL; - buffer = NULL; + struct pin_entry_info_s *pi = NULL; err = ssh_key_grip (key, key_grip_raw); if (err) goto out; - key_grip_raw[sizeof (key_grip_raw) - 1] = 0; - ret = agent_key_available (key_grip_raw); - if (! ret) - goto out; + key_grip_raw[sizeof (key_grip_raw) - 1] = 0; /* FIXME: Why?? */ + /* Check whether the key is alread in our key storage. Don't do + anything then. */ + if ( !agent_key_available (key_grip_raw) ) + goto out; /* Yes, key is available. */ + + err = ssh_key_extract_comment (key, &comment); if (err) goto out; - ret = asprintf (&description, - "Please provide the passphrase, which should be used " - "for protecting the received secret key `%s':", - comment ? comment : ""); - if (ret < 0) + if ( asprintf (&description, + _("Please enter a passphrase to protect%%0A" + "the received secret key%%0A" + " %s%%0A" + "within gpg-agent's key storage"), + comment ? comment : "?") < 0) { - err = gpg_err_code_from_errno (errno); + err = gpg_error_from_errno (errno); goto out; } - err = get_passphrase (ctrl, description, sizeof (passphrase), passphrase); + + pi = gcry_calloc_secure (1, sizeof (*pi) + 100 + 1); + if (!pi) + { + err = gpg_error_from_errno (errno); + goto out; + } + pi->max_length = 100; + pi->max_tries = 1; + err = agent_askpin (ctrl, description, NULL, pi); if (err) goto out; - err = ssh_key_to_buffer (key, passphrase, &buffer, &buffer_n); + err = ssh_key_to_buffer (key, pi->pin, &buffer, &buffer_n); if (err) goto out; + /* Store this key to our key storage. */ err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0); if (err) goto out; + /* Cache this passphrase. */ for (i = 0; i < 20; i++) sprintf (key_grip + 2 * i, "%02X", key_grip_raw[i]); - err = agent_put_cache (key_grip, passphrase, ttl); + err = agent_put_cache (key_grip, pi->pin, ttl); if (err) goto out; - out: + /* And add an entry to the sshcontrol file. */ + err = add_control_entry (ctrl, key_grip, ttl); + + out: + if (pi && pi->max_length) + wipememory (pi->pin, pi->max_length); + xfree (pi); xfree (buffer); xfree (comment); - free (description); - /* FIXME: verify xfree vs free. */ + free (description); /* (asprintf allocated, thus regular free.) */ return err; } + + static gpg_error_t ssh_identity_drop (gcry_sexp_t key) { @@ -2234,12 +2416,9 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock) if (err) goto out; - if (opt.verbose) /* FIXME: using log_debug is not good with - verbose. log_debug should only be used in - debugging mode or in sitattions which are - unexpected. */ - log_debug ("received request of length: %u\n", - request_data_size); + if (opt.verbose > 1) + log_info ("received ssh request of length %u\n", + (unsigned int)request_data_size); request = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+"); if (! request) @@ -2277,17 +2456,28 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock) break; if (i == DIM (request_specs)) { - log_debug ("request %u is not supported\n", - request_type); + log_info ("ssh request %u is not supported\n", request_type); send_err = 1; goto out; } if (opt.verbose) - log_debug ("executing request handler: %s (%u)\n", + log_info ("ssh request handler for %s (%u) started\n", request_specs[i].identifier, request_specs[i].type); err = (*request_specs[i].handler) (ctrl, request, response); + + if (opt.verbose) + { + if (err) + log_info ("ssh request handler for %s (%u) failed: %s\n", + request_specs[i].identifier, request_specs[i].type, + gpg_strerror (err)); + else + log_info ("ssh request handler for %s (%u) ready\n", + request_specs[i].identifier, request_specs[i].type); + } + if (err) { send_err = 1; @@ -2295,6 +2485,10 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock) } response_size = es_ftell (response); + if (opt.verbose > 1) + log_info ("sending ssh response of length %u\n", + (unsigned int)response_size); + err = es_fseek (response, 0, SEEK_SET); if (err) { @@ -2325,6 +2519,8 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock) if (send_err) { + if (opt.verbose > 1) + log_info ("sending ssh error response\n"); err = stream_write_uint32 (stream_sock, 1); if (err) goto leave; @@ -2341,7 +2537,7 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock) es_fclose (response); xfree (request_data); /* FIXME? */ - return !! err; + return !!err; } void @@ -2359,6 +2555,21 @@ start_command_handler_ssh (int sock_client) agent_init_default_ctrl (&ctrl); ctrl.connection_fd = sock_client; + /* Because the ssh protocol does not send us information about the + the current TTY setting, we resort here to use those from startup + or those explictly set. */ + if (!ctrl.display && opt.startup_display) + ctrl.display = strdup (opt.startup_display); + if (!ctrl.ttyname && opt.startup_ttyname) + ctrl.ttyname = strdup (opt.startup_ttyname); + if (!ctrl.ttytype && opt.startup_ttytype) + ctrl.ttytype = strdup (opt.startup_ttytype); + if (!ctrl.lc_ctype && opt.startup_lc_ctype) + ctrl.lc_ctype = strdup (opt.startup_lc_ctype); + if (!ctrl.lc_messages && opt.startup_lc_messages) + ctrl.lc_messages = strdup (opt.startup_lc_messages); + + /* Create stream from socket. */ stream_sock = es_fdopen (sock_client, "r+"); if (!stream_sock) diff --git a/agent/command.c b/agent/command.c index dc8a4a158..997140207 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1,5 +1,5 @@ /* command.c - gpg-agent command handler - * Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc. + * Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -22,12 +22,14 @@ some buffering in secure mempory to protect session keys etc. */ #include + #include #include #include #include #include #include +#include #include @@ -504,6 +506,55 @@ cmd_genkey (ASSUAN_CONTEXT ctx, char *line) } + + +/* READKEY + + Return the public key for the given keygrip. */ +static int +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; + + rc = parse_keygrip (ctx, line, grip); + if (rc) + return rc; /* Return immediately as this is already an Assuan error code.*/ + + rc = agent_public_key_from_file (ctrl, grip, &s_pkey); + if (!rc) + { + size_t len; + unsigned char *buf; + + len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); + assert (len); + buf = xtrymalloc (len); + if (!buf) + rc = gpg_error_from_errno (errno); + else + { + len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, buf, len); + assert (len); + rc = assuan_send_data (ctx, buf, len); + rc = map_assuan_err (rc); + xfree (buf); + } + gcry_sexp_release (s_pkey); + } + + if (rc) + log_error ("command readkey failed: %s\n", gpg_strerror (rc)); + return map_to_assuan_status (rc); +} + + + + + + /* GET_PASSPHRASE [ ] This function is usually used to ask for a passphrase to be used @@ -894,6 +945,7 @@ register_commands (ASSUAN_CONTEXT ctx) { "PKSIGN", cmd_pksign }, { "PKDECRYPT", cmd_pkdecrypt }, { "GENKEY", cmd_genkey }, + { "READKEY", cmd_readkey }, { "GET_PASSPHRASE", cmd_get_passphrase }, { "PRESET_PASSPHRASE", cmd_preset_passphrase }, { "CLEAR_PASSPHRASE", cmd_clear_passphrase }, diff --git a/agent/findkey.c b/agent/findkey.c index 1ac57ad07..86a28d511 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -39,7 +39,9 @@ struct try_unprotect_arg_s { }; - +/* Write an S-expression formatted key to our key storage. With FORCE + pased as true an existsing key with the given GRIP will get + overwritten. */ int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force) @@ -48,51 +50,44 @@ agent_write_private_key (const unsigned char *grip, char *fname; FILE *fp; char hexgrip[40+4+1]; + int fd; for (i=0; i < 20; i++) sprintf (hexgrip+2*i, "%02X", grip[i]); strcpy (hexgrip+40, ".key"); fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); - if (force) - fp = fopen (fname, "wb"); - else - { - int fd; - - if (!access (fname, F_OK)) - { - log_error ("secret key file `%s' already exists\n", fname); - xfree (fname); - return gpg_error (GPG_ERR_GENERAL); - } - /* We would like to create FNAME but only if it does not already - exist. We cannot make this guarantee just using POSIX (GNU - provides the "x" opentype for fopen, however, this is not - portable). Thus, we use the more flexible open function and - then use fdopen to obtain a stream. + if (!force && !access (fname, F_OK)) + { + log_error ("secret key file `%s' already exists\n", fname); + xfree (fname); + return gpg_error (GPG_ERR_GENERAL); + } - The mode parameter to open is what fopen uses. It will be - combined with the process' umask automatically. */ - fd = open (fname, O_CREAT | O_EXCL | O_RDWR, - S_IRUSR | S_IWUSR + /* In FORCE mode we would like to create FNAME but only if it does + not already exist. We cannot make this guarantee just using + POSIX (GNU provides the "x" opentype for fopen, however, this is + not portable). Thus, we use the more flexible open function and + then use fdopen to obtain a stream. */ + fd = open (fname, force? (O_CREAT | O_TRUNC | O_WRONLY) + : (O_CREAT | O_EXCL | O_WRONLY), + S_IRUSR | S_IWUSR #ifndef HAVE_W32_SYSTEM - | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH + | S_IRGRP #endif ); - if (fd < 0) - fp = 0; - else - { - fp = fdopen (fd, "wb"); - if (!fp) - { - int save_e = errno; - close (fd); - errno = save_e; - } - } + if (fd < 0) + fp = NULL; + else + { + fp = fdopen (fd, "wb"); + if (!fp) + { + int save_e = errno; + close (fd); + errno = save_e; + } } if (!fp) @@ -263,6 +258,8 @@ unprotect (CTRL ctrl, const char *desc_text, } pi = gcry_calloc_secure (1, sizeof (*pi) + 100); + if (!pi) + return gpg_error_from_errno (errno); pi->max_length = 100; pi->min_digits = 0; /* we want a real passphrase */ pi->max_digits = 8; @@ -285,32 +282,22 @@ unprotect (CTRL ctrl, const char *desc_text, } - -/* Return the secret key as an S-Exp in RESULT after locating it using - the grip. Returns NULL in RESULT if the operation should be - diverted to a token; SHADOW_INFO will point then to an allocated - S-Expression with the shadow_info part from the file. With - IGNORE_CACHE passed as true the passphrase is not taken from the - cache. DESC_TEXT may be set to present a custom description for the - pinentry. */ -gpg_error_t -agent_key_from_file (CTRL ctrl, const char *desc_text, - const unsigned char *grip, unsigned char **shadow_info, - int ignore_cache, gcry_sexp_t *result) +/* Read the key identified by GRIP from the private key directory and + return it as an gcrypt S-expression object in RESULT. On failure + returns an error code and stores NULL at RESULT. */ +static gpg_error_t +read_key_file (const unsigned char *grip, gcry_sexp_t *result) { int i, rc; char *fname; FILE *fp; struct stat st; unsigned char *buf; - size_t len, buflen, erroff; + size_t buflen, erroff; gcry_sexp_t s_skey; char hexgrip[40+4+1]; - int got_shadow_info = 0; *result = NULL; - if (shadow_info) - *shadow_info = NULL; for (i=0; i < 20; i++) sprintf (hexgrip+2*i, "%02X", grip[i]); @@ -336,8 +323,8 @@ agent_key_from_file (CTRL ctrl, const char *desc_text, } buflen = st.st_size; - buf = xmalloc (buflen+1); - if (fread (buf, buflen, 1, fp) != 1) + buf = xtrymalloc (buflen+1); + if (!buf || fread (buf, buflen, 1, fp) != 1) { rc = gpg_error_from_errno (errno); log_error ("error reading `%s': %s\n", fname, strerror (errno)); @@ -347,6 +334,7 @@ agent_key_from_file (CTRL ctrl, const char *desc_text, return rc; } + /* Convert the file into a gcrypt S-expression object. */ rc = gcry_sexp_sscan (&s_skey, &erroff, buf, buflen); xfree (fname); fclose (fp); @@ -357,18 +345,52 @@ agent_key_from_file (CTRL ctrl, const char *desc_text, (unsigned int)erroff, gpg_strerror (rc)); return rc; } + *result = s_skey; + return 0; +} + + +/* Return the secret key as an S-Exp in RESULT after locating it using + the grip. Returns NULL in RESULT if the operation should be + diverted to a token; SHADOW_INFO will point then to an allocated + S-Expression with the shadow_info part from the file. With + IGNORE_CACHE passed as true the passphrase is not taken from the + cache. DESC_TEXT may be set to present a custom description for the + pinentry. */ +gpg_error_t +agent_key_from_file (ctrl_t ctrl, const char *desc_text, + const unsigned char *grip, unsigned char **shadow_info, + int ignore_cache, gcry_sexp_t *result) +{ + int rc; + unsigned char *buf; + size_t len, buflen, erroff; + gcry_sexp_t s_skey; + int got_shadow_info = 0; + + *result = NULL; + if (shadow_info) + *shadow_info = NULL; + + rc = read_key_file (grip, &s_skey); + if (rc) + return rc; + + /* For use with the protection functions we also need the key as an + canonical encoded S-expression in abuffer. Create this buffer + now. */ len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); assert (len); buf = xtrymalloc (len); if (!buf) { - rc = out_of_core (); + rc = gpg_error_from_errno (errno); gcry_sexp_release (s_skey); return rc; } len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, buf, len); assert (len); - gcry_sexp_release (s_skey); + switch (agent_private_key_type (buf)) { @@ -381,7 +403,7 @@ agent_key_from_file (CTRL ctrl, const char *desc_text, char *desc_text_final; const char *comment = NULL; - /* Note, that we will take the comment as a C styring for + /* Note, that we will take the comment as a C string for display purposes; i.e. all stuff beyond a Nul character is ignored. */ comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0); @@ -460,6 +482,8 @@ agent_key_from_file (CTRL ctrl, const char *desc_text, rc = gpg_error (GPG_ERR_BAD_SECKEY); break; } + gcry_sexp_release (s_skey); + s_skey = NULL; if (rc || got_shadow_info) { xfree (buf); @@ -481,6 +505,200 @@ agent_key_from_file (CTRL ctrl, const char *desc_text, return 0; } + + +/* Return the public key for the keygrip GRIP. The result is stored + at RESULT. This function extracts the public key from the private + key database. On failure an error code is returned and NULL stored + at RESULT. */ +gpg_error_t +agent_public_key_from_file (ctrl_t ctrl, + const unsigned char *grip, + gcry_sexp_t *result) +{ + int i, idx, rc; + gcry_sexp_t s_skey; + const char *algoname; + gcry_sexp_t uri_sexp, comment_sexp; + const char *uri, *comment; + size_t uri_length, comment_length; + char *format, *p; + void *args[4+2+2+1]; /* Size is max. # of elements + 2 for uri + 2 + for comment + end-of-list. */ + int argidx; + gcry_sexp_t list, l2; + const char *name; + const char *s; + size_t n; + const char *elems; + gcry_mpi_t *array; + + *result = NULL; + + rc = read_key_file (grip, &s_skey); + if (rc) + return rc; + + list = gcry_sexp_find_token (s_skey, "shadowed-private-key", 0 ); + if (!list) + list = gcry_sexp_find_token (s_skey, "protected-private-key", 0 ); + if (!list) + list = gcry_sexp_find_token (s_skey, "private-key", 0 ); + if (!list) + { + log_error ("invalid private key format\n"); + gcry_sexp_release (s_skey); + return gpg_error (GPG_ERR_BAD_SECKEY); + } + + l2 = gcry_sexp_cadr (list); + gcry_sexp_release (list); + list = l2; + name = gcry_sexp_nth_data (list, 0, &n); + if (n==3 && !memcmp (name, "rsa", 3)) + { + algoname = "rsa"; + elems = "ne"; + } + else if (n==3 && !memcmp (name, "dsa", 3)) + { + algoname = "dsa"; + elems = "pqgy"; + } + else if (n==3 && !memcmp (name, "elg", 3)) + { + algoname = "elg"; + elems = "pgy"; + } + else + { + log_error ("unknown private key algorithm\n"); + gcry_sexp_release (list); + gcry_sexp_release (s_skey); + return gpg_error (GPG_ERR_BAD_SECKEY); + } + + /* Allocate an array for the parameters and copy them out of the + secret key. FIXME: We should have a generic copy function. */ + array = xtrycalloc (strlen(elems) + 1, sizeof *array); + if (!array) + { + rc = gpg_error_from_errno (errno); + gcry_sexp_release (list); + gcry_sexp_release (s_skey); + return rc; + } + + for (idx=0, s=elems; *s; s++, idx++ ) + { + l2 = gcry_sexp_find_token (list, s, 1); + if (!l2) + { + /* Required parameter not found. */ + for (i=0; i