diff --git a/NEWS b/NEWS index 4295ee96d..adaa2570f 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,9 @@ Noteworthy changes in version 2.0.21 (unreleased) * The included ssh agent does now support ECDSA keys. + * New option --enable-putty-support to allow gpg-agent to act as a + Pageant replacement including full smartcard support. + Noteworthy changes in version 2.0.20 (2013-05-10) ------------------------------------------------- diff --git a/agent/agent.h b/agent/agent.h index 15cf8bfcd..4afe6e9c1 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -220,6 +220,10 @@ gpg_error_t agent_write_status (ctrl_t ctrl, const char *keyword, ...) void bump_key_eventcounter (void); void bump_card_eventcounter (void); void start_command_handler (ctrl_t, gnupg_fd_t, gnupg_fd_t); +#ifdef HAVE_W32_SYSTEM +int serve_mmapped_ssh_request (ctrl_t ctrl, + unsigned char *request, size_t maxreqlen); +#endif /*HAVE_W32_SYSTEM*/ /*-- command-ssh.c --*/ void start_command_handler_ssh (ctrl_t, gnupg_fd_t); diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 4cd353713..65337303f 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -3362,3 +3362,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*/ diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index ba25875be..9d53de9a9 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -1,6 +1,7 @@ /* gpg-agent.c - The GnuPG Agent * Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, * 2006, 2007, 2009, 2010 Free Software Foundation, Inc. + * Copyright (C) 2013 Werner Koch * * This file is part of GnuPG. * @@ -30,7 +31,16 @@ #include #include #include -#ifndef HAVE_W32_SYSTEM +#ifdef HAVE_W32_SYSTEM +# ifndef WINVER +# define WINVER 0x0500 /* Same as in common/sysutils.c */ +# endif +# ifdef HAVE_WINSOCK2_H +# include +# endif +# include +# include +#else /*!HAVE_W32_SYSTEM*/ # include # include #endif /*!HAVE_W32_SYSTEM*/ @@ -106,6 +116,7 @@ enum cmd_and_opt_values oKeepTTY, oKeepDISPLAY, oSSHSupport, + oPuttySupport, oDisableScdaemon, oWriteEnvFile }; @@ -177,7 +188,14 @@ static ARGPARSE_OPTS opts[] = { N_("allow clients to mark keys as \"trusted\"")}, { oAllowPresetPassphrase, "allow-preset-passphrase", 0, N_("allow presetting passphrase")}, - { oSSHSupport, "enable-ssh-support", 0, N_("enable ssh-agent emulation") }, + { oSSHSupport, "enable-ssh-support", 0, N_("enable ssh support") }, + { oPuttySupport, "enable-putty-support", 0, +#ifdef HAVE_W32_SYSTEM + N_("enable putty support") +#else + "@" +#endif + }, { oWriteEnvFile, "write-env-file", 2|8, N_("|FILE|write environment settings also to FILE")}, {0} @@ -202,6 +220,17 @@ static ARGPARSE_OPTS opts[] = { #endif +#ifdef HAVE_W32_SYSTEM +/* Flag indicating that support for Putty has been enabled. */ +static int putty_support; +/* A magic value used with WM_COPYDATA. */ +#define PUTTY_IPC_MAGIC 0x804e50ba +/* To avoid surprises we limit the size of the mapped IPC file to this + value. Putty currently (0.62) uses 8k, thus 16k should be enough + for the foreseeable future. */ +#define PUTTY_IPC_MAXLEN 16384 +#endif /*HAVE_W32_SYSTEM*/ + /* The list of open file descriptors at startup. Note that this list has been allocated using the standard malloc. */ static int *startup_fd_list; @@ -805,6 +834,13 @@ main (int argc, char **argv ) case oKeepDISPLAY: opt.keep_display = 1; break; case oSSHSupport: opt.ssh_support = 1; break; + case oPuttySupport: +# ifdef HAVE_W32_SYSTEM + putty_support = 1; + opt.ssh_support = 1; +# endif + break; + case oWriteEnvFile: if (pargs.r_type) env_file_name = pargs.r.ret_str; @@ -928,6 +964,11 @@ main (int argc, char **argv ) GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); printf ("disable-scdaemon:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); +#ifdef HAVE_W32_SYSTEM + printf ("enable-putty-support:%lu:\n", GC_OPT_FLAG_NONE); +#else + printf ("enable-ssh-support:%lu:\n", GC_OPT_FLAG_NONE); +#endif agent_exit (0); } @@ -1292,6 +1333,8 @@ agent_exit (int rc) static void agent_init_default_ctrl (ctrl_t ctrl) { + assert (ctrl->session_env); + /* Note we ignore malloc errors because we can't do much about it and the request will fail anyway shortly after this initialization. */ @@ -1309,7 +1352,6 @@ agent_init_default_ctrl (ctrl_t ctrl) xfree (ctrl->lc_messages); ctrl->lc_messages = default_lc_messages? xtrystrdup (default_lc_messages) /**/ : NULL; - } @@ -1788,6 +1830,199 @@ check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce) } +#ifdef HAVE_W32_SYSTEM +/* The window message processing function for Putty. Warning: This + code runs as a native Windows thread. Use of our own functions + needs to be bracket with pth_leave/pth_enter. */ +static LRESULT CALLBACK +putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + int ret = 0; + int w32rc; + COPYDATASTRUCT *cds; + const char *mapfile; + HANDLE maphd; + PSID mysid = NULL; + PSID mapsid = NULL; + void *data = NULL; + PSECURITY_DESCRIPTOR psd = NULL; + ctrl_t ctrl = NULL; + + if (msg != WM_COPYDATA) + { + /* pth_leave (); */ + /* log_debug ("putty loop: received WM_%u\n", msg ); */ + /* pth_enter (); */ + return DefWindowProc (hwnd, msg, wparam, lparam); + } + + cds = (COPYDATASTRUCT*)lparam; + if (cds->dwData != PUTTY_IPC_MAGIC) + return 0; /* Ignore data with the wrong magic. */ + mapfile = cds->lpData; + if (!cds->cbData || mapfile[cds->cbData - 1]) + return 0; /* Ignore empty and non-properly terminated strings. */ + + if (DBG_ASSUAN) + { + pth_leave (); + log_debug ("ssh map file '%s'", mapfile); + pth_enter (); + } + + maphd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, mapfile); + if (DBG_ASSUAN) + { + pth_leave (); + log_debug ("ssh map handle %p\n", maphd); + pth_enter (); + } + + if (!maphd || maphd == INVALID_HANDLE_VALUE) + return 0; + + pth_leave (); + + mysid = w32_get_user_sid (); + if (!mysid) + { + log_error ("error getting my sid\n"); + goto leave; + } + + w32rc = GetSecurityInfo (maphd, SE_KERNEL_OBJECT, + OWNER_SECURITY_INFORMATION, + &mapsid, NULL, NULL, NULL, + &psd); + if (w32rc) + { + log_error ("error getting sid of ssh map file: rc=%d", w32rc); + goto leave; + } + + if (DBG_ASSUAN) + { + char *sidstr; + + if (!ConvertSidToStringSid (mysid, &sidstr)) + sidstr = NULL; + log_debug (" my sid: '%s'", sidstr? sidstr: "[error]"); + LocalFree (sidstr); + if (!ConvertSidToStringSid (mapsid, &sidstr)) + sidstr = NULL; + log_debug ("ssh map file sid: '%s'", sidstr? sidstr: "[error]"); + LocalFree (sidstr); + } + + if (!EqualSid (mysid, mapsid)) + { + log_error ("ssh map file has a non-matching sid\n"); + goto leave; + } + + data = MapViewOfFile (maphd, FILE_MAP_ALL_ACCESS, 0, 0, 0); + if (DBG_ASSUAN) + log_debug ("ssh IPC buffer at %p\n", data); + if (!data) + goto leave; + + /* log_printhex ("request:", data, 20); */ + + ctrl = xtrycalloc (1, sizeof *ctrl); + if (!ctrl) + { + log_error ("error allocating connection control data: %s\n", + strerror (errno) ); + goto leave; + } + ctrl->session_env = session_env_new (); + if (!ctrl->session_env) + { + log_error ("error allocating session environment block: %s\n", + strerror (errno) ); + goto leave; + } + + agent_init_default_ctrl (ctrl); + if (!serve_mmapped_ssh_request (ctrl, data, PUTTY_IPC_MAXLEN)) + ret = 1; /* Valid ssh message has been constructed. */ + agent_deinit_default_ctrl (ctrl); + /* log_printhex (" reply:", data, 20); */ + + leave: + xfree (ctrl); + if (data) + UnmapViewOfFile (data); + xfree (mapsid); + if (psd) + LocalFree (psd); + xfree (mysid); + CloseHandle (maphd); + + pth_enter (); + + return ret; +} +#endif /*HAVE_W32_SYSTEM*/ + + +#ifdef HAVE_W32_SYSTEM +/* The thread handling Putty's IPC requests. */ +static void * +putty_message_thread (void *arg) +{ + WNDCLASS wndwclass = {0, putty_message_proc, 0, 0, + NULL, NULL, NULL, NULL, NULL, "Pageant"}; + HWND hwnd; + MSG msg; + + (void)arg; + + if (opt.verbose) + log_info ("putty message loop thread 0x%lx started\n", pth_thread_id ()); + + /* The message loop runs as thread independet from out Pth system. + This also meand that we need to make sure that we switch back to + our system before calling any no-windows function. */ + pth_enter (); + + /* First create a window to make sure that a message queue exists + for this thread. */ + if (!RegisterClass (&wndwclass)) + { + pth_leave (); + log_error ("error registering Pageant window class"); + return NULL; + } + hwnd = CreateWindowEx (0, "Pageant", "Pageant", 0, + 0, 0, 0, 0, + HWND_MESSAGE, /* hWndParent */ + NULL, /* hWndMenu */ + NULL, /* hInstance */ + NULL); /* lpParm */ + if (!hwnd) + { + pth_leave (); + log_error ("error creating Pageant window"); + return NULL; + } + + while (GetMessage(&msg, NULL, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + /* Back to Pth. */ + pth_leave (); + + if (opt.verbose) + log_info ("putty message loop thread 0x%lx stopped\n", pth_thread_id ()); + return NULL; +} +#endif /*HAVE_W32_SYSTEM*/ + + /* This is the standard connection thread's main function. */ static void * start_connection_thread (void *arg) @@ -1897,6 +2132,21 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_ssh) #endif time_ev = NULL; + /* On Windows we need to fire up a separate thread to listen for + requests from Putty (an SSH client), so we can replace Putty's + Pageant (its ssh-agent implementation). */ +#ifdef HAVE_W32_SYSTEM + if (putty_support) + { + pth_attr_set (tattr, PTH_ATTR_NAME, "putty message loop"); + if (!pth_spawn (tattr, putty_message_thread, NULL)) + { + log_error ("error spawning putty message loop: %s\n", + strerror (errno) ); + } + } +#endif /*HAVE_W32_SYSTEM*/ + /* Set a flag to tell call-scd.c that it may enable event notifications. */ opt.sigusr2_enabled = 1; diff --git a/common/sysutils.c b/common/sysutils.c index 82bc81f56..8f93ff5c9 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -1,6 +1,7 @@ /* sysutils.c - system helpers * Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, * 2007, 2008 Free Software Foundation, Inc. + * Copyright (C) 2013 Werner Koch * * This file is part of GnuPG. * @@ -495,3 +496,59 @@ gnupg_allow_set_foregound_window (pid_t pid) (unsigned long)pid, w32_strerror (-1)); #endif } + + +#ifdef HAVE_W32_SYSTEM +/* Return the user's security identifier from the current process. */ +PSID +w32_get_user_sid (void) +{ + int okay = 0; + HANDLE proc = NULL; + HANDLE token = NULL; + TOKEN_USER *user = NULL; + PSID sid = NULL; + DWORD tokenlen, sidlen; + + proc = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); + if (!proc) + goto leave; + + if (!OpenProcessToken (proc, TOKEN_QUERY, &token)) + goto leave; + + if (!GetTokenInformation (token, TokenUser, NULL, 0, &tokenlen) + && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + goto leave; + + user = xtrymalloc (tokenlen); + if (!user) + goto leave; + + if (!GetTokenInformation (token, TokenUser, user, tokenlen, &tokenlen)) + goto leave; + if (!IsValidSid (user->User.Sid)) + goto leave; + sidlen = GetLengthSid (user->User.Sid); + sid = xtrymalloc (sidlen); + if (!sid) + goto leave; + if (!CopySid (sidlen, sid, user->User.Sid)) + goto leave; + okay = 1; + + leave: + xfree (user); + if (token) + CloseHandle (token); + if (proc) + CloseHandle (proc); + + if (!okay) + { + xfree (sid); + sid = NULL; + } + return sid; +} +#endif /*HAVE_W32_SYSTEM*/ diff --git a/common/sysutils.h b/common/sysutils.h index fd4340f3d..0a05e2b3e 100644 --- a/common/sysutils.h +++ b/common/sysutils.h @@ -51,8 +51,9 @@ void gnupg_allow_set_foregound_window (pid_t pid); #ifdef HAVE_W32_SYSTEM +void *w32_get_user_sid (void); -#include "../jnlib/w32help.h" +# include "../jnlib/w32help.h" #endif /*HAVE_W32_SYSTEM*/ diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 49c082ba2..72e7134df 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -482,7 +482,7 @@ static gc_option_t gc_options_gpg_agent[] = GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "Configuration", - GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, "gnupg", N_("Options controlling the configuration") }, { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, "gnupg", "|FILE|read options from FILE", @@ -490,6 +490,12 @@ static gc_option_t gc_options_gpg_agent[] = { "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, "gnupg", "do not use the SCdaemon", GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, + { "enable-ssh-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "gnupg", "enable ssh support", + GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, + { "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "gnupg", "enable putty support", + GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, { "Debug", GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,