diff options
Diffstat (limited to '')
-rw-r--r-- | agent/command-ssh.c | 3848 |
1 files changed, 3848 insertions, 0 deletions
diff --git a/agent/command-ssh.c b/agent/command-ssh.c new file mode 100644 index 0000000..bcc78bd --- /dev/null +++ b/agent/command-ssh.c @@ -0,0 +1,3848 @@ +/* command-ssh.c - gpg-agent's implementation of the ssh-agent protocol. + * Copyright (C) 2004-2006, 2009, 2012 Free Software Foundation, Inc. + * Copyright (C) 2004-2006, 2009, 2012-2014 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +/* Only v2 of the ssh-agent protocol is implemented. Relevant RFCs + are: + + RFC-4250 - Protocol Assigned Numbers + RFC-4251 - Protocol Architecture + RFC-4252 - Authentication Protocol + RFC-4253 - Transport Layer Protocol + RFC-5656 - ECC support + + The protocol for the agent is defined in: + + https://tools.ietf.org/html/draft-miller-ssh-agent + + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <assert.h> +#ifndef HAVE_W32_SYSTEM +#include <sys/socket.h> +#include <sys/un.h> +#endif /*!HAVE_W32_SYSTEM*/ +#ifdef HAVE_SYS_UCRED_H +#include <sys/ucred.h> +#endif +#ifdef HAVE_UCRED_H +#include <ucred.h> +#endif + +#include "agent.h" + +#include "../common/i18n.h" +#include "../common/util.h" +#include "../common/ssh-utils.h" + + + + +/* Request types. */ +#define SSH_REQUEST_REQUEST_IDENTITIES 11 +#define SSH_REQUEST_SIGN_REQUEST 13 +#define SSH_REQUEST_ADD_IDENTITY 17 +#define SSH_REQUEST_REMOVE_IDENTITY 18 +#define SSH_REQUEST_REMOVE_ALL_IDENTITIES 19 +#define SSH_REQUEST_LOCK 22 +#define SSH_REQUEST_UNLOCK 23 +#define SSH_REQUEST_ADD_ID_CONSTRAINED 25 + +/* Options. */ +#define SSH_OPT_CONSTRAIN_LIFETIME 1 +#define SSH_OPT_CONSTRAIN_CONFIRM 2 + +/* Response types. */ +#define SSH_RESPONSE_SUCCESS 6 +#define SSH_RESPONSE_FAILURE 5 +#define SSH_RESPONSE_IDENTITIES_ANSWER 12 +#define SSH_RESPONSE_SIGN_RESPONSE 14 + +/* Other constants. */ +#define SSH_DSA_SIGNATURE_PADDING 20 +#define SSH_DSA_SIGNATURE_ELEMS 2 +#define SSH_AGENT_RSA_SHA2_256 0x02 +#define SSH_AGENT_RSA_SHA2_512 0x04 +#define SPEC_FLAG_USE_PKCS1V2 (1 << 0) +#define SPEC_FLAG_IS_ECDSA (1 << 1) +#define SPEC_FLAG_IS_EdDSA (1 << 2) /*(lowercase 'd' on purpose.)*/ +#define SPEC_FLAG_WITH_CERT (1 << 7) + +/* The name of the control file. */ +#define SSH_CONTROL_FILE_NAME "sshcontrol" + +/* The blurb we put into the header of a newly created control file. */ +static const char sshcontrolblurb[] = +"# List of allowed ssh keys. Only keys present in this file are used\n" +"# in the SSH protocol. The ssh-add tool may add new entries to this\n" +"# file to enable them; you may also add them manually. Comment\n" +"# lines, like this one, as well as empty lines are ignored. Lines do\n" +"# have a certain length limit but this is not serious limitation as\n" +"# the format of the entries is fixed and checked by gpg-agent. A\n" +"# non-comment line starts with optional white spaces, followed by the\n" +"# keygrip of the key given as 40 hex digits, optionally followed by a\n" +"# caching TTL in seconds, and another optional field for arbitrary\n" +"# flags. Prepend the keygrip with an '!' mark to disable it.\n" +"\n"; + + +/* Macros. */ + +/* Return a new uint32 with b0 being the most significant byte and b3 + being the least significant byte. */ +#define uint32_construct(b0, b1, b2, b3) \ + ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) + + + + +/* + * Basic types. + */ + +/* Type for a request handler. */ +typedef gpg_error_t (*ssh_request_handler_t) (ctrl_t ctrl, + estream_t request, + estream_t response); + + +struct ssh_key_type_spec; +typedef struct ssh_key_type_spec ssh_key_type_spec_t; + +/* Type, which is used for associating request handlers with the + appropriate request IDs. */ +typedef struct ssh_request_spec +{ + unsigned char type; + ssh_request_handler_t handler; + const char *identifier; + unsigned int secret_input; +} ssh_request_spec_t; + +/* Type for "key modifier functions", which are necessary since + OpenSSH and GnuPG treat key material slightly different. A key + modifier is called right after a new key identity has been received + in order to "sanitize" the material. */ +typedef gpg_error_t (*ssh_key_modifier_t) (const char *elems, + gcry_mpi_t *mpis); + +/* The encoding of a generated signature is dependent on the + algorithm; therefore algorithm specific signature encoding + functions are necessary. */ +typedef gpg_error_t (*ssh_signature_encoder_t) (ssh_key_type_spec_t *spec, + estream_t signature_blob, + gcry_sexp_t sig); + +/* Type, which is used for boundling all the algorithm specific + information together in a single object. */ +struct ssh_key_type_spec +{ + /* Algorithm identifier as used by OpenSSH. */ + const char *ssh_identifier; + + /* Human readable name of the algorithm. */ + const char *name; + + /* Algorithm identifier as used by GnuPG. */ + int algo; + + /* List of MPI names for secret keys; order matches the one of the + agent protocol. */ + const char *elems_key_secret; + + /* List of MPI names for public keys; order matches the one of the + agent protocol. */ + const char *elems_key_public; + + /* List of MPI names for signature data. */ + const char *elems_signature; + + /* List of MPI names for secret keys; order matches the one, which + is required by gpg-agent's key access layer. */ + const char *elems_sexp_order; + + /* Key modifier function. Key modifier functions are necessary in + order to fix any inconsistencies between the representation of + keys on the SSH and on the GnuPG side. */ + ssh_key_modifier_t key_modifier; + + /* Signature encoder function. Signature encoder functions are + necessary since the encoding of signatures depends on the used + algorithm. */ + ssh_signature_encoder_t signature_encoder; + + /* The name of the ECC curve or NULL for non-ECC algos. This is the + * canonical name for the curve as specified by RFC-5656. */ + const char *curve_name; + + /* An alias for curve_name or NULL. Actually this is Libcgrypt's + * primary name of the curve. */ + const char *alt_curve_name; + + /* The hash algorithm to be used with this key. 0 for using the + default. */ + int hash_algo; + + /* Misc flags. */ + unsigned int flags; +}; + + +/* Definition of an object to access the sshcontrol file. */ +struct ssh_control_file_s +{ + char *fname; /* Name of the file. */ + estream_t fp; /* This is never NULL. */ + int lnr; /* The current line number. */ + struct { + int valid; /* True if the data of this structure is valid. */ + int disabled; /* The item is disabled. */ + int ttl; /* The TTL of the item. */ + int confirm; /* The confirm flag is set. */ + char hexgrip[40+1]; /* The hexgrip of the item (uppercase). */ + } item; +}; + + +/* Prototypes. */ +static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl, + estream_t request, + estream_t response); +static gpg_error_t ssh_handler_sign_request (ctrl_t ctrl, + estream_t request, + estream_t response); +static gpg_error_t ssh_handler_add_identity (ctrl_t ctrl, + estream_t request, + estream_t response); +static gpg_error_t ssh_handler_remove_identity (ctrl_t ctrl, + estream_t request, + estream_t response); +static gpg_error_t ssh_handler_remove_all_identities (ctrl_t ctrl, + estream_t request, + estream_t response); +static gpg_error_t ssh_handler_lock (ctrl_t ctrl, + estream_t request, + estream_t response); +static gpg_error_t ssh_handler_unlock (ctrl_t ctrl, + estream_t request, + estream_t response); + +static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis); +static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, + gcry_sexp_t signature); +static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, + gcry_sexp_t signature); +static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, + gcry_sexp_t signature); +static gpg_error_t ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, + gcry_sexp_t signature); +static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **comment); + + + +/* Global variables. */ + + +/* Associating request types with the corresponding request + handlers. */ + +static const ssh_request_spec_t request_specs[] = + { +#define REQUEST_SPEC_DEFINE(id, name, secret_input) \ + { SSH_REQUEST_##id, ssh_handler_##name, #name, secret_input } + + REQUEST_SPEC_DEFINE (REQUEST_IDENTITIES, request_identities, 1), + REQUEST_SPEC_DEFINE (SIGN_REQUEST, sign_request, 0), + REQUEST_SPEC_DEFINE (ADD_IDENTITY, add_identity, 1), + REQUEST_SPEC_DEFINE (ADD_ID_CONSTRAINED, add_identity, 1), + REQUEST_SPEC_DEFINE (REMOVE_IDENTITY, remove_identity, 0), + REQUEST_SPEC_DEFINE (REMOVE_ALL_IDENTITIES, remove_all_identities, 0), + REQUEST_SPEC_DEFINE (LOCK, lock, 0), + REQUEST_SPEC_DEFINE (UNLOCK, unlock, 0) +#undef REQUEST_SPEC_DEFINE + }; + + +/* Table holding key type specifications. */ +static const ssh_key_type_spec_t ssh_key_types[] = + { + { + "ssh-ed25519", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd", + NULL, ssh_signature_encoder_eddsa, + "Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA + }, + { + "ssh-rsa", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu", + ssh_key_modifier_rsa, ssh_signature_encoder_rsa, + NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2 + }, + { + "ssh-dss", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx", + NULL, ssh_signature_encoder_dsa, + NULL, NULL, 0, 0 + }, + { + "ecdsa-sha2-nistp256", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", + NULL, ssh_signature_encoder_ecdsa, + "nistp256", "NIST P-256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA + }, + { + "ecdsa-sha2-nistp384", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", + NULL, ssh_signature_encoder_ecdsa, + "nistp384", "NIST P-384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA + }, + { + "ecdsa-sha2-nistp521", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", + NULL, ssh_signature_encoder_ecdsa, + "nistp521", "NIST P-521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA + }, + { + "ssh-ed25519-cert-v01@openssh.com", "Ed25519", + GCRY_PK_EDDSA, "qd", "q", "rs", "qd", + NULL, ssh_signature_encoder_eddsa, + "Ed25519", NULL, 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT + }, + { + "ssh-rsa-cert-v01@openssh.com", "RSA", + GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu", + ssh_key_modifier_rsa, ssh_signature_encoder_rsa, + NULL, NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT + }, + { + "ssh-dss-cert-v01@openssh.com", "DSA", + GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx", + NULL, ssh_signature_encoder_dsa, + NULL, NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT + }, + { + "ecdsa-sha2-nistp256-cert-v01@openssh.com", "ECDSA", + GCRY_PK_ECC, "qd", "q", "rs", "qd", + NULL, ssh_signature_encoder_ecdsa, + "nistp256", "NIST P-256", GCRY_MD_SHA256, + SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT + }, + { + "ecdsa-sha2-nistp384-cert-v01@openssh.com", "ECDSA", + GCRY_PK_ECC, "qd", "q", "rs", "qd", + NULL, ssh_signature_encoder_ecdsa, + "nistp384", "NIST P-384", GCRY_MD_SHA384, + SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT + }, + { + "ecdsa-sha2-nistp521-cert-v01@openssh.com", "ECDSA", + GCRY_PK_ECC, "qd", "q", "rs", "qd", + NULL, ssh_signature_encoder_ecdsa, + "nistp521", "NIST P-521", GCRY_MD_SHA512, + SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT + } + }; + + + + + +/* + General utility functions. + */ + +/* A secure realloc, i.e. it makes sure to allocate secure memory if A + is NULL. This is required because the standard gcry_realloc does + not know whether to allocate secure or normal if NULL is passed as + existing buffer. */ +static void * +realloc_secure (void *a, size_t n) +{ + void *p; + + if (a) + p = gcry_realloc (a, n); + else + p = gcry_malloc_secure (n); + + return p; +} + + +/* Lookup the ssh-identifier for the ECC curve CURVE_NAME. Returns + * NULL if not found. If found the ssh indetifier is returned and a + * pointer to the canonical curve name as specified for ssh is stored + * at R_CANON_NAME. */ +static const char * +ssh_identifier_from_curve_name (const char *curve_name, + const char **r_canon_name) +{ + int i; + + for (i = 0; i < DIM (ssh_key_types); i++) + if (ssh_key_types[i].curve_name + && (!strcmp (ssh_key_types[i].curve_name, curve_name) + || (ssh_key_types[i].alt_curve_name + && !strcmp (ssh_key_types[i].alt_curve_name, curve_name)))) + { + *r_canon_name = ssh_key_types[i].curve_name; + return ssh_key_types[i].ssh_identifier; + } + + return NULL; +} + + +/* + Primitive I/O functions. + */ + + +/* Read a byte from STREAM, store it in B. */ +static gpg_error_t +stream_read_byte (estream_t stream, unsigned char *b) +{ + gpg_error_t err; + int ret; + + ret = es_fgetc (stream); + if (ret == EOF) + { + if (es_ferror (stream)) + err = gpg_error_from_syserror (); + else + err = gpg_error (GPG_ERR_EOF); + *b = 0; + } + else + { + *b = ret & 0xFF; + err = 0; + } + + return err; +} + +/* Write the byte contained in B to STREAM. */ +static gpg_error_t +stream_write_byte (estream_t stream, unsigned char b) +{ + gpg_error_t err; + int ret; + + ret = es_fputc (b, stream); + if (ret == EOF) + err = gpg_error_from_syserror (); + else + err = 0; + + return err; +} + + +/* Read a uint32 from STREAM, store it in UINT32. */ +static gpg_error_t +stream_read_uint32 (estream_t stream, u32 *uint32) +{ + unsigned char buffer[4]; + size_t bytes_read; + gpg_error_t err; + int ret; + + ret = es_read (stream, buffer, sizeof (buffer), &bytes_read); + if (ret) + err = gpg_error_from_syserror (); + else + { + if (bytes_read != sizeof (buffer)) + err = gpg_error (GPG_ERR_EOF); + else + { + u32 n; + + n = uint32_construct (buffer[0], buffer[1], buffer[2], buffer[3]); + *uint32 = n; + err = 0; + } + } + + return err; +} + +/* Write the uint32 contained in UINT32 to STREAM. */ +static gpg_error_t +stream_write_uint32 (estream_t stream, u32 uint32) +{ + unsigned char buffer[4]; + gpg_error_t err; + int ret; + + buffer[0] = uint32 >> 24; + buffer[1] = uint32 >> 16; + buffer[2] = uint32 >> 8; + buffer[3] = uint32 >> 0; + + ret = es_write (stream, buffer, sizeof (buffer), NULL); + if (ret) + err = gpg_error_from_syserror (); + else + err = 0; + + return err; +} + +/* Read SIZE bytes from STREAM into BUFFER. */ +static gpg_error_t +stream_read_data (estream_t stream, unsigned char *buffer, size_t size) +{ + gpg_error_t err; + size_t bytes_read; + int ret; + + ret = es_read (stream, buffer, size, &bytes_read); + if (ret) + err = gpg_error_from_syserror (); + else + { + if (bytes_read != size) + err = gpg_error (GPG_ERR_EOF); + else + err = 0; + } + + return err; +} + +/* Skip over SIZE bytes from STREAM. */ +static gpg_error_t +stream_read_skip (estream_t stream, size_t size) +{ + char buffer[128]; + size_t bytes_to_read, bytes_read; + int ret; + + do + { + bytes_to_read = size; + if (bytes_to_read > sizeof buffer) + bytes_to_read = sizeof buffer; + + ret = es_read (stream, buffer, bytes_to_read, &bytes_read); + if (ret) + return gpg_error_from_syserror (); + else if (bytes_read != bytes_to_read) + return gpg_error (GPG_ERR_EOF); + else + size -= bytes_to_read; + } + while (size); + + return 0; +} + + +/* Write SIZE bytes from BUFFER to STREAM. */ +static gpg_error_t +stream_write_data (estream_t stream, const unsigned char *buffer, size_t size) +{ + gpg_error_t err; + int ret; + + ret = es_write (stream, buffer, size, NULL); + if (ret) + err = gpg_error_from_syserror (); + else + err = 0; + + return err; +} + +/* Read a binary string from STREAM into STRING, store size of string + in STRING_SIZE. Append a hidden nul so that the result may + directly be used as a C string. Depending on SECURE use secure + memory for STRING. If STRING is NULL do only a dummy read. */ +static gpg_error_t +stream_read_string (estream_t stream, unsigned int secure, + unsigned char **string, u32 *string_size) +{ + gpg_error_t err; + unsigned char *buffer = NULL; + u32 length = 0; + + if (string_size) + *string_size = 0; + + /* Read string length. */ + err = stream_read_uint32 (stream, &length); + if (err) + goto out; + + if (string) + { + /* Allocate space. */ + if (secure) + buffer = xtrymalloc_secure (length + 1); + else + buffer = xtrymalloc (length + 1); + if (! buffer) + { + err = gpg_error_from_syserror (); + goto out; + } + + /* Read data. */ + err = stream_read_data (stream, buffer, length); + if (err) + goto out; + + /* Finalize string object. */ + buffer[length] = 0; + *string = buffer; + } + else /* Dummy read requested. */ + { + err = stream_read_skip (stream, length); + if (err) + goto out; + } + + if (string_size) + *string_size = length; + + out: + + if (err) + xfree (buffer); + + return err; +} + + +/* Read a binary string from STREAM and store it as an opaque MPI at + R_MPI, adding 0x40 (this is the prefix for EdDSA key in OpenPGP). + Depending on SECURE use secure memory. If the string is too large + for key material return an error. */ +static gpg_error_t +stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi) +{ + gpg_error_t err; + unsigned char *buffer = NULL; + u32 length = 0; + + *r_mpi = NULL; + + /* Read string length. */ + err = stream_read_uint32 (stream, &length); + if (err) + goto leave; + + /* To avoid excessive use of secure memory we check that an MPI is + not too large. */ + if (length > (4096/8) + 8) + { + log_error (_("ssh keys greater than %d bits are not supported\n"), 4096); + err = GPG_ERR_TOO_LARGE; + goto leave; + } + + /* Allocate space. */ + if (secure) + buffer = xtrymalloc_secure (length+1); + else + buffer = xtrymalloc (length+1); + if (!buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Read data. */ + err = stream_read_data (stream, buffer + 1, length); + if (err) + goto leave; + + buffer[0] = 0x40; + *r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*(length+1)); + buffer = NULL; + + leave: + xfree (buffer); + return err; +} + + +/* Read a C-string from STREAM, store copy in STRING. */ +static gpg_error_t +stream_read_cstring (estream_t stream, char **string) +{ + return stream_read_string (stream, 0, (unsigned char **)string, NULL); +} + + +/* Write a binary string from STRING of size STRING_N to STREAM. */ +static gpg_error_t +stream_write_string (estream_t stream, + const unsigned char *string, u32 string_n) +{ + gpg_error_t err; + + err = stream_write_uint32 (stream, string_n); + if (err) + goto out; + + err = stream_write_data (stream, string, string_n); + + out: + + return err; +} + +/* Write a C-string from STRING to STREAM. */ +static gpg_error_t +stream_write_cstring (estream_t stream, const char *string) +{ + gpg_error_t err; + + err = stream_write_string (stream, + (const unsigned char *) string, strlen (string)); + + return err; +} + +/* Read an MPI from STREAM, store it in MPINT. Depending on SECURE + use secure memory. */ +static gpg_error_t +stream_read_mpi (estream_t stream, unsigned int secure, gcry_mpi_t *mpint) +{ + unsigned char *mpi_data; + u32 mpi_data_size; + gpg_error_t err; + gcry_mpi_t mpi; + + mpi_data = NULL; + + err = stream_read_string (stream, secure, &mpi_data, &mpi_data_size); + if (err) + goto out; + + /* To avoid excessive use of secure memory we check that an MPI is + not too large. */ + if (mpi_data_size > 520) + { + log_error (_("ssh keys greater than %d bits are not supported\n"), 4096); + err = GPG_ERR_TOO_LARGE; + goto out; + } + + err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_STD, mpi_data, mpi_data_size, NULL); + if (err) + goto out; + + *mpint = mpi; + + out: + + xfree (mpi_data); + + return err; +} + +/* Write the MPI contained in MPINT to STREAM. */ +static gpg_error_t +stream_write_mpi (estream_t stream, gcry_mpi_t mpint) +{ + unsigned char *mpi_buffer; + size_t mpi_buffer_n; + gpg_error_t err; + + mpi_buffer = NULL; + + err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &mpi_buffer, &mpi_buffer_n, mpint); + if (err) + goto out; + + err = stream_write_string (stream, mpi_buffer, mpi_buffer_n); + + out: + + xfree (mpi_buffer); + + return err; +} + + +/* Copy data from SRC to DST until EOF is reached. */ +static gpg_error_t +stream_copy (estream_t dst, estream_t src) +{ + char buffer[BUFSIZ]; + size_t bytes_read; + gpg_error_t err; + int ret; + + err = 0; + while (1) + { + ret = es_read (src, buffer, sizeof (buffer), &bytes_read); + if (ret || (! bytes_read)) + { + if (ret) + err = gpg_error_from_syserror (); + break; + } + ret = es_write (dst, buffer, bytes_read, NULL); + if (ret) + { + err = gpg_error_from_syserror (); + break; + } + } + + return err; +} + +/* Open the ssh control file and create it if not available. With + APPEND passed as true the file will be opened in append mode, + otherwise in read only mode. On success 0 is returned and a new + control file object stored at R_CF. On error an error code is + returned and NULL is stored at R_CF. */ +static gpg_error_t +open_control_file (ssh_control_file_t *r_cf, int append) +{ + gpg_error_t err; + ssh_control_file_t cf; + + cf = xtrycalloc (1, sizeof *cf); + if (!cf) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Note: As soon as we start to use non blocking functions here + (i.e. where Pth might switch threads) we need to employ a + mutex. */ + cf->fname = make_filename_try (gnupg_homedir (), SSH_CONTROL_FILE_NAME, NULL); + if (!cf->fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + /* FIXME: With "a+" we are not able to check whether this will + be created and thus the blurb needs to be written first. */ + cf->fp = es_fopen (cf->fname, append? "a+":"r"); + if (!cf->fp && errno == ENOENT) + { + estream_t stream = es_fopen (cf->fname, "wx,mode=-rw-r"); + if (!stream) + { + err = gpg_error_from_syserror (); + log_error (_("can't create '%s': %s\n"), + cf->fname, gpg_strerror (err)); + goto leave; + } + es_fputs (sshcontrolblurb, stream); + es_fclose (stream); + cf->fp = es_fopen (cf->fname, append? "a+":"r"); + } + + if (!cf->fp) + { + err = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), + cf->fname, gpg_strerror (err)); + goto leave; + } + + err = 0; + + leave: + if (err && cf) + { + if (cf->fp) + es_fclose (cf->fp); + xfree (cf->fname); + xfree (cf); + } + else + *r_cf = cf; + + return err; +} + + +static void +rewind_control_file (ssh_control_file_t cf) +{ + es_fseek (cf->fp, 0, SEEK_SET); + cf->lnr = 0; + es_clearerr (cf->fp); +} + + +static void +close_control_file (ssh_control_file_t cf) +{ + if (!cf) + return; + es_fclose (cf->fp); + xfree (cf->fname); + xfree (cf); +} + + + +/* Read the next line from the control file and store the data in CF. + Returns 0 on success, GPG_ERR_EOF on EOF, or other error codes. */ +static gpg_error_t +read_control_file_item (ssh_control_file_t cf) +{ + int c, i, n; + char *p, *pend, line[256]; + long ttl = 0; + + cf->item.valid = 0; + es_clearerr (cf->fp); + + do + { + if (!es_fgets (line, DIM(line)-1, cf->fp) ) + { + if (es_feof (cf->fp)) + return gpg_error (GPG_ERR_EOF); + return gpg_error_from_syserror (); + } + cf->lnr++; + + if (!*line || line[strlen(line)-1] != '\n') + { + /* Eat until end of line */ + while ((c = es_getc (cf->fp)) != EOF && c != '\n') + ; + return gpg_error (*line? GPG_ERR_LINE_TOO_LONG + : GPG_ERR_INCOMPLETE_LINE); + } + + /* Allow for empty lines and spaces */ + for (p=line; spacep (p); p++) + ; + } + while (!*p || *p == '\n' || *p == '#'); + + cf->item.disabled = 0; + if (*p == '!') + { + cf->item.disabled = 1; + for (p++; spacep (p); p++) + ; + } + + for (i=0; hexdigitp (p) && i < 40; p++, i++) + cf->item.hexgrip[i] = (*p >= 'a'? (*p & 0xdf): *p); + cf->item.hexgrip[i] = 0; + if (i != 40 || !(spacep (p) || *p == '\n')) + { + log_error ("%s:%d: invalid formatted line\n", cf->fname, cf->lnr); + return gpg_error (GPG_ERR_BAD_DATA); + } + + ttl = strtol (p, &pend, 10); + p = pend; + if (!(spacep (p) || *p == '\n') || (int)ttl < -1) + { + log_error ("%s:%d: invalid TTL value; assuming 0\n", cf->fname, cf->lnr); + cf->item.ttl = 0; + } + cf->item.ttl = ttl; + + /* Now check for key-value pairs of the form NAME[=VALUE]. */ + cf->item.confirm = 0; + while (*p) + { + for (; spacep (p) && *p != '\n'; p++) + ; + if (!*p || *p == '\n') + break; + n = strcspn (p, "= \t\n"); + if (p[n] == '=') + { + log_error ("%s:%d: assigning a value to a flag is not yet supported; " + "flag ignored\n", cf->fname, cf->lnr); + p++; + } + else if (n == 7 && !memcmp (p, "confirm", 7)) + { + cf->item.confirm = 1; + } + else + log_error ("%s:%d: invalid flag '%.*s'; ignored\n", + cf->fname, cf->lnr, n, p); + p += n; + } + + /* log_debug ("%s:%d: grip=%s ttl=%d%s%s\n", */ + /* cf->fname, cf->lnr, */ + /* cf->item.hexgrip, cf->item.ttl, */ + /* cf->item.disabled? " disabled":"", */ + /* cf->item.confirm? " confirm":""); */ + + cf->item.valid = 1; + return 0; /* Okay: valid entry found. */ +} + + + +/* Search the control file CF from the beginning until a matching + HEXGRIP is found; return success in this case and store true at + DISABLED if the found key has been disabled. If R_TTL is not NULL + a specified TTL for that key is stored there. If R_CONFIRM is not + NULL it is set to 1 if the key has the confirm flag set. */ +static gpg_error_t +search_control_file (ssh_control_file_t cf, const char *hexgrip, + int *r_disabled, int *r_ttl, int *r_confirm) +{ + gpg_error_t err; + + assert (strlen (hexgrip) == 40 ); + + if (r_disabled) + *r_disabled = 0; + if (r_ttl) + *r_ttl = 0; + if (r_confirm) + *r_confirm = 0; + + rewind_control_file (cf); + while (!(err=read_control_file_item (cf))) + { + if (!cf->item.valid) + continue; /* Should not happen. */ + if (!strcmp (hexgrip, cf->item.hexgrip)) + break; + } + if (!err) + { + if (r_disabled) + *r_disabled = cf->item.disabled; + if (r_ttl) + *r_ttl = cf->item.ttl; + if (r_confirm) + *r_confirm = cf->item.confirm; + } + return err; +} + + + +/* Add an entry to the control file to mark the key with the keygrip + HEXGRIP as usable for SSH; i.e. it will be returned when ssh asks + for it. FMTFPR is the fingerprint string. This function is in + general used to add a key received through the ssh-add function. + We can assume that the user wants to allow ssh using this key. */ +static gpg_error_t +add_control_entry (ctrl_t ctrl, ssh_key_type_spec_t *spec, + const char *hexgrip, gcry_sexp_t key, + int ttl, int confirm) +{ + gpg_error_t err; + ssh_control_file_t cf; + int disabled; + char *fpr_md5 = NULL; + char *fpr_sha256 = NULL; + + (void)ctrl; + + err = open_control_file (&cf, 1); + if (err) + return err; + + err = search_control_file (cf, hexgrip, &disabled, NULL, NULL); + if (err && gpg_err_code(err) == GPG_ERR_EOF) + { + struct tm *tp; + time_t atime = time (NULL); + + err = ssh_get_fingerprint_string (key, GCRY_MD_MD5, &fpr_md5); + if (err) + goto out; + + err = ssh_get_fingerprint_string (key, GCRY_MD_SHA256, &fpr_sha256); + if (err) + goto out; + + /* Not yet in the file - add it. Because the file has been + opened in append mode, we simply need to write to it. */ + tp = localtime (&atime); + es_fprintf (cf->fp, + ("# %s key added on: %04d-%02d-%02d %02d:%02d:%02d\n" + "# Fingerprints: %s\n" + "# %s\n" + "%s %d%s\n"), + spec->name, + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, + tp->tm_hour, tp->tm_min, tp->tm_sec, + fpr_md5, fpr_sha256, hexgrip, ttl, confirm? " confirm":""); + + } + out: + xfree (fpr_md5); + xfree (fpr_sha256); + close_control_file (cf); + return 0; +} + + +/* Scan the sshcontrol file and return the TTL. */ +static int +ttl_from_sshcontrol (const char *hexgrip) +{ + ssh_control_file_t cf; + int disabled, ttl; + + if (!hexgrip || strlen (hexgrip) != 40) + return 0; /* Wrong input: Use global default. */ + + if (open_control_file (&cf, 0)) + return 0; /* Error: Use the global default TTL. */ + + if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL) + || disabled) + ttl = 0; /* Use the global default if not found or disabled. */ + + close_control_file (cf); + + return ttl; +} + + +/* Scan the sshcontrol file and return the confirm flag. */ +static int +confirm_flag_from_sshcontrol (const char *hexgrip) +{ + ssh_control_file_t cf; + int disabled, confirm; + + if (!hexgrip || strlen (hexgrip) != 40) + return 1; /* Wrong input: Better ask for confirmation. */ + + if (open_control_file (&cf, 0)) + return 1; /* Error: Better ask for confirmation. */ + + if (search_control_file (cf, hexgrip, &disabled, NULL, &confirm) + || disabled) + confirm = 0; /* If not found or disabled, there is no reason to + ask for confirmation. */ + + close_control_file (cf); + + return confirm; +} + + + + +/* Open the ssh control file for reading. This is a public version of + open_control_file. The caller must use ssh_close_control_file to + release the returned handle. */ +ssh_control_file_t +ssh_open_control_file (void) +{ + ssh_control_file_t cf; + + /* Then look at all the registered and non-disabled keys. */ + if (open_control_file (&cf, 0)) + return NULL; + return cf; +} + +/* Close an ssh control file handle. This is the public version of + close_control_file. CF may be NULL. */ +void +ssh_close_control_file (ssh_control_file_t cf) +{ + close_control_file (cf); +} + +/* Read the next item from the ssh control file. The function returns + 0 if a item was read, GPG_ERR_EOF on eof or another error value. + R_HEXGRIP shall either be null or a BUFFER of at least 41 byte. + R_DISABLED, R_TTLm and R_CONFIRM return flags from the control + file; they are only set on success. */ +gpg_error_t +ssh_read_control_file (ssh_control_file_t cf, + char *r_hexgrip, + int *r_disabled, int *r_ttl, int *r_confirm) +{ + gpg_error_t err; + + do + err = read_control_file_item (cf); + while (!err && !cf->item.valid); + if (!err) + { + if (r_hexgrip) + strcpy (r_hexgrip, cf->item.hexgrip); + if (r_disabled) + *r_disabled = cf->item.disabled; + if (r_ttl) + *r_ttl = cf->item.ttl; + if (r_confirm) + *r_confirm = cf->item.confirm; + } + return err; +} + + +/* Search for a key with HEXGRIP in sshcontrol and return all + info. */ +gpg_error_t +ssh_search_control_file (ssh_control_file_t cf, + const char *hexgrip, + int *r_disabled, int *r_ttl, int *r_confirm) +{ + gpg_error_t err; + int i; + const char *s; + char uphexgrip[41]; + + /* We need to make sure that HEXGRIP is all uppercase. The easiest + way to do this and also check its length is by copying to a + second buffer. */ + for (i=0, s=hexgrip; i < 40 && *s; s++, i++) + uphexgrip[i] = *s >= 'a'? (*s & 0xdf): *s; + uphexgrip[i] = 0; + if (i != 40) + err = gpg_error (GPG_ERR_INV_LENGTH); + else + err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm); + if (gpg_err_code (err) == GPG_ERR_EOF) + err = gpg_error (GPG_ERR_NOT_FOUND); + return err; +} + + + + +/* + + MPI lists. + + */ + +/* Free the list of MPIs MPI_LIST. */ +static void +mpint_list_free (gcry_mpi_t *mpi_list) +{ + if (mpi_list) + { + unsigned int i; + + for (i = 0; mpi_list[i]; i++) + gcry_mpi_release (mpi_list[i]); + xfree (mpi_list); + } +} + +/* Receive key material MPIs from STREAM according to KEY_SPEC; + depending on SECRET expect a public key or secret key. CERT is the + certificate blob used if KEY_SPEC indicates the certificate format; + it needs to be positioned to the end of the nonce. The newly + allocated list of MPIs is stored in MPI_LIST. Returns usual error + code. */ +static gpg_error_t +ssh_receive_mpint_list (estream_t stream, int secret, + ssh_key_type_spec_t *spec, estream_t cert, + gcry_mpi_t **mpi_list) +{ + const char *elems_public; + unsigned int elems_n; + const char *elems; + int elem_is_secret; + gcry_mpi_t *mpis = NULL; + gpg_error_t err = 0; + unsigned int i; + + if (secret) + elems = spec->elems_key_secret; + else + elems = spec->elems_key_public; + elems_n = strlen (elems); + elems_public = spec->elems_key_public; + + /* Check that either both, CERT and the WITH_CERT flag, are given or + none of them. */ + if (!(!!(spec->flags & SPEC_FLAG_WITH_CERT) ^ !cert)) + { + err = gpg_error (GPG_ERR_INV_CERT_OBJ); + goto out; + } + + mpis = xtrycalloc (elems_n + 1, sizeof *mpis ); + if (!mpis) + { + err = gpg_error_from_syserror (); + goto out; + } + + elem_is_secret = 0; + for (i = 0; i < elems_n; i++) + { + if (secret) + elem_is_secret = !strchr (elems_public, elems[i]); + + if (cert && !elem_is_secret) + err = stream_read_mpi (cert, elem_is_secret, &mpis[i]); + else + err = stream_read_mpi (stream, elem_is_secret, &mpis[i]); + if (err) + goto out; + } + + *mpi_list = mpis; + mpis = NULL; + + out: + if (err) + mpint_list_free (mpis); + + return err; +} + + + +/* Key modifier function for RSA. */ +static gpg_error_t +ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis) +{ + gcry_mpi_t p; + gcry_mpi_t q; + gcry_mpi_t u; + + if (strcmp (elems, "nedupq")) + /* Modifying only necessary for secret keys. */ + goto out; + + u = mpis[3]; + p = mpis[4]; + q = mpis[5]; + + if (gcry_mpi_cmp (p, q) > 0) + { + /* P shall be smaller then Q! Swap primes. iqmp becomes u. */ + gcry_mpi_t tmp; + + tmp = mpis[4]; + mpis[4] = mpis[5]; + mpis[5] = tmp; + } + else + /* U needs to be recomputed. */ + gcry_mpi_invm (u, p, q); + + out: + + return 0; +} + +/* Signature encoder function for RSA. */ +static gpg_error_t +ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, + gcry_sexp_t s_signature) +{ + gpg_error_t err = 0; + gcry_sexp_t valuelist = NULL; + gcry_sexp_t sublist = NULL; + gcry_mpi_t sig_value = NULL; + gcry_mpi_t *mpis = NULL; + const char *elems; + size_t elems_n; + int i; + + unsigned char *data; + size_t data_n; + gcry_mpi_t s; + + valuelist = gcry_sexp_nth (s_signature, 1); + if (!valuelist) + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + + elems = spec->elems_signature; + elems_n = strlen (elems); + + mpis = xtrycalloc (elems_n + 1, sizeof *mpis); + if (!mpis) + { + err = gpg_error_from_syserror (); + goto out; + } + + for (i = 0; i < elems_n; i++) + { + sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); + if (!sublist) + { + err = gpg_error (GPG_ERR_INV_SEXP); + break; + } + + sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG); + if (!sig_value) + { + err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ + break; + } + gcry_sexp_release (sublist); + sublist = NULL; + + mpis[i] = sig_value; + } + if (err) + goto out; + + /* RSA specific */ + s = mpis[0]; + + err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, s); + if (err) + goto out; + + err = stream_write_string (signature_blob, data, data_n); + xfree (data); + + out: + gcry_sexp_release (valuelist); + gcry_sexp_release (sublist); + mpint_list_free (mpis); + return err; +} + + +/* Signature encoder function for DSA. */ +static gpg_error_t +ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, + gcry_sexp_t s_signature) +{ + gpg_error_t err = 0; + gcry_sexp_t valuelist = NULL; + gcry_sexp_t sublist = NULL; + gcry_mpi_t sig_value = NULL; + gcry_mpi_t *mpis = NULL; + const char *elems; + size_t elems_n; + int i; + + unsigned char buffer[SSH_DSA_SIGNATURE_PADDING * SSH_DSA_SIGNATURE_ELEMS]; + unsigned char *data = NULL; + size_t data_n; + + valuelist = gcry_sexp_nth (s_signature, 1); + if (!valuelist) + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + + elems = spec->elems_signature; + elems_n = strlen (elems); + + mpis = xtrycalloc (elems_n + 1, sizeof *mpis); + if (!mpis) + { + err = gpg_error_from_syserror (); + goto out; + } + + for (i = 0; i < elems_n; i++) + { + sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); + if (!sublist) + { + err = gpg_error (GPG_ERR_INV_SEXP); + break; + } + + sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG); + if (!sig_value) + { + err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ + break; + } + gcry_sexp_release (sublist); + sublist = NULL; + + mpis[i] = sig_value; + } + if (err) + goto out; + + /* DSA specific code. */ + + /* FIXME: Why this complicated code? Why collecting boths mpis in a + buffer instead of writing them out one after the other? */ + for (i = 0; i < 2; i++) + { + err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, mpis[i]); + if (err) + break; + + if (data_n > SSH_DSA_SIGNATURE_PADDING) + { + err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ + break; + } + + memset (buffer + (i * SSH_DSA_SIGNATURE_PADDING), 0, + SSH_DSA_SIGNATURE_PADDING - data_n); + memcpy (buffer + (i * SSH_DSA_SIGNATURE_PADDING) + + (SSH_DSA_SIGNATURE_PADDING - data_n), data, data_n); + + xfree (data); + data = NULL; + } + if (err) + goto out; + + err = stream_write_string (signature_blob, buffer, sizeof (buffer)); + + out: + xfree (data); + gcry_sexp_release (valuelist); + gcry_sexp_release (sublist); + mpint_list_free (mpis); + return err; +} + + +/* Signature encoder function for ECDSA. */ +static gpg_error_t +ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec, + estream_t stream, gcry_sexp_t s_signature) +{ + gpg_error_t err = 0; + gcry_sexp_t valuelist = NULL; + gcry_sexp_t sublist = NULL; + gcry_mpi_t sig_value = NULL; + gcry_mpi_t *mpis = NULL; + const char *elems; + size_t elems_n; + int i; + + unsigned char *data[2] = {NULL, NULL}; + size_t data_n[2]; + size_t innerlen; + + valuelist = gcry_sexp_nth (s_signature, 1); + if (!valuelist) + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + + elems = spec->elems_signature; + elems_n = strlen (elems); + + mpis = xtrycalloc (elems_n + 1, sizeof *mpis); + if (!mpis) + { + err = gpg_error_from_syserror (); + goto out; + } + + for (i = 0; i < elems_n; i++) + { + sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); + if (!sublist) + { + err = gpg_error (GPG_ERR_INV_SEXP); + break; + } + + sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG); + if (!sig_value) + { + err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ + break; + } + gcry_sexp_release (sublist); + sublist = NULL; + + mpis[i] = sig_value; + } + if (err) + goto out; + + /* ECDSA specific */ + + innerlen = 0; + for (i = 0; i < DIM(data); i++) + { + err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &data[i], &data_n[i], mpis[i]); + if (err) + goto out; + innerlen += 4 + data_n[i]; + } + + err = stream_write_uint32 (stream, innerlen); + if (err) + goto out; + + for (i = 0; i < DIM(data); i++) + { + err = stream_write_string (stream, data[i], data_n[i]); + if (err) + goto out; + } + + out: + for (i = 0; i < DIM(data); i++) + xfree (data[i]); + gcry_sexp_release (valuelist); + gcry_sexp_release (sublist); + mpint_list_free (mpis); + return err; +} + + +/* Signature encoder function for EdDSA. */ +static gpg_error_t +ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec, + estream_t stream, gcry_sexp_t s_signature) +{ + gpg_error_t err = 0; + gcry_sexp_t valuelist = NULL; + gcry_sexp_t sublist = NULL; + const char *elems; + size_t elems_n; + int i; + + unsigned char *data[2] = {NULL, NULL}; + size_t data_n[2]; + size_t totallen = 0; + + valuelist = gcry_sexp_nth (s_signature, 1); + if (!valuelist) + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + + elems = spec->elems_signature; + elems_n = strlen (elems); + + if (elems_n != DIM(data)) + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + + for (i = 0; i < DIM(data); i++) + { + sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); + if (!sublist) + { + err = gpg_error (GPG_ERR_INV_SEXP); + break; + } + + data[i] = gcry_sexp_nth_buffer (sublist, 1, &data_n[i]); + if (!data[i]) + { + err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ + break; + } + totallen += data_n[i]; + gcry_sexp_release (sublist); + sublist = NULL; + } + if (err) + goto out; + + err = stream_write_uint32 (stream, totallen); + if (err) + goto out; + + for (i = 0; i < DIM(data); i++) + { + err = stream_write_data (stream, data[i], data_n[i]); + if (err) + goto out; + } + + out: + for (i = 0; i < DIM(data); i++) + xfree (data[i]); + gcry_sexp_release (valuelist); + gcry_sexp_release (sublist); + return err; +} + + +/* + S-Expressions. + */ + + +/* This function constructs a new S-Expression for the key identified + by the KEY_SPEC, SECRET, CURVE_NAME, MPIS, and COMMENT, which is to + be stored at R_SEXP. Returns an error code. */ +static gpg_error_t +sexp_key_construct (gcry_sexp_t *r_sexp, + ssh_key_type_spec_t key_spec, int secret, + const char *curve_name, gcry_mpi_t *mpis, + const char *comment) +{ + gpg_error_t err; + gcry_sexp_t sexp_new = NULL; + void *formatbuf = NULL; + void **arg_list = NULL; + estream_t format = NULL; + char *algo_name = NULL; + + if ((key_spec.flags & SPEC_FLAG_IS_EdDSA)) + { + /* It is much easier and more readable to use a separate code + path for EdDSA. */ + if (!curve_name) + err = gpg_error (GPG_ERR_INV_CURVE); + else if (!mpis[0] || !gcry_mpi_get_flag (mpis[0], GCRYMPI_FLAG_OPAQUE)) + err = gpg_error (GPG_ERR_BAD_PUBKEY); + else if (secret + && (!mpis[1] + || !gcry_mpi_get_flag (mpis[1], GCRYMPI_FLAG_OPAQUE))) + err = gpg_error (GPG_ERR_BAD_SECKEY); + else if (secret) + err = gcry_sexp_build (&sexp_new, NULL, + "(private-key(ecc(curve %s)" + "(flags eddsa)(q %m)(d %m))" + "(comment%s))", + curve_name, + mpis[0], mpis[1], + comment? comment:""); + else + err = gcry_sexp_build (&sexp_new, NULL, + "(public-key(ecc(curve %s)" + "(flags eddsa)(q %m))" + "(comment%s))", + curve_name, + mpis[0], + comment? comment:""); + } + else + { + const char *key_identifier[] = { "public-key", "private-key" }; + int arg_idx; + const char *elems; + size_t elems_n; + unsigned int i, j; + + if (secret) + elems = key_spec.elems_sexp_order; + else + elems = key_spec.elems_key_public; + elems_n = strlen (elems); + + format = es_fopenmem (0, "a+b"); + if (!format) + { + err = gpg_error_from_syserror (); + goto out; + } + + /* Key identifier, algorithm identifier, mpis, comment, and a NULL + as a safeguard. */ + arg_list = xtrymalloc (sizeof (*arg_list) * (2 + 1 + elems_n + 1 + 1)); + if (!arg_list) + { + err = gpg_error_from_syserror (); + goto out; + } + arg_idx = 0; + + es_fputs ("(%s(%s", format); + arg_list[arg_idx++] = &key_identifier[secret]; + algo_name = xtrystrdup (gcry_pk_algo_name (key_spec.algo)); + if (!algo_name) + { + err = gpg_error_from_syserror (); + goto out; + } + strlwr (algo_name); + arg_list[arg_idx++] = &algo_name; + if (curve_name) + { + es_fputs ("(curve%s)", format); + arg_list[arg_idx++] = &curve_name; + } + + for (i = 0; i < elems_n; i++) + { + es_fprintf (format, "(%c%%m)", elems[i]); + if (secret) + { + for (j = 0; j < elems_n; j++) + if (key_spec.elems_key_secret[j] == elems[i]) + break; + } + else + j = i; + arg_list[arg_idx++] = &mpis[j]; + } + es_fputs (")(comment%s))", format); + arg_list[arg_idx++] = &comment; + arg_list[arg_idx] = NULL; + + es_putc (0, format); + if (es_ferror (format)) + { + err = gpg_error_from_syserror (); + goto out; + } + if (es_fclose_snatch (format, &formatbuf, NULL)) + { + err = gpg_error_from_syserror (); + goto out; + } + format = NULL; + + err = gcry_sexp_build_array (&sexp_new, NULL, formatbuf, arg_list); + } + + if (!err) + *r_sexp = sexp_new; + + out: + es_fclose (format); + xfree (arg_list); + xfree (formatbuf); + xfree (algo_name); + + return err; +} + + +/* This function extracts the key from the s-expression SEXP according + to KEY_SPEC and stores it in ssh format at (R_BLOB, R_BLOBLEN). If + WITH_SECRET is true, the secret key parts are also extracted if + possible. Returns 0 on success or an error code. Note that data + stored at R_BLOB must be freed using es_free! */ +static gpg_error_t +ssh_key_to_blob (gcry_sexp_t sexp, int with_secret, + ssh_key_type_spec_t key_spec, + void **r_blob, size_t *r_blob_size) +{ + gpg_error_t err = 0; + gcry_sexp_t value_list = NULL; + gcry_sexp_t value_pair = NULL; + estream_t stream = NULL; + void *blob = NULL; + size_t blob_size; + const char *elems, *p_elems; + const char *data; + size_t datalen; + + *r_blob = NULL; + *r_blob_size = 0; + + stream = es_fopenmem (0, "r+b"); + if (!stream) + { + err = gpg_error_from_syserror (); + goto out; + } + + /* Get the type of the key expression. */ + data = gcry_sexp_nth_data (sexp, 0, &datalen); + if (!data) + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + + if ((datalen == 10 && !strncmp (data, "public-key", 10)) + || (datalen == 21 && !strncmp (data, "protected-private-key", 21)) + || (datalen == 20 && !strncmp (data, "shadowed-private-key", 20))) + elems = key_spec.elems_key_public; + else if (datalen == 11 && !strncmp (data, "private-key", 11)) + elems = with_secret? key_spec.elems_key_secret : key_spec.elems_key_public; + else + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + + /* Get key value list. */ + value_list = gcry_sexp_cadr (sexp); + if (!value_list) + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + + /* Write the ssh algorithm identifier. */ + if ((key_spec.flags & SPEC_FLAG_IS_ECDSA)) + { + /* Map the curve name to the ssh name. */ + const char *name, *sshname, *canon_name; + + name = gcry_pk_get_curve (sexp, 0, NULL); + if (!name) + { + err = gpg_error (GPG_ERR_INV_CURVE); + goto out; + } + + sshname = ssh_identifier_from_curve_name (name, &canon_name); + if (!sshname) + { + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + goto out; + } + err = stream_write_cstring (stream, sshname); + if (err) + goto out; + err = stream_write_cstring (stream, canon_name); + if (err) + goto out; + } + else + { + /* Note: This is also used for EdDSA. */ + err = stream_write_cstring (stream, key_spec.ssh_identifier); + if (err) + goto out; + } + + /* Write the parameters. */ + for (p_elems = elems; *p_elems; p_elems++) + { + gcry_sexp_release (value_pair); + value_pair = gcry_sexp_find_token (value_list, p_elems, 1); + if (!value_pair) + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + if ((key_spec.flags & SPEC_FLAG_IS_EdDSA)) + { + + data = gcry_sexp_nth_data (value_pair, 1, &datalen); + if (!data) + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + if (*p_elems == 'q' && datalen) + { /* Remove the prefix 0x40. */ + data++; + datalen--; + } + err = stream_write_string (stream, data, datalen); + if (err) + goto out; + } + else + { + gcry_mpi_t mpi; + + /* Note that we need to use STD format; i.e. prepend a 0x00 + to indicate a positive number if the high bit is set. */ + mpi = gcry_sexp_nth_mpi (value_pair, 1, GCRYMPI_FMT_STD); + if (!mpi) + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + err = stream_write_mpi (stream, mpi); + gcry_mpi_release (mpi); + if (err) + goto out; + } + } + + if (es_fclose_snatch (stream, &blob, &blob_size)) + { + err = gpg_error_from_syserror (); + goto out; + } + stream = NULL; + + *r_blob = blob; + blob = NULL; + *r_blob_size = blob_size; + + out: + gcry_sexp_release (value_list); + gcry_sexp_release (value_pair); + es_fclose (stream); + es_free (blob); + + return err; +} + + +/* + + Key I/O. + +*/ + +/* Search for a key specification entry. If SSH_NAME is not NULL, + search for an entry whose "ssh_name" is equal to SSH_NAME; + otherwise, search for an entry whose algorithm is equal to ALGO. + Store found entry in SPEC on success, return error otherwise. */ +static gpg_error_t +ssh_key_type_lookup (const char *ssh_name, int algo, + ssh_key_type_spec_t *spec) +{ + gpg_error_t err; + unsigned int i; + + for (i = 0; i < DIM (ssh_key_types); i++) + if ((ssh_name && (! strcmp (ssh_name, ssh_key_types[i].ssh_identifier))) + || algo == ssh_key_types[i].algo) + break; + + if (i == DIM (ssh_key_types)) + err = gpg_error (GPG_ERR_NOT_FOUND); + else + { + *spec = ssh_key_types[i]; + err = 0; + } + + return err; +} + + +/* Receive a key from STREAM, according to the key specification given + as KEY_SPEC. Depending on SECRET, receive a secret or a public + key. If READ_COMMENT is true, receive a comment string as well. + Constructs a new S-Expression from received data and stores it in + KEY_NEW. Returns zero on success or an error code. */ +static gpg_error_t +ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, + int read_comment, ssh_key_type_spec_t *key_spec) +{ + gpg_error_t err; + char *key_type = NULL; + char *comment = NULL; + estream_t cert = NULL; + gcry_sexp_t key = NULL; + ssh_key_type_spec_t spec; + gcry_mpi_t *mpi_list = NULL; + const char *elems; + const char *curve_name = NULL; + + + err = stream_read_cstring (stream, &key_type); + if (err) + goto out; + + err = ssh_key_type_lookup (key_type, 0, &spec); + if (err) + goto out; + + if ((spec.flags & SPEC_FLAG_WITH_CERT)) + { + /* This is an OpenSSH certificate+private key. The certificate + is an SSH string and which we store in an estream object. */ + unsigned char *buffer; + u32 buflen; + char *cert_key_type; + + err = stream_read_string (stream, 0, &buffer, &buflen); + if (err) + goto out; + cert = es_fopenmem_init (0, "rb", buffer, buflen); + xfree (buffer); + if (!cert) + { + err = gpg_error_from_syserror (); + goto out; + } + + /* Check that the key type matches. */ + err = stream_read_cstring (cert, &cert_key_type); + if (err) + goto out; + if (strcmp (cert_key_type, key_type) ) + { + xfree (cert_key_type); + log_error ("key types in received ssh certificate do not match\n"); + err = gpg_error (GPG_ERR_INV_CERT_OBJ); + goto out; + } + xfree (cert_key_type); + + /* Skip the nonce. */ + err = stream_read_string (cert, 0, NULL, NULL); + if (err) + goto out; + } + + if ((spec.flags & SPEC_FLAG_IS_EdDSA)) + { + /* The format of an EdDSA key is: + * string key_type ("ssh-ed25519") + * string public_key + * string private_key + * + * Note that the private key is the concatenation of the private + * key with the public key. Thus there's are 64 bytes; however + * we only want the real 32 byte private key - Libgcrypt expects + * this. + */ + mpi_list = xtrycalloc (3, sizeof *mpi_list); + if (!mpi_list) + { + err = gpg_error_from_syserror (); + goto out; + } + + err = stream_read_blob (cert? cert : stream, 0, &mpi_list[0]); + if (err) + goto out; + if (secret) + { + u32 len = 0; + unsigned char *buffer; + + /* Read string length. */ + err = stream_read_uint32 (stream, &len); + if (err) + goto out; + if (len != 32 && len != 64) + { + err = gpg_error (GPG_ERR_BAD_SECKEY); + goto out; + } + buffer = xtrymalloc_secure (32); + if (!buffer) + { + err = gpg_error_from_syserror (); + goto out; + } + err = stream_read_data (stream, buffer, 32); + if (err) + { + xfree (buffer); + goto out; + } + mpi_list[1] = gcry_mpi_set_opaque (NULL, buffer, 8*32); + buffer = NULL; + if (len == 64) + { + err = stream_read_skip (stream, 32); + if (err) + goto out; + } + } + } + else if ((spec.flags & SPEC_FLAG_IS_ECDSA)) + { + /* The format of an ECDSA key is: + * string key_type ("ecdsa-sha2-nistp256" | + * "ecdsa-sha2-nistp384" | + * "ecdsa-sha2-nistp521" ) + * string ecdsa_curve_name + * string ecdsa_public_key + * mpint ecdsa_private + * + * Note that we use the mpint reader instead of the string + * reader for ecsa_public_key. For the certificate variante + * ecdsa_curve_name+ecdsa_public_key are replaced by the + * certificate. + */ + unsigned char *buffer; + + err = stream_read_string (cert? cert : stream, 0, &buffer, NULL); + if (err) + goto out; + /* Get the canonical name. Should be the same as the read + * string but we use this mapping to validate that name. */ + if (!ssh_identifier_from_curve_name (buffer, &curve_name)) + { + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + xfree (buffer); + goto out; + } + xfree (buffer); + + err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list); + if (err) + goto out; + } + else + { + err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list); + if (err) + goto out; + } + + if (read_comment) + { + err = stream_read_cstring (stream, &comment); + if (err) + goto out; + } + + if (secret) + elems = spec.elems_key_secret; + else + elems = spec.elems_key_public; + + if (spec.key_modifier) + { + err = (*spec.key_modifier) (elems, mpi_list); + if (err) + goto out; + } + + if ((spec.flags & SPEC_FLAG_IS_EdDSA)) + { + if (secret) + { + err = gcry_sexp_build (&key, NULL, + "(private-key(ecc(curve \"Ed25519\")" + "(flags eddsa)(q %m)(d %m))" + "(comment%s))", + mpi_list[0], mpi_list[1], + comment? comment:""); + } + else + { + err = gcry_sexp_build (&key, NULL, + "(public-key(ecc(curve \"Ed25519\")" + "(flags eddsa)(q %m))" + "(comment%s))", + mpi_list[0], + comment? comment:""); + } + } + else + { + err = sexp_key_construct (&key, spec, secret, curve_name, mpi_list, + comment? comment:""); + if (err) + goto out; + } + + if (key_spec) + *key_spec = spec; + *key_new = key; + + out: + es_fclose (cert); + mpint_list_free (mpi_list); + xfree (key_type); + xfree (comment); + + return err; +} + + +/* Write the public key from KEY to STREAM in SSH key format. If + OVERRIDE_COMMENT is not NULL, it will be used instead of the + comment stored in the key. */ +static gpg_error_t +ssh_send_key_public (estream_t stream, gcry_sexp_t key, + const char *override_comment) +{ + ssh_key_type_spec_t spec; + int algo; + char *comment = NULL; + void *blob = NULL; + size_t bloblen; + gpg_error_t err = 0; + + algo = get_pk_algo_from_key (key); + if (algo == 0) + goto out; + + err = ssh_key_type_lookup (NULL, algo, &spec); + if (err) + goto out; + + err = ssh_key_to_blob (key, 0, spec, &blob, &bloblen); + if (err) + goto out; + + err = stream_write_string (stream, blob, bloblen); + if (err) + goto out; + + if (override_comment) + err = stream_write_cstring (stream, override_comment); + else + { + err = ssh_key_extract_comment (key, &comment); + if (err) + err = stream_write_cstring (stream, "(none)"); + else + err = stream_write_cstring (stream, comment); + } + if (err) + goto out; + + out: + xfree (comment); + es_free (blob); + + return err; +} + + +/* Read a public key out of BLOB/BLOB_SIZE according to the key + specification given as KEY_SPEC, storing the new key in KEY_PUBLIC. + Returns zero on success or an error code. */ +static gpg_error_t +ssh_read_key_public_from_blob (unsigned char *blob, size_t blob_size, + gcry_sexp_t *key_public, + ssh_key_type_spec_t *key_spec) +{ + gpg_error_t err; + estream_t blob_stream; + + blob_stream = es_fopenmem (0, "r+b"); + if (!blob_stream) + { + err = gpg_error_from_syserror (); + goto out; + } + + err = stream_write_data (blob_stream, blob, blob_size); + if (err) + goto out; + + err = es_fseek (blob_stream, 0, SEEK_SET); + if (err) + goto out; + + err = ssh_receive_key (blob_stream, key_public, 0, 0, key_spec); + + out: + es_fclose (blob_stream); + return err; +} + + + +/* This function calculates the key grip for the key contained in the + S-Expression KEY and writes it to BUFFER, which must be large + enough to hold it. Returns usual error code. */ +static gpg_error_t +ssh_key_grip (gcry_sexp_t key, unsigned char *buffer) +{ + if (!gcry_pk_get_keygrip (key, buffer)) + { + gpg_error_t err = gcry_pk_testkey (key); + return err? err : gpg_error (GPG_ERR_INTERNAL); + } + + return 0; +} + + +static gpg_error_t +card_key_list (ctrl_t ctrl, char **r_serialno, strlist_t *result) +{ + gpg_error_t err; + + *r_serialno = NULL; + *result = NULL; + + err = agent_card_serialno (ctrl, r_serialno, 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 0; + } + + err = agent_card_cardlist (ctrl, result); + if (err) + { + xfree (*r_serialno); + *r_serialno = NULL; + } + return err; +} + +/* Check whether a smartcard is available and whether it has a usable + key. Store a copy of that key at R_PK and return 0. If no key is + available store NULL at R_PK and return an error code. If CARDSN + is not NULL, a string with the serial number of the card will be + a malloced and stored there. */ +static gpg_error_t +card_key_available (ctrl_t ctrl, gcry_sexp_t *r_pk, char **cardsn) +{ + gpg_error_t err; + char *authkeyid; + char *serialno = NULL; + unsigned char *pkbuf; + size_t pkbuflen; + gcry_sexp_t s_pk; + unsigned char grip[20]; + + *r_pk = NULL; + if (cardsn) + *cardsn = NULL; + + /* First see whether a card is available and whether the application + is supported. */ + err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid); + if ( gpg_err_code (err) == GPG_ERR_CARD_REMOVED ) + { + /* Ask for the serial number to reset the card. */ + err = agent_card_serialno (ctrl, &serialno, NULL); + if (err) + { + if (opt.verbose) + log_info (_("error getting serial number of card: %s\n"), + gpg_strerror (err)); + return err; + } + log_info (_("detected card with S/N: %s\n"), serialno); + err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid); + } + if (err) + { + log_error (_("no authentication key for ssh on card: %s\n"), + gpg_strerror (err)); + xfree (serialno); + return err; + } + + /* Get the S/N if we don't have it yet. Use the fast getattr method. */ + if (!serialno && (err = agent_card_getattr (ctrl, "SERIALNO", &serialno)) ) + { + log_error (_("error getting serial number of card: %s\n"), + gpg_strerror (err)); + xfree (authkeyid); + return err; + } + + /* Read the public key. */ + err = agent_card_readkey (ctrl, authkeyid, &pkbuf); + if (err) + { + if (opt.verbose) + log_info (_("no suitable card key found: %s\n"), gpg_strerror (err)); + xfree (serialno); + xfree (authkeyid); + return err; + } + + pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); + err = gcry_sexp_sscan (&s_pk, NULL, (char*)pkbuf, pkbuflen); + if (err) + { + log_error ("failed to build S-Exp from received card key: %s\n", + gpg_strerror (err)); + xfree (pkbuf); + xfree (serialno); + xfree (authkeyid); + return err; + } + + err = ssh_key_grip (s_pk, grip); + if (err) + { + log_debug ("error computing keygrip from received card key: %s\n", + gcry_strerror (err)); + xfree (pkbuf); + gcry_sexp_release (s_pk); + xfree (serialno); + xfree (authkeyid); + return err; + } + + if ( agent_key_available (grip) ) + { + /* (Shadow)-key is not available in our key storage. */ + err = agent_write_shadow_key (grip, serialno, authkeyid, pkbuf, 0); + if (err) + { + xfree (pkbuf); + gcry_sexp_release (s_pk); + xfree (serialno); + xfree (authkeyid); + return err; + } + } + + if (cardsn) + { + char *dispsn; + + /* If the card handler is able to return a short serialnumber, + use that one, else use the complete serialno. */ + if (!agent_card_getattr (ctrl, "$DISPSERIALNO", &dispsn)) + { + *cardsn = xtryasprintf ("cardno:%s", dispsn); + xfree (dispsn); + } + else + *cardsn = xtryasprintf ("cardno:%s", serialno); + if (!*cardsn) + { + err = gpg_error_from_syserror (); + xfree (pkbuf); + gcry_sexp_release (s_pk); + xfree (serialno); + xfree (authkeyid); + return err; + } + } + + xfree (pkbuf); + xfree (serialno); + xfree (authkeyid); + *r_pk = s_pk; + return 0; +} + + + + +/* + + Request handler. Each handler is provided with a CTRL context, a + REQUEST object and a RESPONSE object. The actual request is to be + read from REQUEST, the response needs to be written to RESPONSE. + +*/ + + +/* Handler for the "request_identities" command. */ +static gpg_error_t +ssh_handler_request_identities (ctrl_t ctrl, + estream_t request, estream_t response) +{ + u32 key_counter; + estream_t key_blobs; + gcry_sexp_t key_public; + gpg_error_t err; + int ret; + ssh_control_file_t cf = NULL; + gpg_error_t ret_err; + + (void)request; + + /* Prepare buffer stream. */ + + key_public = NULL; + key_counter = 0; + + key_blobs = es_fopenmem (0, "r+b"); + if (! key_blobs) + { + err = gpg_error_from_syserror (); + goto out; + } + + /* First check whether a key is currently available in the card + reader - this should be allowed even without being listed in + sshcontrol. */ + + if (!opt.disable_scdaemon) + { + char *serialno; + strlist_t card_list, sl; + + err = card_key_list (ctrl, &serialno, &card_list); + if (err) + { + if (opt.verbose) + log_info (_("error getting list of cards: %s\n"), + gpg_strerror (err)); + goto scd_out; + } + + for (sl = card_list; sl; sl = sl->next) + { + char *serialno0; + char *cardsn; + + err = agent_card_serialno (ctrl, &serialno0, sl->d); + if (err) + { + if (opt.verbose) + log_info (_("error getting serial number of card: %s\n"), + gpg_strerror (err)); + continue; + } + + xfree (serialno0); + if (card_key_available (ctrl, &key_public, &cardsn)) + continue; + + err = ssh_send_key_public (key_blobs, key_public, cardsn); + if (err && opt.verbose) + gcry_log_debugsxp ("pubkey", key_public); + gcry_sexp_release (key_public); + key_public = NULL; + xfree (cardsn); + if (err) + { + xfree (serialno); + free_strlist (card_list); + goto out; + } + + key_counter++; + } + + xfree (serialno); + free_strlist (card_list); + } + + scd_out: + /* Then look at all the registered and non-disabled keys. */ + err = open_control_file (&cf, 0); + if (err) + goto out; + + while (!read_control_file_item (cf)) + { + unsigned char grip[20]; + + if (!cf->item.valid) + continue; /* Should not happen. */ + if (cf->item.disabled) + continue; + assert (strlen (cf->item.hexgrip) == 40); + hex2bin (cf->item.hexgrip, grip, sizeof (grip)); + + err = agent_public_key_from_file (ctrl, grip, &key_public); + if (err) + { + log_error ("%s:%d: key '%s' skipped: %s\n", + cf->fname, cf->lnr, cf->item.hexgrip, + gpg_strerror (err)); + continue; + } + + err = ssh_send_key_public (key_blobs, key_public, NULL); + if (err) + goto out; + gcry_sexp_release (key_public); + key_public = NULL; + + key_counter++; + } + err = 0; + + ret = es_fseek (key_blobs, 0, SEEK_SET); + if (ret) + { + err = gpg_error_from_syserror (); + goto out; + } + + out: + /* Send response. */ + + gcry_sexp_release (key_public); + + if (!err) + { + ret_err = stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER); + if (!ret_err) + ret_err = stream_write_uint32 (response, key_counter); + if (!ret_err) + ret_err = stream_copy (response, key_blobs); + } + else + { + log_error ("ssh request identities failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); + ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); + } + + es_fclose (key_blobs); + close_control_file (cf); + + return ret_err; +} + + +/* This function hashes the data contained in DATA of size DATA_N + according to the message digest algorithm specified by MD_ALGORITHM + and writes the message digest to HASH, which needs to large enough + for the digest. */ +static gpg_error_t +data_hash (unsigned char *data, size_t data_n, + int md_algorithm, unsigned char *hash) +{ + gcry_md_hash_buffer (md_algorithm, hash, data, data_n); + + return 0; +} + + +/* This function signs the data described by CTRL. If HASH is not + NULL, (HASH,HASHLEN) overrides the hash stored in CTRL. This is to + allow the use of signature algorithms that implement the hashing + internally (e.g. Ed25519). On success the created signature is + stored in ssh format at R_SIG and it's size at R_SIGLEN; the caller + must use es_free to releaase this memory. */ +static gpg_error_t +data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec, + const void *hash, size_t hashlen, + unsigned char **r_sig, size_t *r_siglen) +{ + gpg_error_t err; + gcry_sexp_t signature_sexp = NULL; + estream_t stream = NULL; + void *blob = NULL; + size_t bloblen; + char hexgrip[40+1]; + + *r_sig = NULL; + *r_siglen = 0; + + /* Quick check to see whether we have a valid keygrip and convert it + to hex. */ + if (!ctrl->have_keygrip) + { + err = gpg_error (GPG_ERR_NO_SECKEY); + goto out; + } + bin2hex (ctrl->keygrip, 20, hexgrip); + + /* Ask for confirmation if needed. */ + if (confirm_flag_from_sshcontrol (hexgrip)) + { + gcry_sexp_t key; + char *fpr, *prompt; + char *comment = NULL; + + err = agent_raw_key_from_file (ctrl, ctrl->keygrip, &key); + if (err) + goto out; + err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &fpr); + if (!err) + { + gcry_sexp_t tmpsxp = gcry_sexp_find_token (key, "comment", 0); + if (tmpsxp) + comment = gcry_sexp_nth_string (tmpsxp, 1); + gcry_sexp_release (tmpsxp); + } + gcry_sexp_release (key); + if (err) + goto out; + prompt = xtryasprintf (L_("An ssh process requested the use of key%%0A" + " %s%%0A" + " (%s)%%0A" + "Do you want to allow this?"), + fpr, comment? comment:""); + xfree (fpr); + gcry_free (comment); + err = agent_get_confirmation (ctrl, prompt, L_("Allow"), L_("Deny"), 0); + xfree (prompt); + if (err) + goto out; + } + + /* Create signature. */ + ctrl->use_auth_call = 1; + err = agent_pksign_do (ctrl, NULL, + L_("Please enter the passphrase " + "for the ssh key%%0A %F%%0A (%c)"), + &signature_sexp, + CACHE_MODE_SSH, ttl_from_sshcontrol, + hash, hashlen); + ctrl->use_auth_call = 0; + if (err) + goto out; + + stream = es_fopenmem (0, "r+b"); + if (!stream) + { + err = gpg_error_from_syserror (); + goto out; + } + + err = stream_write_cstring (stream, spec->ssh_identifier); + if (err) + goto out; + + err = spec->signature_encoder (spec, stream, signature_sexp); + if (err) + goto out; + + err = es_fclose_snatch (stream, &blob, &bloblen); + if (err) + goto out; + stream = NULL; + + *r_sig = blob; blob = NULL; + *r_siglen = bloblen; + + out: + xfree (blob); + es_fclose (stream); + gcry_sexp_release (signature_sexp); + + return err; +} + + +/* Handler for the "sign_request" command. */ +static gpg_error_t +ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response) +{ + gcry_sexp_t key = NULL; + ssh_key_type_spec_t spec; + unsigned char hash[MAX_DIGEST_LEN]; + unsigned int hash_n; + unsigned char key_grip[20]; + unsigned char *key_blob = NULL; + u32 key_blob_size; + unsigned char *data = NULL; + unsigned char *sig = NULL; + size_t sig_n; + u32 data_size; + gpg_error_t err; + gpg_error_t ret_err; + int hash_algo; + + /* Receive key. */ + + err = stream_read_string (request, 0, &key_blob, &key_blob_size); + if (err) + goto out; + + err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, &spec); + if (err) + goto out; + + /* Receive data to sign. */ + err = stream_read_string (request, 0, &data, &data_size); + if (err) + goto out; + + /* Flag processing. */ + { + u32 flags; + + err = stream_read_uint32 (request, &flags); + if (err) + goto out; + + if (spec.algo == GCRY_PK_RSA) + { + if ((flags & SSH_AGENT_RSA_SHA2_512)) + { + flags &= ~SSH_AGENT_RSA_SHA2_512; + spec.ssh_identifier = "rsa-sha2-512"; + spec.hash_algo = GCRY_MD_SHA512; + } + if ((flags & SSH_AGENT_RSA_SHA2_256)) + { + /* Note: We prefer SHA256 over SHA512. */ + flags &= ~SSH_AGENT_RSA_SHA2_256; + spec.ssh_identifier = "rsa-sha2-256"; + spec.hash_algo = GCRY_MD_SHA256; + } + } + + /* Some flag is present that we do not know about. Note that + * processed or known flags have been cleared at this point. */ + if (flags) + { + err = gpg_error (GPG_ERR_UNKNOWN_OPTION); + goto out; + } + } + + hash_algo = spec.hash_algo; + if (!hash_algo) + hash_algo = GCRY_MD_SHA1; /* Use the default. */ + ctrl->digest.algo = hash_algo; + if ((spec.flags & SPEC_FLAG_USE_PKCS1V2)) + ctrl->digest.raw_value = 0; + else + ctrl->digest.raw_value = 1; + + /* Calculate key grip. */ + err = ssh_key_grip (key, key_grip); + if (err) + goto out; + ctrl->have_keygrip = 1; + memcpy (ctrl->keygrip, key_grip, 20); + + /* Hash data unless we use EdDSA. */ + if ((spec.flags & SPEC_FLAG_IS_EdDSA)) + { + ctrl->digest.valuelen = 0; + } + else + { + hash_n = gcry_md_get_algo_dlen (hash_algo); + if (!hash_n) + { + err = gpg_error (GPG_ERR_INTERNAL); + goto out; + } + err = data_hash (data, data_size, hash_algo, hash); + if (err) + goto out; + memcpy (ctrl->digest.value, hash, hash_n); + ctrl->digest.valuelen = hash_n; + } + + /* Sign data. */ + if ((spec.flags & SPEC_FLAG_IS_EdDSA)) + err = data_sign (ctrl, &spec, data, data_size, &sig, &sig_n); + else + err = data_sign (ctrl, &spec, NULL, 0, &sig, &sig_n); + + out: + /* Done. */ + if (!err) + { + ret_err = stream_write_byte (response, SSH_RESPONSE_SIGN_RESPONSE); + if (ret_err) + goto leave; + ret_err = stream_write_string (response, sig, sig_n); + if (ret_err) + goto leave; + } + else + { + log_error ("ssh sign request failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); + ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); + if (ret_err) + goto leave; + } + + leave: + + gcry_sexp_release (key); + xfree (key_blob); + xfree (data); + es_free (sig); + + return ret_err; +} + + +/* This function extracts the comment contained in the key + s-expression KEY and stores a copy in COMMENT. Returns usual error + code. */ +static gpg_error_t +ssh_key_extract_comment (gcry_sexp_t key, char **r_comment) +{ + gcry_sexp_t comment_list; + + *r_comment = NULL; + + comment_list = gcry_sexp_find_token (key, "comment", 0); + if (!comment_list) + return gpg_error (GPG_ERR_INV_SEXP); + + *r_comment = gcry_sexp_nth_string (comment_list, 1); + gcry_sexp_release (comment_list); + if (!*r_comment) + return gpg_error (GPG_ERR_INV_SEXP); + + return 0; +} + + +/* This function converts the key contained in the S-Expression KEY + into a buffer, which is protected by the passphrase PASSPHRASE. + If PASSPHRASE is the empty passphrase, the key is not protected. + Returns usual error code. */ +static gpg_error_t +ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase, + unsigned char **buffer, size_t *buffer_n) +{ + unsigned char *buffer_new; + unsigned int buffer_new_n; + gpg_error_t err; + + buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, NULL, 0); + buffer_new = xtrymalloc_secure (buffer_new_n); + if (! buffer_new) + { + err = gpg_error_from_syserror (); + goto out; + } + + buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, + buffer_new, buffer_new_n); + + if (*passphrase) + err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0, -1); + else + { + /* The key derivation function does not support zero length + * strings. Store key unprotected if the user wishes so. */ + *buffer = buffer_new; + *buffer_n = buffer_new_n; + buffer_new = NULL; + err = 0; + } + + out: + + xfree (buffer_new); + + return err; +} + + + +/* Callback function to compare the first entered PIN with the one + currently being entered. */ +static gpg_error_t +reenter_compare_cb (struct pin_entry_info_s *pi) +{ + const char *pin1 = pi->check_cb_arg; + + if (!strcmp (pin1, pi->pin)) + return 0; /* okay */ + return gpg_error (GPG_ERR_BAD_PASSPHRASE); +} + + +/* Store the ssh KEY into our local key storage and protect it after + asking for a passphrase. Cache that passphrase. TTL is the + maximum caching time for that key. If the key already exists in + our key storage, don't do anything. When entering a key also add + an entry to the sshcontrol file. */ +static gpg_error_t +ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec, + gcry_sexp_t key, int ttl, int confirm) +{ + gpg_error_t err; + unsigned char key_grip_raw[20]; + char key_grip[41]; + unsigned char *buffer = NULL; + size_t buffer_n; + char *description = NULL; + const char *description2 = L_("Please re-enter this passphrase"); + char *comment = NULL; + char *key_fpr = NULL; + const char *initial_errtext = NULL; + struct pin_entry_info_s *pi = NULL; + struct pin_entry_info_s *pi2 = NULL; + + err = ssh_key_grip (key, key_grip_raw); + if (err) + goto out; + + bin2hex (key_grip_raw, 20, key_grip); + + err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &key_fpr); + if (err) + goto out; + + /* Check whether the key is already in our key storage. Don't do + anything then besides (re-)adding it to sshcontrol. */ + if ( !agent_key_available (key_grip_raw) ) + goto key_exists; /* Yes, key is available. */ + + err = ssh_key_extract_comment (key, &comment); + if (err) + goto out; + + if ( asprintf (&description, + L_("Please enter a passphrase to protect" + " the received secret key%%0A" + " %s%%0A" + " %s%%0A" + "within gpg-agent's key storage"), + key_fpr, comment ? comment : "") < 0) + { + err = gpg_error_from_syserror (); + goto out; + } + + pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1); + if (!pi) + { + err = gpg_error_from_syserror (); + goto out; + } + pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1); + if (!pi2) + { + err = gpg_error_from_syserror (); + goto out; + } + pi->max_length = MAX_PASSPHRASE_LEN + 1; + pi->max_tries = 1; + pi->with_repeat = 1; + pi2->max_length = MAX_PASSPHRASE_LEN + 1; + pi2->max_tries = 1; + pi2->check_cb = reenter_compare_cb; + pi2->check_cb_arg = pi->pin; + + next_try: + err = agent_askpin (ctrl, description, NULL, initial_errtext, pi, NULL, 0); + initial_errtext = NULL; + if (err) + goto out; + + /* Unless the passphrase is empty or the pinentry told us that + it already did the repetition check, ask to confirm it. */ + if (*pi->pin && !pi->repeat_okay) + { + err = agent_askpin (ctrl, description2, NULL, NULL, pi2, NULL, 0); + if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE) + { /* The re-entered one did not match and the user did not + hit cancel. */ + initial_errtext = L_("does not match - try again"); + goto next_try; + } + } + + err = ssh_key_to_protected_buffer (key, pi->pin, &buffer, &buffer_n); + if (err) + goto out; + + /* Store this key to our key storage. We do not store a creation + * timestamp because we simply do not know. */ + err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0, 0); + if (err) + goto out; + + /* Cache this passphrase. */ + err = agent_put_cache (ctrl, key_grip, CACHE_MODE_SSH, pi->pin, ttl); + if (err) + goto out; + + key_exists: + /* And add an entry to the sshcontrol file. */ + err = add_control_entry (ctrl, spec, key_grip, key, ttl, confirm); + + + out: + if (pi2 && pi2->max_length) + wipememory (pi2->pin, pi2->max_length); + xfree (pi2); + if (pi && pi->max_length) + wipememory (pi->pin, pi->max_length); + xfree (pi); + xfree (buffer); + xfree (comment); + xfree (key_fpr); + xfree (description); + + return err; +} + + +/* This function removes the key contained in the S-Expression KEY + from the local key storage, in case it exists there. Returns usual + error code. FIXME: this function is a stub. */ +static gpg_error_t +ssh_identity_drop (gcry_sexp_t key) +{ + unsigned char key_grip[21] = { 0 }; + gpg_error_t err; + + err = ssh_key_grip (key, key_grip); + if (err) + goto out; + + key_grip[sizeof (key_grip) - 1] = 0; + + /* FIXME: What to do here - forgetting the passphrase or deleting + the key from key cache? */ + + out: + + return err; +} + +/* Handler for the "add_identity" command. */ +static gpg_error_t +ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response) +{ + gpg_error_t ret_err; + ssh_key_type_spec_t spec; + gpg_error_t err; + gcry_sexp_t key; + unsigned char b; + int confirm; + int ttl; + + confirm = 0; + key = NULL; + ttl = 0; + + /* FIXME? */ + err = ssh_receive_key (request, &key, 1, 1, &spec); + if (err) + goto out; + + while (1) + { + err = stream_read_byte (request, &b); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_EOF) + err = 0; + break; + } + + switch (b) + { + case SSH_OPT_CONSTRAIN_LIFETIME: + { + u32 n = 0; + + err = stream_read_uint32 (request, &n); + if (! err) + ttl = n; + break; + } + + case SSH_OPT_CONSTRAIN_CONFIRM: + { + confirm = 1; + break; + } + + default: + /* FIXME: log/bad? */ + break; + } + } + if (err) + goto out; + + err = ssh_identity_register (ctrl, &spec, key, ttl, confirm); + + out: + + gcry_sexp_release (key); + + if (! err) + ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); + else + ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); + + return ret_err; +} + +/* Handler for the "remove_identity" command. */ +static gpg_error_t +ssh_handler_remove_identity (ctrl_t ctrl, + estream_t request, estream_t response) +{ + unsigned char *key_blob; + u32 key_blob_size; + gcry_sexp_t key; + gpg_error_t ret_err; + gpg_error_t err; + + (void)ctrl; + + /* Receive key. */ + + key_blob = NULL; + key = NULL; + + err = stream_read_string (request, 0, &key_blob, &key_blob_size); + if (err) + goto out; + + err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, NULL); + if (err) + goto out; + + err = ssh_identity_drop (key); + + out: + + xfree (key_blob); + gcry_sexp_release (key); + + if (! err) + ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); + else + ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); + + return ret_err; +} + +/* FIXME: stub function. Actually useful? */ +static gpg_error_t +ssh_identities_remove_all (void) +{ + gpg_error_t err; + + err = 0; + + /* FIXME: shall we remove _all_ cache entries or only those + registered through the ssh-agent protocol? */ + + return err; +} + +/* Handler for the "remove_all_identities" command. */ +static gpg_error_t +ssh_handler_remove_all_identities (ctrl_t ctrl, + estream_t request, estream_t response) +{ + gpg_error_t ret_err; + gpg_error_t err; + + (void)ctrl; + (void)request; + + err = ssh_identities_remove_all (); + + if (! err) + ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); + else + ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); + + return ret_err; +} + +/* Lock agent? FIXME: stub function. */ +static gpg_error_t +ssh_lock (void) +{ + gpg_error_t err; + + /* FIXME */ + log_error ("ssh-agent's lock command is not implemented\n"); + err = 0; + + return err; +} + +/* Unock agent? FIXME: stub function. */ +static gpg_error_t +ssh_unlock (void) +{ + gpg_error_t err; + + log_error ("ssh-agent's unlock command is not implemented\n"); + err = 0; + + return err; +} + +/* Handler for the "lock" command. */ +static gpg_error_t +ssh_handler_lock (ctrl_t ctrl, estream_t request, estream_t response) +{ + gpg_error_t ret_err; + gpg_error_t err; + + (void)ctrl; + (void)request; + + err = ssh_lock (); + + if (! err) + ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); + else + ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); + + return ret_err; +} + +/* Handler for the "unlock" command. */ +static gpg_error_t +ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response) +{ + gpg_error_t ret_err; + gpg_error_t err; + + (void)ctrl; + (void)request; + + err = ssh_unlock (); + + if (! err) + ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); + else + ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); + + return ret_err; +} + + + +/* Return the request specification for the request identified by TYPE + or NULL in case the requested request specification could not be + found. */ +static const ssh_request_spec_t * +request_spec_lookup (int type) +{ + const ssh_request_spec_t *spec; + unsigned int i; + + for (i = 0; i < DIM (request_specs); i++) + if (request_specs[i].type == type) + break; + if (i == DIM (request_specs)) + { + if (opt.verbose) + log_info ("ssh request %u is not supported\n", type); + spec = NULL; + } + else + spec = request_specs + i; + + return spec; +} + +/* Process a single request. The request is read from and the + response is written to STREAM_SOCK. Uses CTRL as context. Returns + zero in case of success, non zero in case of failure. */ +static int +ssh_request_process (ctrl_t ctrl, estream_t stream_sock) +{ + const ssh_request_spec_t *spec; + estream_t response = NULL; + estream_t request = NULL; + unsigned char request_type; + gpg_error_t err; + int send_err = 0; + int ret; + unsigned char *request_data = NULL; + u32 request_data_size; + u32 response_size; + + /* Create memory streams for request/response data. The entire + request will be stored in secure memory, since it might contain + secret key material. The response does not have to be stored in + secure memory, since we never give out secret keys. + + Note: we only have little secure memory, but there is NO + possibility of DoS here; only trusted clients are allowed to + connect to the agent. What could happen is that the agent + returns out-of-secure-memory errors on requests in case the + agent's owner floods his own agent with many large messages. + -moritz */ + + /* Retrieve request. */ + err = stream_read_string (stream_sock, 1, &request_data, &request_data_size); + if (err) + goto out; + + if (opt.verbose > 1) + log_info ("received ssh request of length %u\n", + (unsigned int)request_data_size); + + if (! request_data_size) + { + send_err = 1; + goto out; + /* Broken request; FIXME. */ + } + + request_type = request_data[0]; + spec = request_spec_lookup (request_type); + if (! spec) + { + send_err = 1; + goto out; + /* Unknown request; FIXME. */ + } + + if (spec->secret_input) + request = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+b"); + else + request = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+b"); + if (! request) + { + err = gpg_error_from_syserror (); + goto out; + } + ret = es_setvbuf (request, NULL, _IONBF, 0); + if (ret) + { + err = gpg_error_from_syserror (); + goto out; + } + err = stream_write_data (request, request_data + 1, request_data_size - 1); + if (err) + goto out; + es_rewind (request); + + response = es_fopenmem (0, "r+b"); + if (! response) + { + err = gpg_error_from_syserror (); + goto out; + } + + if (opt.verbose) + log_info ("ssh request handler for %s (%u) started\n", + spec->identifier, spec->type); + + err = (*spec->handler) (ctrl, request, response); + + if (opt.verbose) + { + if (err) + log_info ("ssh request handler for %s (%u) failed: %s\n", + spec->identifier, spec->type, gpg_strerror (err)); + else + log_info ("ssh request handler for %s (%u) ready\n", + spec->identifier, spec->type); + } + + if (err) + { + send_err = 1; + goto out; + } + + response_size = es_ftell (response); + if (opt.verbose > 1) + log_info ("sending ssh response of length %u\n", + (unsigned int)response_size); + + err = es_fseek (response, 0, SEEK_SET); + if (err) + { + send_err = 1; + goto out; + } + + err = stream_write_uint32 (stream_sock, response_size); + if (err) + { + send_err = 1; + goto out; + } + + err = stream_copy (stream_sock, response); + if (err) + goto out; + + err = es_fflush (stream_sock); + if (err) + goto out; + + out: + + if (err && es_feof (stream_sock)) + log_error ("error occurred while processing request: %s\n", + gpg_strerror (err)); + + if (send_err) + { + if (opt.verbose > 1) + log_info ("sending ssh error response\n"); + err = stream_write_uint32 (stream_sock, 1); + if (err) + goto leave; + err = stream_write_byte (stream_sock, SSH_RESPONSE_FAILURE); + if (err) + goto leave; + } + + leave: + + es_fclose (request); + es_fclose (response); + xfree (request_data); + + return !!err; +} + + +/* Return the peer's pid. */ +static unsigned long +get_client_pid (int fd) +{ + pid_t client_pid = (pid_t)0; + +#ifdef SO_PEERCRED + { +#ifdef HAVE_STRUCT_SOCKPEERCRED_PID + struct sockpeercred cr; +#else + struct ucred cr; +#endif + socklen_t cl = sizeof cr; + + if (!getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)) + { +#if defined (HAVE_STRUCT_SOCKPEERCRED_PID) || defined (HAVE_STRUCT_UCRED_PID) + client_pid = cr.pid; +#elif defined (HAVE_STRUCT_UCRED_CR_PID) + client_pid = cr.cr_pid; +#else +#error "Unknown SO_PEERCRED struct" +#endif + } + } +#elif defined (LOCAL_PEERPID) + { + socklen_t len = sizeof (pid_t); + + getsockopt (fd, SOL_LOCAL, LOCAL_PEERPID, &client_pid, &len); + } +#elif defined (LOCAL_PEEREID) + { + struct unpcbid unp; + socklen_t unpl = sizeof unp; + + if (getsockopt (fd, 0, LOCAL_PEEREID, &unp, &unpl) != -1) + client_pid = unp.unp_pid; + } +#elif defined (HAVE_GETPEERUCRED) + { + ucred_t *ucred = NULL; + + if (getpeerucred (fd, &ucred) != -1) + { + client_pid= ucred_getpid (ucred); + ucred_free (ucred); + } + } +#else + (void)fd; +#endif + + return (unsigned long)client_pid; +} + + +/* Start serving client on SOCK_CLIENT. */ +void +start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client) +{ + estream_t stream_sock = NULL; + gpg_error_t err; + int ret; + + err = agent_copy_startup_env (ctrl); + if (err) + goto out; + + ctrl->client_pid = get_client_pid (FD2INT(sock_client)); + + /* Create stream from socket. */ + stream_sock = es_fdopen (FD2INT(sock_client), "r+"); + if (!stream_sock) + { + err = gpg_error_from_syserror (); + log_error (_("failed to create stream from socket: %s\n"), + gpg_strerror (err)); + goto out; + } + /* We have to disable the estream buffering, because the estream + core doesn't know about secure memory. */ + ret = es_setvbuf (stream_sock, NULL, _IONBF, 0); + if (ret) + { + err = gpg_error_from_syserror (); + log_error ("failed to disable buffering " + "on socket stream: %s\n", gpg_strerror (err)); + goto out; + } + + /* Main processing loop. */ + while ( !ssh_request_process (ctrl, stream_sock) ) + { + /* Check whether we have reached EOF before trying to read + another request. */ + int c; + + c = es_fgetc (stream_sock); + if (c == EOF) + break; + es_ungetc (c, stream_sock); + } + + /* Reset the SCD in case it has been used. */ + agent_reset_scd (ctrl); + + + out: + if (stream_sock) + es_fclose (stream_sock); +} + + +#ifdef HAVE_W32_SYSTEM +/* Serve one ssh-agent request. This is used for the Putty support. + REQUEST is the mmapped memory which may be accessed up to a + length of MAXREQLEN. Returns 0 on success which also indicates + that a valid SSH response message is now in REQUEST. */ +int +serve_mmapped_ssh_request (ctrl_t ctrl, + unsigned char *request, size_t maxreqlen) +{ + gpg_error_t err; + int send_err = 0; + int valid_response = 0; + const ssh_request_spec_t *spec; + u32 msglen; + estream_t request_stream, response_stream; + + if (agent_copy_startup_env (ctrl)) + goto leave; /* Error setting up the environment. */ + + if (maxreqlen < 5) + goto leave; /* Caller error. */ + + msglen = uint32_construct (request[0], request[1], request[2], request[3]); + if (msglen < 1 || msglen > maxreqlen - 4) + { + log_error ("ssh message len (%u) out of range", (unsigned int)msglen); + goto leave; + } + + spec = request_spec_lookup (request[4]); + if (!spec) + { + send_err = 1; /* Unknown request type. */ + goto leave; + } + + /* Create a stream object with the data part of the request. */ + if (spec->secret_input) + request_stream = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+"); + else + request_stream = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+"); + if (!request_stream) + { + err = gpg_error_from_syserror (); + goto leave; + } + /* We have to disable the estream buffering, because the estream + core doesn't know about secure memory. */ + if (es_setvbuf (request_stream, NULL, _IONBF, 0)) + { + err = gpg_error_from_syserror (); + goto leave; + } + /* Copy the request to the stream but omit the request type. */ + err = stream_write_data (request_stream, request + 5, msglen - 1); + if (err) + goto leave; + es_rewind (request_stream); + + response_stream = es_fopenmem (0, "r+b"); + if (!response_stream) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (opt.verbose) + log_info ("ssh request handler for %s (%u) started\n", + spec->identifier, spec->type); + + err = (*spec->handler) (ctrl, request_stream, response_stream); + + if (opt.verbose) + { + if (err) + log_info ("ssh request handler for %s (%u) failed: %s\n", + spec->identifier, spec->type, gpg_strerror (err)); + else + log_info ("ssh request handler for %s (%u) ready\n", + spec->identifier, spec->type); + } + + es_fclose (request_stream); + request_stream = NULL; + + if (err) + { + send_err = 1; + goto leave; + } + + /* Put the response back into the mmapped buffer. */ + { + void *response_data; + size_t response_size; + + /* NB: In contrast to the request-stream, the response stream + includes the message type byte. */ + if (es_fclose_snatch (response_stream, &response_data, &response_size)) + { + log_error ("snatching ssh response failed: %s", + gpg_strerror (gpg_error_from_syserror ())); + send_err = 1; /* Ooops. */ + goto leave; + } + + if (opt.verbose > 1) + log_info ("sending ssh response of length %u\n", + (unsigned int)response_size); + if (response_size > maxreqlen - 4) + { + log_error ("invalid length of the ssh response: %s", + gpg_strerror (GPG_ERR_INTERNAL)); + es_free (response_data); + send_err = 1; + goto leave; + } + + request[0] = response_size >> 24; + request[1] = response_size >> 16; + request[2] = response_size >> 8; + request[3] = response_size >> 0; + memcpy (request+4, response_data, response_size); + es_free (response_data); + valid_response = 1; + } + + leave: + if (send_err) + { + request[0] = 0; + request[1] = 0; + request[2] = 0; + request[3] = 1; + request[4] = SSH_RESPONSE_FAILURE; + valid_response = 1; + } + + /* Reset the SCD in case it has been used. */ + agent_reset_scd (ctrl); + + return valid_response? 0 : -1; +} +#endif /*HAVE_W32_SYSTEM*/ |