diff options
Diffstat (limited to 'agent/command-ssh.c')
-rw-r--r-- | agent/command-ssh.c | 1250 |
1 files changed, 875 insertions, 375 deletions
diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 2f96ef5..3583ea0 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -1,5 +1,6 @@ /* command-ssh.c - gpg-agent's ssh-agent emulation layer - * Copyright (C) 2004, 2005, 2006, 2009 Free Software Foundation, Inc. + * Copyright (C) 2004, 2005, 2006, 2009, 2012 Free Software Foundation, Inc. + * Copyright (C) 2013 Werner Koch * * This file is part of GnuPG. * @@ -17,7 +18,18 @@ * along with this program; if not, see <http://www.gnu.org/licenses/>. */ -/* Only v2 of the ssh-agent protocol is implemented. */ +/* Only v2 of the ssh-agent protocol is implemented. Relevant RFCs + are: + + RFC-4250 - Protocol Assigned Numbers + RFC-4251 - Protocol Architecture + RFC-4252 - Authentication Protocol + RFC-4253 - Transport Layer Protocol + RFC-5656 - ECC support + + The protocol for the agent is defined in OpenSSH's PROTOCL.agent + file. + */ #include <config.h> @@ -27,7 +39,6 @@ #include <errno.h> #include <sys/types.h> #include <sys/stat.h> -#include <dirent.h> #include <assert.h> #include "agent.h" @@ -63,7 +74,10 @@ #define SSH_DSA_SIGNATURE_PADDING 20 #define SSH_DSA_SIGNATURE_ELEMS 2 #define SPEC_FLAG_USE_PKCS1V2 (1 << 0) +#define SPEC_FLAG_IS_ECDSA (1 << 1) +/* 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[] = @@ -75,12 +89,11 @@ static const char sshcontrolblurb[] = "# 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" +"# 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 @@ -100,6 +113,10 @@ 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 @@ -120,12 +137,13 @@ typedef gpg_error_t (*ssh_key_modifier_t) (const char *elems, /* 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) (estream_t signature_blob, +typedef gpg_error_t (*ssh_signature_encoder_t) (ssh_key_type_spec_t *spec, + estream_t signature_blob, gcry_mpi_t *mpis); /* Type, which is used for boundling all the algorithm specific information together in a single object. */ -typedef struct ssh_key_type_spec +struct ssh_key_type_spec { /* Algorithm identifier as used by OpenSSH. */ const char *ssh_identifier; @@ -158,9 +176,32 @@ typedef struct ssh_key_type_spec algorithm. */ ssh_signature_encoder_t signature_encoder; + /* The name of the ECC curve or NULL. */ + const char *curve_name; + + /* The hash algorithm to be used with this key. 0 for using the + default. */ + int hash_algo; + /* Misc flags. */ unsigned int flags; -} ssh_key_type_spec_t; +}; + + +/* Definition of an object to access the sshcontrol file. */ +struct ssh_control_file_s +{ + char *fname; /* Name of the file. */ + FILE *fp; /* This is never NULL. */ + int lnr; /* The current line number. */ + struct { + int valid; /* True if the data of this structure is valid. */ + int disabled; /* The item is disabled. */ + int ttl; /* The TTL of the item. */ + int confirm; /* The confirm flag is set. */ + char hexgrip[40+1]; /* The hexgrip of the item (uppercase). */ + } item; +}; /* Prototypes. */ @@ -187,10 +228,15 @@ static gpg_error_t ssh_handler_unlock (ctrl_t ctrl, 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 (estream_t signature_blob, +static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, gcry_mpi_t *mpis); -static gpg_error_t ssh_signature_encoder_dsa (estream_t signature_blob, +static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, gcry_mpi_t *mpis); +static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, + gcry_mpi_t *mpis); @@ -223,13 +269,29 @@ static ssh_key_type_spec_t ssh_key_types[] = { "ssh-rsa", "rsa", "nedupq", "en", "s", "nedpqu", ssh_key_modifier_rsa, ssh_signature_encoder_rsa, - SPEC_FLAG_USE_PKCS1V2 + NULL, 0, SPEC_FLAG_USE_PKCS1V2 }, { "ssh-dss", "dsa", "pqgyx", "pqgy", "rs", "pqgyx", NULL, ssh_signature_encoder_dsa, - 0 + NULL, 0, 0 }, + { + "ecdsa-sha2-nistp256", "ecdsa", "qd", "q", "rs", "qd", + NULL, ssh_signature_encoder_ecdsa, + "nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA + }, + { + "ecdsa-sha2-nistp384", "ecdsa", "qd", "q", "rs", "qd", + NULL, ssh_signature_encoder_ecdsa, + "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA + }, + { + "ecdsa-sha2-nistp521", "ecdsa", "qd", "q", "rs", "qd", + NULL, ssh_signature_encoder_ecdsa, + "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA + } + }; @@ -324,6 +386,7 @@ stream_write_byte (estream_t stream, unsigned char b) return err; } + /* Read a uint32 from STREAM, store it in UINT32. */ static gpg_error_t stream_read_uint32 (estream_t stream, u32 *uint32) @@ -414,8 +477,9 @@ stream_write_data (estream_t stream, const unsigned char *buffer, size_t size) } /* Read a binary string from STREAM into STRING, store size of string - in STRING_SIZE; depending on SECURE use secure memory for - string. */ + in STRING_SIZE. Append a hidden nul so that the result may + directly be used as a C string. Depending on SECURE use secure + memory for STRING. */ static gpg_error_t stream_read_string (estream_t stream, unsigned int secure, unsigned char **string, u32 *string_size) @@ -618,7 +682,7 @@ file_to_buffer (const char *filename, unsigned char **buffer, size_t *buffer_n) buffer_new = NULL; err = 0; - stream = es_fopen (filename, "r"); + stream = es_fopen (filename, "rb"); if (! stream) { err = gpg_error_from_syserror (); @@ -660,94 +724,123 @@ 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 +/* 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. */ + 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 (FILE **r_fp, int append) +open_control_file (ssh_control_file_t *r_cf, int append) { gpg_error_t err; - char *fname; - FILE *fp; + 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. */ - *r_fp = NULL; - fname = make_filename (opt.homedir, "sshcontrol", NULL); + cf->fname = make_filename_try (opt.homedir, SSH_CONTROL_FILE_NAME, NULL); + if (!cf->fname) + { + err = gpg_error_from_syserror (); + goto leave; + } /* FIXME: With "a+" we are not able to check whether this will will be created and thus the blurb needs to be written first. */ - fp = fopen (fname, append? "a+":"r"); - if (!fp && errno == ENOENT) + cf->fp = fopen (cf->fname, append? "a+":"r"); + if (!cf->fp && errno == ENOENT) { - /* Fixme: "x" is a GNU extension. We might want to use the es_ - functions here. */ - fp = fopen (fname, "wx"); - if (!fp) + estream_t stream = es_fopen (cf->fname, "wx,mode=-rw-r"); + if (!stream) { - 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; + err = gpg_error_from_syserror (); + log_error (_("can't create `%s': %s\n"), + cf->fname, gpg_strerror (err)); + goto leave; } - fputs (sshcontrolblurb, fp); - fclose (fp); - fp = fopen (fname, append? "a+":"r"); + es_fputs (sshcontrolblurb, stream); + es_fclose (stream); + cf->fp = fopen (cf->fname, append? "a+":"r"); } - if (!fp) + if (!cf->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; + err = gpg_error_from_syserror (); + log_error (_("can't open `%s': %s\n"), + cf->fname, gpg_strerror (err)); + goto leave; } - *r_fp = fp; + err = 0; - return 0; + leave: + if (err && cf) + { + if (cf->fp) + fclose (cf->fp); + xfree (cf->fname); + xfree (cf); + } + else + *r_cf = cf; + + return err; } -/* 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. 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 void +rewind_control_file (ssh_control_file_t cf) +{ + fseek (cf->fp, 0, SEEK_SET); + cf->lnr = 0; + clearerr (cf->fp); +} + + +static void +close_control_file (ssh_control_file_t cf) +{ + if (!cf) + return; + fclose (cf->fp); + xfree (cf->fname); + xfree (cf); +} + + + +/* Read the next line from the control file and store the data in CF. + Returns 0 on success, GPG_ERR_EOF on EOF, or other error codes. */ static gpg_error_t -search_control_file (FILE *fp, const char *hexgrip, - int *r_disabled, int *r_ttl, int *r_confirm) +read_control_file_item (ssh_control_file_t cf) { int c, i, n; char *p, *pend, line[256]; - long ttl; - int lnr = 0; - const char fname[] = "sshcontrol"; + long ttl = 0; - assert (strlen (hexgrip) == 40 ); + cf->item.valid = 0; + clearerr (cf->fp); - if (r_confirm) - *r_confirm = 0; - - fseek (fp, 0, SEEK_SET); - clearerr (fp); - *r_disabled = 0; - next_line: do { - if (!fgets (line, DIM(line)-1, fp) ) + if (!fgets (line, DIM(line)-1, cf->fp) ) { - if (feof (fp)) + if (feof (cf->fp)) return gpg_error (GPG_ERR_EOF); - return gpg_error (gpg_err_code_from_errno (errno)); + return gpg_error_from_syserror (); } - lnr++; + cf->lnr++; if (!*line || line[strlen(line)-1] != '\n') { /* Eat until end of line */ - while ( (c=getc (fp)) != EOF && c != '\n') + while ( (c=getc (cf->fp)) != EOF && c != '\n') ; return gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); @@ -759,35 +852,34 @@ search_control_file (FILE *fp, const char *hexgrip, } while (!*p || *p == '\n' || *p == '#'); - *r_disabled = 0; + cf->item.disabled = 0; if (*p == '!') { - *r_disabled = 1; + cf->item.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; + cf->item.hexgrip[i] = (*p >= 'a'? (*p & 0xdf): *p); + cf->item.hexgrip[i] = 0; if (i != 40 || !(spacep (p) || *p == '\n')) { - log_error ("invalid formatted line in `%s', line %d\n", fname, lnr); + 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') || ttl < -1) + if (!(spacep (p) || *p == '\n') || (int)ttl < -1) { - log_error ("invalid TTL value in `%s', line %d; assuming 0\n", - fname, lnr); - ttl = 0; + log_error ("%s:%d: invalid TTL value; assuming 0\n", cf->fname, cf->lnr); + cf->item.ttl = 0; } - if (r_ttl) - *r_ttl = ttl; + 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++) @@ -797,22 +889,68 @@ search_control_file (FILE *fp, const char *hexgrip, n = strcspn (p, "= \t\n"); if (p[n] == '=') { - log_error ("assigning a value to a flag is not yet supported; " - "in `%s', line %d; flag ignored\n", fname, lnr); + 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)) { - if (r_confirm) - *r_confirm = 1; + cf->item.confirm = 1; } else - log_error ("invalid flag `%.*s' in `%s', line %d; ignored\n", - n, p, fname, lnr); + log_error ("%s:%d: invalid flag '%.*s'; ignored\n", + cf->fname, cf->lnr, n, p); p += n; } - return 0; /* Okay: found it. */ + /* 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 ); + + *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) + { + *r_disabled = cf->item.disabled; + if (r_ttl) + *r_ttl = cf->item.ttl; + if (r_confirm) + *r_confirm = cf->item.confirm; + } + return err; } @@ -827,16 +965,16 @@ add_control_entry (ctrl_t ctrl, const char *hexgrip, const char *fmtfpr, int ttl, int confirm) { gpg_error_t err; - FILE *fp; + ssh_control_file_t cf; int disabled; (void)ctrl; - err = open_control_file (&fp, 1); + err = open_control_file (&cf, 1); if (err) return err; - err = search_control_file (fp, hexgrip, &disabled, NULL, NULL); + err = search_control_file (cf, hexgrip, &disabled, NULL, NULL); if (err && gpg_err_code(err) == GPG_ERR_EOF) { struct tm *tp; @@ -845,15 +983,16 @@ add_control_entry (ctrl_t ctrl, const char *hexgrip, const char *fmtfpr, /* Not yet in the file - add it. Because the file has been opened in append mode, we simply need to write to it. */ tp = localtime (&atime); - fprintf (fp, ("# Key added on: %04d-%02d-%02d %02d:%02d:%02d\n" - "# Fingerprint: %s\n" - "%s %d%s\n"), + fprintf (cf->fp, + ("# Key added on: %04d-%02d-%02d %02d:%02d:%02d\n" + "# Fingerprint: %s\n" + "%s %d%s\n"), 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec, fmtfpr, hexgrip, ttl, confirm? " confirm":""); } - fclose (fp); + close_control_file (cf); return 0; } @@ -862,20 +1001,20 @@ add_control_entry (ctrl_t ctrl, const char *hexgrip, const char *fmtfpr, static int ttl_from_sshcontrol (const char *hexgrip) { - FILE *fp; + ssh_control_file_t cf; int disabled, ttl; if (!hexgrip || strlen (hexgrip) != 40) return 0; /* Wrong input: Use global default. */ - if (open_control_file (&fp, 0)) + if (open_control_file (&cf, 0)) return 0; /* Error: Use the global default TTL. */ - if (search_control_file (fp, hexgrip, &disabled, &ttl, NULL) + if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL) || disabled) ttl = 0; /* Use the global default if not found or disabled. */ - fclose (fp); + close_control_file (cf); return ttl; } @@ -885,26 +1024,107 @@ ttl_from_sshcontrol (const char *hexgrip) static int confirm_flag_from_sshcontrol (const char *hexgrip) { - FILE *fp; + 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 (&fp, 0)) + if (open_control_file (&cf, 0)) return 1; /* Error: Better ask for confirmation. */ - if (search_control_file (fp, hexgrip, &disabled, NULL, &confirm) + 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. */ - fclose (fp); + close_control_file (cf); return confirm; } + + +/* Open the ssh control file for reading. This is a public version of + open_control_file. The caller must use ssh_close_control_file to + release the retruned handle. */ +ssh_control_file_t +ssh_open_control_file (void) +{ + ssh_control_file_t cf; + + /* Then look at all the registered and non-disabled keys. */ + if (open_control_file (&cf, 0)) + return NULL; + return cf; +} + +/* Close an ssh control file handle. This is the public version of + close_control_file. CF may be NULL. */ +void +ssh_close_control_file (ssh_control_file_t cf) +{ + close_control_file (cf); +} + +/* Read the next item from the ssh control file. The function returns + 0 if a item was read, GPG_ERR_EOF on eof or another error value. + R_HEXGRIP shall either be null or a BUFFER of at least 41 byte. + R_DISABLED, R_TTLm and R_CONFIRM return flags from the control + file; they are only set on success. */ +gpg_error_t +ssh_read_control_file (ssh_control_file_t cf, + char *r_hexgrip, + int *r_disabled, int *r_ttl, int *r_confirm) +{ + gpg_error_t err; + + do + err = read_control_file_item (cf); + while (!err && !cf->item.valid); + if (!err) + { + if (r_hexgrip) + strcpy (r_hexgrip, cf->item.hexgrip); + if (r_disabled) + *r_disabled = cf->item.disabled; + if (r_ttl) + *r_ttl = cf->item.ttl; + if (r_confirm) + *r_confirm = cf->item.confirm; + } + return err; +} + + +/* Search for a key with HEXGRIP in sshcontrol and return all + info. */ +gpg_error_t +ssh_search_control_file (ssh_control_file_t cf, + const char *hexgrip, + int *r_disabled, int *r_ttl, int *r_confirm) +{ + gpg_error_t err; + int i; + const char *s; + char uphexgrip[41]; + + /* We need to make sure that HEXGRIP is all uppercase. The easiest + way to do this and also check its length is by copying to a + second buffer. */ + for (i=0, s=hexgrip; i < 40; s++, 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; +} + @@ -1022,13 +1242,16 @@ ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis) /* Signature encoder function for RSA. */ static gpg_error_t -ssh_signature_encoder_rsa (estream_t signature_blob, gcry_mpi_t *mpis) +ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, gcry_mpi_t *mpis) { unsigned char *data; size_t data_n; gpg_error_t err; gcry_mpi_t s; + (void)spec; + s = mpis[0]; err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, s); @@ -1046,7 +1269,8 @@ ssh_signature_encoder_rsa (estream_t signature_blob, gcry_mpi_t *mpis) /* Signature encoder function for DSA. */ static gpg_error_t -ssh_signature_encoder_dsa (estream_t signature_blob, gcry_mpi_t *mpis) +ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec, + estream_t signature_blob, gcry_mpi_t *mpis) { unsigned char buffer[SSH_DSA_SIGNATURE_PADDING * SSH_DSA_SIGNATURE_ELEMS]; unsigned char *data; @@ -1054,8 +1278,12 @@ ssh_signature_encoder_dsa (estream_t signature_blob, gcry_mpi_t *mpis) gpg_error_t err; int i; + (void)spec; + data = NULL; + /* 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]); @@ -1088,74 +1316,107 @@ ssh_signature_encoder_dsa (estream_t signature_blob, gcry_mpi_t *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_mpi_t *mpis) +{ + unsigned char *data[2] = {NULL, NULL}; + size_t data_n[2]; + size_t innerlen; + gpg_error_t err; + int i; + + (void)spec; + + 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]); + return err; +} + + /* S-Expressions. */ /* This function constructs a new S-Expression for the key identified - by the KEY_SPEC, SECRET, MPIS and COMMENT, which is to be stored in - *SEXP. Returns usual error code. */ + 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 *sexp, +sexp_key_construct (gcry_sexp_t *r_sexp, ssh_key_type_spec_t key_spec, int secret, - gcry_mpi_t *mpis, const char *comment) + const char *curve_name, gcry_mpi_t *mpis, + const char *comment) { const char *key_identifier[] = { "public-key", "private-key" }; - gcry_sexp_t sexp_new; - char *sexp_template; - size_t sexp_template_n; gpg_error_t err; + gcry_sexp_t sexp_new = NULL; + void *formatbuf = NULL; + void **arg_list = NULL; + int arg_idx; + estream_t format; const char *elems; size_t elems_n; - unsigned int i; - unsigned int j; - void **arg_list; + unsigned int i, j; - err = 0; - sexp_new = NULL; - arg_list = NULL; if (secret) elems = key_spec.elems_sexp_order; else elems = key_spec.elems_key_public; elems_n = strlen (elems); - /* - Calculate size for sexp_template_n: - - "(%s(%s<mpis>)(comment%s))" -> 20 + sizeof (<mpis>). - - mpi: (X%m) -> 5. - - */ - sexp_template_n = 20 + (elems_n * 5); - sexp_template = xtrymalloc (sexp_template_n); - if (! sexp_template) + format = es_fopenmem (0, "a+b"); + if (!format) { err = gpg_error_from_syserror (); goto out; } - /* Key identifier, algorithm identifier, mpis, comment. */ - arg_list = xtrymalloc (sizeof (*arg_list) * (2 + elems_n + 1)); - if (! arg_list) + /* 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; - i = 0; - arg_list[i++] = &key_identifier[secret]; - arg_list[i++] = &key_spec.identifier; + es_fputs ("(%s(%s", format); + arg_list[arg_idx++] = &key_identifier[secret]; + arg_list[arg_idx++] = &key_spec.identifier; + if (curve_name) + { + es_fputs ("(curve%s)", format); + arg_list[arg_idx++] = &curve_name; + } - *sexp_template = 0; - sexp_template_n = 0; - sexp_template_n = sprintf (sexp_template + sexp_template_n, "(%%s(%%s"); for (i = 0; i < elems_n; i++) { - sexp_template_n += sprintf (sexp_template + sexp_template_n, "(%c%%m)", - elems[i]); + es_fprintf (format, "(%c%%m)", elems[i]); if (secret) { for (j = 0; j < elems_n; j++) @@ -1164,58 +1425,66 @@ sexp_key_construct (gcry_sexp_t *sexp, } else j = i; - arg_list[i + 2] = &mpis[j]; + arg_list[arg_idx++] = &mpis[j]; } - sexp_template_n += sprintf (sexp_template + sexp_template_n, - ")(comment%%s))"); + es_fputs (")(comment%s))", format); + arg_list[arg_idx++] = &comment; + arg_list[arg_idx] = NULL; - arg_list[i + 2] = &comment; + 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, sexp_template, arg_list); + err = gcry_sexp_build_array (&sexp_new, NULL, formatbuf, arg_list); if (err) goto out; - *sexp = sexp_new; + *r_sexp = sexp_new; + err = 0; out: - + es_fclose (format); xfree (arg_list); - xfree (sexp_template); + xfree (formatbuf); return err; } + /* This functions breaks up the key contained in the S-Expression SEXP according to KEY_SPEC. The MPIs are bundled in a newly create list, which is to be stored in MPIS; a newly allocated string - holding the comment will be stored in COMMENT; SECRET will be - filled with a boolean flag specifying what kind of key it is. - Returns usual error code. */ + holding the curve name may be stored at RCURVE, and a comment will + be stored at COMMENT; SECRET will be filled with a boolean flag + specifying what kind of key it is. Returns an error code. */ static gpg_error_t sexp_key_extract (gcry_sexp_t sexp, ssh_key_type_spec_t key_spec, int *secret, - gcry_mpi_t **mpis, char **comment) + gcry_mpi_t **mpis, char **r_curve, char **comment) { - gpg_error_t err; - gcry_sexp_t value_list; - gcry_sexp_t value_pair; - gcry_sexp_t comment_list; + gpg_error_t err = 0; + gcry_sexp_t value_list = NULL; + gcry_sexp_t value_pair = NULL; + gcry_sexp_t comment_list = NULL; unsigned int i; - char *comment_new; + char *comment_new = NULL; const char *data; size_t data_n; int is_secret; size_t elems_n; const char *elems; - gcry_mpi_t *mpis_new; + gcry_mpi_t *mpis_new = NULL; gcry_mpi_t mpi; - - err = 0; - value_list = NULL; - value_pair = NULL; - comment_list = NULL; - comment_new = NULL; - mpis_new = NULL; + char *curve_name = NULL; data = gcry_sexp_nth_data (sexp, 0, &data_n); if (! data) @@ -1281,6 +1550,51 @@ sexp_key_extract (gcry_sexp_t sexp, if (err) goto out; + if ((key_spec.flags & SPEC_FLAG_IS_ECDSA)) + { + /* Parse the "curve" parameter. We currently expect the curve + name for ECC and not the parameters of the curve. This can + easily be changed but then we need to find the curve name + from the parameters using gcry_pk_get_curve. */ + const char *mapped; + + value_pair = gcry_sexp_find_token (value_list, "curve", 5); + if (!value_pair) + { + err = gpg_error (GPG_ERR_INV_CURVE); + goto out; + } + curve_name = gcry_sexp_nth_string (value_pair, 1); + if (!curve_name) + { + err = gpg_error (GPG_ERR_INV_CURVE); /* (Or out of core.) */ + goto out; + } + + /* Fixme: The mapping should be done by using gcry_pk_get_curve + et al to iterate over all name aliases. */ + if (!strcmp (curve_name, "NIST P-256")) + mapped = "nistp256"; + else if (!strcmp (curve_name, "NIST P-384")) + mapped = "nistp384"; + else if (!strcmp (curve_name, "NIST P-521")) + mapped = "nistp521"; + else + mapped = NULL; + if (mapped) + { + xfree (curve_name); + curve_name = xtrystrdup (mapped); + if (!curve_name) + { + err = gpg_error_from_syserror (); + goto out; + } + } + gcry_sexp_release (value_pair); + value_pair = NULL; + } + /* We do not require a comment sublist to be present here. */ data = NULL; data_n = 0; @@ -1305,6 +1619,7 @@ sexp_key_extract (gcry_sexp_t sexp, *secret = is_secret; *mpis = mpis_new; *comment = comment_new; + *r_curve = curve_name; out: @@ -1314,6 +1629,7 @@ sexp_key_extract (gcry_sexp_t sexp, if (err) { + xfree (curve_name); xfree (comment_new); mpint_list_free (mpis_new); } @@ -1400,6 +1716,24 @@ ssh_key_type_lookup (const char *ssh_name, const char *name, return err; } + +/* Lookup the ssh-identifier for the ECC curve CURVE_NAME. Returns + NULL if not found. */ +static const char * +ssh_identifier_from_curve_name (const char *curve_name) +{ + int i; + + for (i = 0; i < DIM (ssh_key_types); i++) + if (ssh_key_types[i].curve_name + && !strcmp (ssh_key_types[i].curve_name, curve_name)) + return ssh_key_types[i].ssh_identifier; + + return NULL; +} + + + /* 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. @@ -1416,6 +1750,8 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, ssh_key_type_spec_t spec; gcry_mpi_t *mpi_list = NULL; const char *elems; + char *curve_name = NULL; + err = stream_read_cstring (stream, &key_type); @@ -1426,6 +1762,50 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, if (err) goto out; + if ((spec.flags & SPEC_FLAG_IS_ECDSA)) + { + /* The format of an ECDSA key is: + * string key_type ("ecdsa-sha2-nistp256" | + * "ecdsa-sha2-nistp384" | + * "ecdsa-sha2-nistp521" ) + * string ecdsa_curve_name + * string ecdsa_public_key + * mpint ecdsa_private + * + * Note that we use the mpint reader instead of the string + * reader for ecsa_public_key. + */ + unsigned char *buffer; + const char *mapped; + + err = stream_read_string (stream, 0, &buffer, NULL); + if (err) + goto out; + curve_name = buffer; + /* Fixme: Check that curve_name matches the keytype. */ + /* Because Libgcrypt < 1.6 has no support for the "nistpNNN" + curve names, we need to translate them here to Libgcrypt's + native names. */ + if (!strcmp (curve_name, "nistp256")) + mapped = "NIST P-256"; + else if (!strcmp (curve_name, "nistp384")) + mapped = "NIST P-384"; + else if (!strcmp (curve_name, "nistp521")) + mapped = "NIST P-521"; + else + mapped = NULL; + if (mapped) + { + xfree (curve_name); + curve_name = xtrystrdup (mapped); + if (!curve_name) + { + err = gpg_error_from_syserror (); + goto out; + } + } + } + err = ssh_receive_mpint_list (stream, secret, spec, &mpi_list); if (err) goto out; @@ -1449,7 +1829,8 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, goto out; } - err = sexp_key_construct (&key, spec, secret, mpi_list, comment? comment:""); + err = sexp_key_construct (&key, spec, secret, curve_name, mpi_list, + comment? comment:""); if (err) goto out; @@ -1458,8 +1839,8 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, *key_new = key; out: - mpint_list_free (mpi_list); + xfree (curve_name); xfree (key_type); xfree (comment); @@ -1471,7 +1852,8 @@ ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, BLOB/BLOB_SIZE. Returns zero on success or an error code. */ static gpg_error_t ssh_convert_key_to_blob (unsigned char **blob, size_t *blob_size, - const char *type, gcry_mpi_t *mpis) + ssh_key_type_spec_t *spec, + const char *curve_name, gcry_mpi_t *mpis) { unsigned char *blob_new; long int blob_size_new; @@ -1493,14 +1875,31 @@ ssh_convert_key_to_blob (unsigned char **blob, size_t *blob_size, goto out; } - err = stream_write_cstring (stream, type); - if (err) - goto out; + if ((spec->flags & SPEC_FLAG_IS_ECDSA) && curve_name) + { + const char *sshname = ssh_identifier_from_curve_name (curve_name); + if (!curve_name) + { + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + goto out; + } + err = stream_write_cstring (stream, sshname); + if (err) + goto out; + err = stream_write_cstring (stream, curve_name); + if (err) + goto out; + } + else + { + err = stream_write_cstring (stream, spec->ssh_identifier); + if (err) + goto out; + } - for (i = 0; mpis[i] && (! err); i++) - err = stream_write_mpi (stream, mpis[i]); - if (err) - goto out; + for (i = 0; mpis[i]; i++) + if ((err = stream_write_mpi (stream, mpis[i]))) + goto out; blob_size_new = es_ftell (stream); if (blob_size_new == -1) @@ -1542,22 +1941,19 @@ ssh_convert_key_to_blob (unsigned char **blob, size_t *blob_size, 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_public, +ssh_send_key_public (estream_t stream, + gcry_sexp_t key_public, const char *override_comment) { ssh_key_type_spec_t spec; - gcry_mpi_t *mpi_list; - char *key_type; - char *comment; - unsigned char *blob; + gcry_mpi_t *mpi_list = NULL; + char *key_type = NULL; + char *curve; + char *comment = NULL; + unsigned char *blob = NULL; size_t blob_n; gpg_error_t err; - key_type = NULL; - mpi_list = NULL; - comment = NULL; - blob = NULL; - err = sexp_extract_identifier (key_public, &key_type); if (err) goto out; @@ -1566,12 +1962,11 @@ ssh_send_key_public (estream_t stream, gcry_sexp_t key_public, if (err) goto out; - err = sexp_key_extract (key_public, spec, NULL, &mpi_list, &comment); + err = sexp_key_extract (key_public, spec, NULL, &mpi_list, &curve, &comment); if (err) goto out; - err = ssh_convert_key_to_blob (&blob, &blob_n, - spec.ssh_identifier, mpi_list); + err = ssh_convert_key_to_blob (&blob, &blob_n, &spec, curve, mpi_list); if (err) goto out; @@ -1585,8 +1980,9 @@ ssh_send_key_public (estream_t stream, gcry_sexp_t key_public, out: mpint_list_free (mpi_list); - xfree (key_type); + xfree (curve); xfree (comment); + xfree (key_type); xfree (blob); return err; @@ -1639,7 +2035,10 @@ static gpg_error_t ssh_key_grip (gcry_sexp_t key, unsigned char *buffer) { if (!gcry_pk_get_keygrip (key, buffer)) - return gpg_error (GPG_ERR_INTERNAL); + { + gpg_error_t err = gcry_pk_testkey (key); + return err? err : gpg_error (GPG_ERR_INTERNAL); + } return 0; } @@ -1652,6 +2051,7 @@ static gpg_error_t key_secret_to_public (gcry_sexp_t *key_public, ssh_key_type_spec_t spec, gcry_sexp_t key_secret) { + char *curve; char *comment; gcry_mpi_t *mpis; gpg_error_t err; @@ -1660,16 +2060,18 @@ key_secret_to_public (gcry_sexp_t *key_public, comment = NULL; mpis = NULL; - err = sexp_key_extract (key_secret, spec, &is_secret, &mpis, &comment); + err = sexp_key_extract (key_secret, spec, &is_secret, &mpis, + &curve, &comment); if (err) goto out; - err = sexp_key_construct (key_public, spec, 0, mpis, comment); + err = sexp_key_construct (key_public, spec, 0, curve, mpis, comment); out: mpint_list_free (mpis); xfree (comment); + xfree (curve); return err; } @@ -1856,22 +2258,16 @@ static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl, estream_t request, estream_t response) { - char *key_type; ssh_key_type_spec_t spec; - struct dirent *dir_entry; - char *key_directory; - size_t key_directory_n; - char *key_path; - unsigned char *buffer; - size_t buffer_n; + char *key_fname = NULL; + char *fnameptr; u32 key_counter; estream_t key_blobs; gcry_sexp_t key_secret; gcry_sexp_t key_public; - DIR *dir; gpg_error_t err; int ret; - FILE *ctrl_fp = NULL; + ssh_control_file_t cf = NULL; char *cardsn; gpg_error_t ret_err; @@ -1879,56 +2275,24 @@ ssh_handler_request_identities (ctrl_t ctrl, /* Prepare buffer stream. */ - key_directory = NULL; key_secret = NULL; key_public = NULL; - key_type = NULL; - key_path = NULL; key_counter = 0; - buffer = NULL; - dir = NULL; err = 0; - key_blobs = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+"); + key_blobs = es_mopen (NULL, 0, 0, 1, NULL, NULL, "r+b"); if (! key_blobs) { err = gpg_error_from_syserror (); goto out; } - /* Open key directory. */ - key_directory = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL); - if (! key_directory) - { - err = gpg_err_code_from_errno (errno); - goto out; - } - key_directory_n = strlen (key_directory); - - key_path = xtrymalloc (key_directory_n + 46); - if (! key_path) - { - err = gpg_err_code_from_errno (errno); - goto out; - } - - sprintf (key_path, "%s/", key_directory); - sprintf (key_path + key_directory_n + 41, ".key"); - - dir = opendir (key_directory); - if (! dir) - { - err = gpg_err_code_from_errno (errno); - 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 (!card_key_available (ctrl, &key_public, &cardsn)) + if (!opt.disable_scdaemon + && !card_key_available (ctrl, &key_public, &cardsn)) { err = ssh_send_key_public (key_blobs, key_public, cardsn); gcry_sexp_release (key_public); @@ -1941,77 +2305,93 @@ ssh_handler_request_identities (ctrl_t ctrl, } - /* Then look at all the registered an allowed keys. */ + /* Prepare buffer for key name construction. */ + { + char *dname; + dname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL); + if (!dname) + { + err = gpg_err_code_from_syserror (); + goto out; + } + + key_fname = xtrymalloc (strlen (dname) + 1 + 40 + 4 + 1); + if (!key_fname) + { + err = gpg_err_code_from_syserror (); + xfree (dname); + goto out; + } + fnameptr = stpcpy (stpcpy (key_fname, dname), "/"); + xfree (dname); + } - /* 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. */ - /* FIXME: make sure that buffer gets deallocated properly. */ - err = open_control_file (&ctrl_fp, 0); + /* Then look at all the registered and non-disabled keys. */ + err = open_control_file (&cf, 0); if (err) goto out; - while ( (dir_entry = readdir (dir)) ) + while (!read_control_file_item (cf)) { - 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, NULL, NULL) - || disabled) - continue; - - strncpy (key_path + key_directory_n + 1, dir_entry->d_name, 40); + if (!cf->item.valid) + continue; /* Should not happen. */ + if (cf->item.disabled) + continue; + assert (strlen (cf->item.hexgrip) == 40); - /* Read file content. */ - err = file_to_buffer (key_path, &buffer, &buffer_n); - if (err) - goto out; + stpcpy (stpcpy (fnameptr, cf->item.hexgrip), ".key"); - err = gcry_sexp_sscan (&key_secret, NULL, (char*)buffer, buffer_n); - if (err) - goto out; + /* Read file content. */ + { + unsigned char *buffer; + size_t buffer_n; + + err = file_to_buffer (key_fname, &buffer, &buffer_n); + if (err) + { + log_error ("%s:%d: key '%s' skipped: %s\n", + cf->fname, cf->lnr, cf->item.hexgrip, + gpg_strerror (err)); + continue; + } - xfree (buffer); - buffer = NULL; + err = gcry_sexp_sscan (&key_secret, NULL, (char*)buffer, buffer_n); + xfree (buffer); + if (err) + goto out; + } - err = sexp_extract_identifier (key_secret, &key_type); - if (err) - goto out; + { + char *key_type = NULL; - err = ssh_key_type_lookup (NULL, key_type, &spec); - if (err) - goto out; + err = sexp_extract_identifier (key_secret, &key_type); + if (err) + goto out; - xfree (key_type); - key_type = NULL; + err = ssh_key_type_lookup (NULL, key_type, &spec); + xfree (key_type); + if (err) + goto out; + } - err = key_secret_to_public (&key_public, spec, key_secret); - if (err) - goto out; + err = key_secret_to_public (&key_public, spec, key_secret); + if (err) + goto out; - gcry_sexp_release (key_secret); - key_secret = NULL; + gcry_sexp_release (key_secret); + key_secret = NULL; - err = ssh_send_key_public (key_blobs, key_public, NULL); - if (err) - goto out; + err = ssh_send_key_public (key_blobs, key_public, NULL); + if (err) + goto out; - gcry_sexp_release (key_public); - key_public = NULL; + gcry_sexp_release (key_public); + key_public = NULL; - key_counter++; - } + key_counter++; } + err = 0; ret = es_fseek (key_blobs, 0, SEEK_SET); if (ret) @@ -2021,44 +2401,27 @@ ssh_handler_request_identities (ctrl_t ctrl, } out: - /* Send response. */ gcry_sexp_release (key_secret); gcry_sexp_release (key_public); - if (! err) + if (!err) { ret_err = stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER); - if (ret_err) - goto leave; - ret_err = stream_write_uint32 (response, key_counter); - if (ret_err) - goto leave; - ret_err = stream_copy (response, key_blobs); - if (ret_err) - goto leave; + if (!ret_err) + ret_err = stream_write_uint32 (response, key_counter); + if (!ret_err) + ret_err = stream_copy (response, key_blobs); } else { ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); - goto leave; - }; - - leave: - - if (key_blobs) - es_fclose (key_blobs); - if (dir) - closedir (dir); + } - if (ctrl_fp) - fclose (ctrl_fp); - - xfree (key_directory); - xfree (key_path); - xfree (buffer); - xfree (key_type); + es_fclose (key_blobs); + close_control_file (cf); + xfree (key_fname); return ret_err; } @@ -2081,7 +2444,7 @@ data_hash (unsigned char *data, size_t data_n, signature in newly allocated memory in SIG and it's size in SIG_N; SIG_ENCODER is the signature encoder to use. */ static gpg_error_t -data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, +data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec, unsigned char **sig, size_t *sig_n) { gpg_error_t err; @@ -2092,10 +2455,6 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, gcry_mpi_t sig_value = NULL; unsigned char *sig_blob = NULL; size_t sig_blob_n = 0; - char *identifier = NULL; - const char *identifier_raw; - size_t identifier_n; - ssh_key_type_spec_t spec; int ret; unsigned int i; const char *elems; @@ -2174,29 +2533,11 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, goto out; } - identifier_raw = gcry_sexp_nth_data (valuelist, 0, &identifier_n); - if (! identifier_raw) - { - err = gpg_error (GPG_ERR_INV_SEXP); - goto out; - } - - identifier = make_cstring (identifier_raw, identifier_n); - if (! identifier) - { - err = gpg_error_from_syserror (); - goto out; - } - - err = ssh_key_type_lookup (NULL, identifier, &spec); + err = stream_write_cstring (stream, spec->ssh_identifier); if (err) goto out; - err = stream_write_cstring (stream, spec.ssh_identifier); - if (err) - goto out; - - elems = spec.elems_signature; + elems = spec->elems_signature; elems_n = strlen (elems); mpis = xtrycalloc (elems_n + 1, sizeof *mpis); @@ -2208,7 +2549,7 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, for (i = 0; i < elems_n; i++) { - sublist = gcry_sexp_find_token (valuelist, spec.elems_signature + i, 1); + sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); if (! sublist) { err = gpg_error (GPG_ERR_INV_SEXP); @@ -2229,7 +2570,7 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, if (err) goto out; - err = (*sig_encoder) (stream, mpis); + err = spec->signature_encoder (spec, stream, mpis); if (err) goto out; @@ -2272,7 +2613,6 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder, gcry_sexp_release (signature_sexp); gcry_sexp_release (sublist); mpint_list_free (mpis); - xfree (identifier); return err; } @@ -2295,6 +2635,7 @@ ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response) u32 flags; gpg_error_t err; gpg_error_t ret_err; + int hash_algo; key_blob = NULL; data = NULL; @@ -2321,14 +2662,18 @@ ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response) if (err) goto out; + hash_algo = spec.hash_algo; + if (!hash_algo) + hash_algo = GCRY_MD_SHA1; /* Use the default. */ + /* Hash data. */ - hash_n = gcry_md_get_algo_dlen (GCRY_MD_SHA1); + 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, GCRY_MD_SHA1, hash); + err = data_hash (data, data_size, hash_algo, hash); if (err) goto out; @@ -2339,14 +2684,17 @@ ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response) /* Sign data. */ - ctrl->digest.algo = GCRY_MD_SHA1; + ctrl->digest.algo = hash_algo; memcpy (ctrl->digest.value, hash, hash_n); ctrl->digest.valuelen = hash_n; - ctrl->digest.raw_value = ! (spec.flags & SPEC_FLAG_USE_PKCS1V2); + if ((spec.flags & SPEC_FLAG_USE_PKCS1V2)) + ctrl->digest.raw_value = 0; + else + ctrl->digest.raw_value = 1; ctrl->have_keygrip = 1; memcpy (ctrl->keygrip, key_grip, 20); - err = data_sign (ctrl, spec.signature_encoder, &sig, &sig_n); + err = data_sign (ctrl, &spec, &sig, &sig_n); out: @@ -2467,6 +2815,7 @@ reenter_compare_cb (struct pin_entry_info_s *pi) return -1; } + /* Store the ssh KEY into our local key storage and protect it after asking for a passphrase. Cache that passphrase. TTL is the maximum caching time for that key. If the key already exists in @@ -2517,7 +2866,6 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl, int confirm) goto out; } - pi = gcry_calloc_secure (2, sizeof (*pi) + 100 + 1); if (!pi) { @@ -3007,44 +3355,50 @@ ssh_request_process (ctrl_t ctrl, estream_t stream_sock) return !!err; } -/* Start serving client on SOCK_CLIENT. */ -void -start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client) + +/* Because the ssh protocol does not send us information about the the + current TTY setting, we use this function to use those from startup + or those explictly set. */ +static gpg_error_t +setup_ssh_env (ctrl_t ctrl) { - estream_t stream_sock = NULL; + static const char *names[] = + {"GPG_TTY", "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL}; gpg_error_t err = 0; - int ret; + int idx; + const char *value; - /* 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. */ - { - static const char *names[] = - {"GPG_TTY", "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL}; - int idx; - const char *value; + for (idx=0; !err && names[idx]; idx++) + if ((value = session_env_getenv (opt.startup_env, names[idx]))) + err = session_env_setenv (ctrl->session_env, names[idx], value); - for (idx=0; !err && names[idx]; idx++) - if (!session_env_getenv (ctrl->session_env, names[idx]) - && (value = session_env_getenv (opt.startup_env, names[idx]))) - err = session_env_setenv (ctrl->session_env, names[idx], value); + if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype) + if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype))) + err = gpg_error_from_syserror (); - if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype) - if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype))) - err = gpg_error_from_syserror (); + if (!err && !ctrl->lc_messages && opt.startup_lc_messages) + if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages))) + err = gpg_error_from_syserror (); - if (!err && !ctrl->lc_messages && opt.startup_lc_messages) - if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages))) - err = gpg_error_from_syserror (); + if (err) + log_error ("error setting default session environment: %s\n", + gpg_strerror (err)); - if (err) - { - log_error ("error setting default session environment: %s\n", - gpg_strerror (err)); - goto out; - } - } + return err; +} + + +/* Start serving client on SOCK_CLIENT. */ +void +start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client) +{ + estream_t stream_sock = NULL; + gpg_error_t err; + int ret; + err = setup_ssh_env (ctrl); + if (err) + goto out; /* Create stream from socket. */ stream_sock = es_fdopen (FD2INT(sock_client), "r+"); @@ -3087,3 +3441,149 @@ start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client) if (stream_sock) es_fclose (stream_sock); } + + +#ifdef HAVE_W32_SYSTEM +/* Serve one ssh-agent request. This is used for the Putty support. + REQUEST is the the mmapped memory which may be accessed up to a + length of MAXREQLEN. Returns 0 on success which also indicates + that a valid SSH response message is now in REQUEST. */ +int +serve_mmapped_ssh_request (ctrl_t ctrl, + unsigned char *request, size_t maxreqlen) +{ + gpg_error_t err; + int send_err = 0; + int valid_response = 0; + ssh_request_spec_t *spec; + u32 msglen; + estream_t request_stream, response_stream; + + if (setup_ssh_env (ctrl)) + goto leave; /* Error setting up the environment. */ + + if (maxreqlen < 5) + goto leave; /* Caller error. */ + + msglen = uint32_construct (request[0], request[1], request[2], request[3]); + if (msglen < 1 || msglen > maxreqlen - 4) + { + log_error ("ssh message len (%u) out of range", (unsigned int)msglen); + goto leave; + } + + spec = request_spec_lookup (request[4]); + if (!spec) + { + send_err = 1; /* Unknown request type. */ + goto leave; + } + + /* Create a stream object with the data part of the request. */ + if (spec->secret_input) + request_stream = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+"); + else + request_stream = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+"); + if (!request_stream) + { + err = gpg_error_from_syserror (); + goto leave; + } + /* We have to disable the estream buffering, because the estream + core doesn't know about secure memory. */ + if (es_setvbuf (request_stream, NULL, _IONBF, 0)) + { + err = gpg_error_from_syserror (); + goto leave; + } + /* Copy the request to the stream but omit the request type. */ + err = stream_write_data (request_stream, request + 5, msglen - 1); + if (err) + goto leave; + es_rewind (request_stream); + + response_stream = es_fopenmem (0, "r+b"); + if (!response_stream) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (opt.verbose) + log_info ("ssh request handler for %s (%u) started\n", + spec->identifier, spec->type); + + err = (*spec->handler) (ctrl, request_stream, response_stream); + + if (opt.verbose) + { + if (err) + log_info ("ssh request handler for %s (%u) failed: %s\n", + spec->identifier, spec->type, gpg_strerror (err)); + else + log_info ("ssh request handler for %s (%u) ready\n", + spec->identifier, spec->type); + } + + es_fclose (request_stream); + request_stream = NULL; + + if (err) + { + send_err = 1; + goto leave; + } + + /* Put the response back into the mmapped buffer. */ + { + void *response_data; + size_t response_size; + + /* NB: In contrast to the request-stream, the response stream + includes the the message type byte. */ + if (es_fclose_snatch (response_stream, &response_data, &response_size)) + { + log_error ("snatching ssh response failed: %s", + gpg_strerror (gpg_error_from_syserror ())); + send_err = 1; /* Ooops. */ + goto leave; + } + + if (opt.verbose > 1) + log_info ("sending ssh response of length %u\n", + (unsigned int)response_size); + if (response_size > maxreqlen - 4) + { + log_error ("invalid length of the ssh response: %s", + gpg_strerror (GPG_ERR_INTERNAL)); + es_free (response_data); + send_err = 1; + goto leave; + } + + request[0] = response_size >> 24; + request[1] = response_size >> 16; + request[2] = response_size >> 8; + request[3] = response_size >> 0; + memcpy (request+4, response_data, response_size); + es_free (response_data); + valid_response = 1; + } + + leave: + if (send_err) + { + request[0] = 0; + request[1] = 0; + request[2] = 0; + request[3] = 1; + request[4] = SSH_RESPONSE_FAILURE; + valid_response = 1; + } + + /* Reset the SCD in case it has been used. */ + agent_reset_scd (ctrl); + + return valid_response? 0 : -1; +} +#endif /*HAVE_W32_SYSTEM*/ |