2 * Windows implementation of SSH connection-sharing IPC setup.
8 #if !defined NO_SECURITY
10 #define DEFINE_PLUG_METHOD_MACROS
22 * The hack I use to build for Coverity scanning, using winegcc and
23 * Makefile.mgw, didn't provide some defines in wincrypt.h last time I
24 * looked. Therefore, define them myself here, but enclosed in #ifdef
25 * COVERITY to ensure I don't make up random nonsense values for any
28 #ifndef CRYPTPROTECTMEMORY_BLOCK_SIZE
29 #define CRYPTPROTECTMEMORY_BLOCK_SIZE 16
31 #ifndef CRYPTPROTECTMEMORY_CROSS_PROCESS
32 #define CRYPTPROTECTMEMORY_CROSS_PROCESS 1
36 #define CONNSHARE_PIPE_PREFIX "\\\\.\\pipe\\putty-connshare"
37 #define CONNSHARE_MUTEX_PREFIX "Local\\putty-connshare-mutex"
39 static char *obfuscate_name(const char *realname)
42 * Windows's named pipes all live in the same namespace, so one
43 * user can see what pipes another user has open. This is an
44 * undesirable privacy leak and in particular permits one user to
45 * know what username@host another user is SSHing to, so we
46 * protect that information by using CryptProtectMemory (which
47 * uses a key built in to each user's account).
52 unsigned char lenbuf[4];
53 unsigned char digest[32];
57 cryptlen = strlen(realname) + 1;
58 cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1;
59 cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE;
60 cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE;
62 cryptdata = snewn(cryptlen, char);
63 memset(cryptdata, 0, cryptlen);
64 strcpy(cryptdata, realname);
67 * CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to
68 * use the same key in all processes with this user id, meaning
69 * that the next PuTTY process calling this function with the same
70 * input will get the same data.
72 * (Contrast with CryptProtectData, which invents a new session
73 * key every time since its API permits returning more data than
74 * was input, so calling _that_ and hashing the output would not
77 * We don't worry too much if this doesn't work for some reason.
78 * Omitting this step still has _some_ privacy value (in that
79 * another user can test-hash things to confirm guesses as to
80 * where you might be connecting to, but cannot invert SHA-256 in
81 * the absence of any plausible guess). So we don't abort if we
82 * can't call CryptProtectMemory at all, or if it fails.
85 p_CryptProtectMemory(cryptdata, cryptlen,
86 CRYPTPROTECTMEMORY_CROSS_PROCESS);
89 * We don't want to give away the length of the hostname either,
90 * so having got it back out of CryptProtectMemory we now hash it.
93 PUT_32BIT_MSB_FIRST(lenbuf, cryptlen);
94 SHA256_Bytes(&sha, lenbuf, 4);
95 SHA256_Bytes(&sha, cryptdata, cryptlen);
96 SHA256_Final(&sha, digest);
101 * Finally, make printable.
103 for (i = 0; i < 32; i++) {
104 sprintf(retbuf + 2*i, "%02x", digest[i]);
105 /* the last of those will also write the trailing NUL */
108 return dupstr(retbuf);
111 static char *make_name(const char *prefix, const char *name)
113 char *username, *retname;
115 username = get_username();
116 retname = dupprintf("%s.%s.%s", prefix, username, name);
122 Socket new_named_pipe_client(const char *pipename, Plug plug);
123 Socket new_named_pipe_listener(const char *pipename, Plug plug);
125 int platform_ssh_share(const char *pi_name, Conf *conf,
126 Plug downplug, Plug upplug, Socket *sock,
127 char **logtext, char **ds_err, char **us_err,
128 int can_upstream, int can_downstream)
130 char *name, *mutexname, *pipename;
133 PSECURITY_DESCRIPTOR psd;
137 * Transform the platform-independent version of the connection
138 * identifier into the obfuscated version we'll use for our
139 * Windows named pipe and mutex. A side effect of doing this is
140 * that it also eliminates any characters illegal in Windows pipe
143 name = obfuscate_name(pi_name);
145 *logtext = dupprintf("Unable to call CryptProtectMemory: %s",
146 win_strerror(GetLastError()));
151 * Make a mutex name out of the connection identifier, and lock it
152 * while we decide whether to be upstream or downstream.
155 SECURITY_ATTRIBUTES sa;
157 mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name);
158 if (!make_private_security_descriptor(MUTEX_ALL_ACCESS,
159 &psd, &acl, logtext)) {
165 memset(&sa, 0, sizeof(sa));
166 sa.nLength = sizeof(sa);
167 sa.lpSecurityDescriptor = psd;
168 sa.bInheritHandle = FALSE;
170 mutex = CreateMutex(&sa, FALSE, mutexname);
173 *logtext = dupprintf("CreateMutex(\"%s\") failed: %s",
174 mutexname, win_strerror(GetLastError()));
186 WaitForSingleObject(mutex, INFINITE);
189 pipename = make_name(CONNSHARE_PIPE_PREFIX, name);
193 if (can_downstream) {
194 retsock = new_named_pipe_client(pipename, downplug);
195 if (sk_socket_error(retsock) == NULL) {
202 return SHARE_DOWNSTREAM;
205 *ds_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock));
210 retsock = new_named_pipe_listener(pipename, upplug);
211 if (sk_socket_error(retsock) == NULL) {
218 return SHARE_UPSTREAM;
221 *us_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock));
225 /* One of the above clauses ought to have happened. */
226 assert(*logtext || *ds_err || *us_err);
235 void platform_ssh_share_cleanup(const char *name)
239 #else /* !defined NO_SECURITY */
243 #endif /* !defined NO_SECURITY */