diff --git a/agent/agent.h b/agent/agent.h index 7445061ac..885e7fe75 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -157,6 +157,10 @@ struct /* Forward reference for local definitions in command.c. */ struct server_local_s; +/* Declaration of objects from command-ssh.c. */ +struct ssh_control_file_s; +typedef struct ssh_control_file_s *ssh_control_file_t; + /* Forward reference for local definitions in call-scd.c. */ struct scd_local_s; @@ -290,6 +294,16 @@ gpg_error_t pinentry_loopback(ctrl_t, const char *keyword, size_t max_length); /*-- command-ssh.c --*/ +ssh_control_file_t ssh_open_control_file (void); +void ssh_close_control_file (ssh_control_file_t cf); +gpg_error_t ssh_read_control_file (ssh_control_file_t cf, + char *r_hexgrip, int *r_disabled, + int *r_ttl, int *r_confirm); +gpg_error_t ssh_search_control_file (ssh_control_file_t cf, + const char *hexgrip, + int *r_disabled, + int *r_ttl, int *r_confirm); + void start_command_handler_ssh (ctrl_t, gnupg_fd_t); /*-- findkey.c --*/ diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 39435aa30..f378ade55 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, 2012 Free Software Foundation, Inc. + * Copyright (C) 2013 Werner Koch * * This file is part of GnuPG. * @@ -87,7 +88,7 @@ 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"; @@ -186,8 +187,8 @@ struct ssh_key_type_spec }; -/* An object used to access the sshcontrol file. */ -struct control_file_s +/* 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. */ @@ -200,8 +201,6 @@ struct control_file_s char hexgrip[40+1]; /* The hexgrip of the item (uppercase). */ } item; }; -typedef struct control_file_s *control_file_t; - /* Prototypes. */ @@ -730,10 +729,10 @@ file_to_buffer (const char *filename, unsigned char **buffer, size_t *buffer_n) 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 (control_file_t *r_cf, int append) +open_control_file (ssh_control_file_t *r_cf, int append) { gpg_error_t err; - control_file_t cf; + ssh_control_file_t cf; cf = xtrycalloc (1, sizeof *cf); if (!cf) @@ -795,7 +794,7 @@ open_control_file (control_file_t *r_cf, int append) static void -rewind_control_file (control_file_t cf) +rewind_control_file (ssh_control_file_t cf) { fseek (cf->fp, 0, SEEK_SET); cf->lnr = 0; @@ -804,7 +803,7 @@ rewind_control_file (control_file_t cf) static void -close_control_file (control_file_t cf) +close_control_file (ssh_control_file_t cf) { if (!cf) return; @@ -818,7 +817,7 @@ close_control_file (control_file_t 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 (control_file_t cf) +read_control_file_item (ssh_control_file_t cf) { int c, i, n; char *p, *pend, line[256]; @@ -921,7 +920,7 @@ read_control_file_item (control_file_t cf) 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 (control_file_t cf, const char *hexgrip, +search_control_file (ssh_control_file_t cf, const char *hexgrip, int *r_disabled, int *r_ttl, int *r_confirm) { gpg_error_t err; @@ -965,7 +964,7 @@ add_control_entry (ctrl_t ctrl, const char *hexgrip, const char *fmtfpr, int ttl, int confirm) { gpg_error_t err; - control_file_t cf; + ssh_control_file_t cf; int disabled; (void)ctrl; @@ -1001,7 +1000,7 @@ add_control_entry (ctrl_t ctrl, const char *hexgrip, const char *fmtfpr, static int ttl_from_sshcontrol (const char *hexgrip) { - control_file_t cf; + ssh_control_file_t cf; int disabled, ttl; if (!hexgrip || strlen (hexgrip) != 40) @@ -1024,7 +1023,7 @@ ttl_from_sshcontrol (const char *hexgrip) static int confirm_flag_from_sshcontrol (const char *hexgrip) { - control_file_t cf; + ssh_control_file_t cf; int disabled, confirm; if (!hexgrip || strlen (hexgrip) != 40) @@ -1044,6 +1043,87 @@ confirm_flag_from_sshcontrol (const char *hexgrip) } + + +/* 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; +} + @@ -2185,7 +2265,7 @@ ssh_handler_request_identities (ctrl_t ctrl, gcry_sexp_t key_public; gpg_error_t err; int ret; - control_file_t cf = NULL; + ssh_control_file_t cf = NULL; char *cardsn; gpg_error_t ret_err; diff --git a/agent/command.c b/agent/command.c index 036486856..5e955ab6d 100644 --- a/agent/command.c +++ b/agent/command.c @@ -998,13 +998,15 @@ cmd_readkey (assuan_context_t ctx, char *line) static const char hlp_keyinfo[] = - "KEYINFO [--list] [--data] [--ssh-fpr] \n" + "KEYINFO [--[ssh-]list] [--data] [--ssh-fpr] [--with-ssh] \n" "\n" "Return information about the key specified by the KEYGRIP. If the\n" "key is not available GPG_ERR_NOT_FOUND is returned. If the option\n" "--list is given the keygrip is ignored and information about all\n" - "available keys are returned. The information is returned as a\n" - "status line unless --data was specified, with this format:\n" + "available keys are returned. If --ssh-list is given information\n" + "about all keys listed in the sshcontrol are returned. With --with-ssh\n" + "information from sshcontrol is always added to the info. Unless --data\n" + "is given, the information is returned as a status line using the format:\n" "\n" " KEYINFO \n" "\n" @@ -1013,7 +1015,8 @@ static const char hlp_keyinfo[] = "TYPE is describes the type of the key:\n" " 'D' - Regular key stored on disk,\n" " 'T' - Key is stored on a smartcard (token),\n" - " '-' - Unknown type.\n" + " 'X' - Unknown type,\n" + " '-' - Key is missing.\n" "\n" "SERIALNO is an ASCII string with the serial number of the\n" " smartcard. If the serial number is not known a single\n" @@ -1031,13 +1034,21 @@ static const char hlp_keyinfo[] = " '-' - Unknown protection.\n" "\n" "FPR returns the formatted ssh-style fingerprint of the key. It is only\n" - " print if the option --ssh-fpr has been used. '-' is printed if the\n" - " fingerprint is not available.\n" + " printed if the option --ssh-fpr has been used. It defaults to '-'.\n" + "\n" + "TTL is the TTL in seconds for that key or '-' if n/a.\n" + "\n" + "FLAGS is a word consisting of one-letter flags:\n" + " 'D' - The key has been disabled,\n" + " 'S' - The key is listed in sshcontrol (requires --with-ssh),\n" + " 'c' - Use of the key needs to be confirmed,\n" + " '-' - No flags given.\n" "\n" "More information may be added in the future."; static gpg_error_t do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx, - int data, int with_ssh_fpr) + int data, int with_ssh_fpr, int in_ssh, + int ttl, int disabled, int confirm) { gpg_error_t err; char hexgrip[40+1]; @@ -1050,24 +1061,55 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx, const char *cached; const char *protectionstr; char *pw; + int missing_key = 0; + char ttlbuf[20]; + char flagsbuf[5]; err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info); if (err) - goto leave; + { + if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND) + missing_key = 1; + else + goto leave; + } /* Reformat the grip so that we use uppercase as good style. */ bin2hex (grip, 20, hexgrip); - switch (keytype) + if (ttl > 0) + snprintf (ttlbuf, sizeof ttlbuf, "%d", ttl); + else + strcpy (ttlbuf, "-"); + + *flagsbuf = 0; + if (disabled) + strcat (flagsbuf, "D"); + if (in_ssh) + strcat (flagsbuf, "S"); + if (confirm) + strcat (flagsbuf, "c"); + if (!*flagsbuf) + strcpy (flagsbuf, "-"); + + + if (missing_key) { - case PRIVATE_KEY_CLEAR: protectionstr = "C"; keytypestr = "D"; - break; - case PRIVATE_KEY_PROTECTED: protectionstr = "P"; keytypestr = "D"; - break; - case PRIVATE_KEY_SHADOWED: protectionstr = "-"; keytypestr = "T"; - break; - default: protectionstr = "-"; keytypestr = "-"; - break; + protectionstr = "-"; keytypestr = "-"; + } + else + { + switch (keytype) + { + case PRIVATE_KEY_CLEAR: protectionstr = "C"; keytypestr = "D"; + break; + case PRIVATE_KEY_PROTECTED: protectionstr = "P"; keytypestr = "D"; + break; + case PRIVATE_KEY_SHADOWED: protectionstr = "-"; keytypestr = "T"; + break; + default: protectionstr = "-"; keytypestr = "X"; + break; + } } /* Compute the ssh fingerprint if requested. */ @@ -1105,16 +1147,20 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx, cached, protectionstr, fpr? fpr : "-", + ttlbuf, + flagsbuf, NULL); else { char *string; - string = xtryasprintf ("%s %s %s %s %s %s %s\n", + string = xtryasprintf ("%s %s %s %s %s %s %s %s %s\n", hexgrip, keytypestr, serialno? serialno : "-", idstr? idstr : "-", cached, protectionstr, - fpr? fpr : "-"); + fpr? fpr : "-", + ttlbuf, + flagsbuf); if (!string) err = gpg_error_from_syserror (); else @@ -1141,18 +1187,44 @@ cmd_keyinfo (assuan_context_t ctx, char *line) unsigned char grip[20]; DIR *dir = NULL; int list_mode; - int opt_data, opt_ssh_fpr; + int opt_data, opt_ssh_fpr, opt_with_ssh; + ssh_control_file_t cf = NULL; + char hexgrip[41]; + int disabled, ttl, confirm, is_ssh; - list_mode = has_option (line, "--list"); + if (has_option (line, "--ssh-list")) + list_mode = 2; + else + list_mode = has_option (line, "--list"); opt_data = has_option (line, "--data"); opt_ssh_fpr = has_option (line, "--ssh-fpr"); + opt_with_ssh = has_option (line, "--with-ssh"); line = skip_options (line); - if (list_mode) + if (opt_with_ssh || list_mode == 2) + cf = ssh_open_control_file (); + + if (list_mode == 2) + { + if (cf) + { + while (!ssh_read_control_file (cf, hexgrip, + &disabled, &ttl, &confirm)) + { + if (hex2bin (hexgrip, grip, 20) < 0 ) + continue; /* Bad hex string. */ + err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, 1, + ttl, disabled, confirm); + if (err) + goto leave; + } + } + err = 0; + } + else if (list_mode) { char *dirname; struct dirent *dir_entry; - char hexgrip[41]; dirname = make_filename_try (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL); if (!dirname) @@ -1180,7 +1252,19 @@ cmd_keyinfo (assuan_context_t ctx, char *line) if ( hex2bin (hexgrip, grip, 20) < 0 ) continue; /* Bad hex string. */ - err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr); + disabled = ttl = confirm = is_ssh = 0; + if (opt_with_ssh) + { + err = ssh_search_control_file (cf, hexgrip, + &disabled, &ttl, &confirm); + if (!err) + is_ssh = 1; + else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) + goto leave; + } + + err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh, + ttl, disabled, confirm); if (err) goto leave; } @@ -1191,10 +1275,23 @@ cmd_keyinfo (assuan_context_t ctx, char *line) err = parse_keygrip (ctx, line, grip); if (err) goto leave; - err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr); + disabled = ttl = confirm = is_ssh = 0; + if (opt_with_ssh) + { + err = ssh_search_control_file (cf, line, + &disabled, &ttl, &confirm); + if (!err) + is_ssh = 1; + else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) + goto leave; + } + + err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh, + ttl, disabled, confirm); } leave: + ssh_close_control_file (cf); if (dir) closedir (dir); if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)