Darren Tucker | a627d42 | 2013-06-02 07:31:17 +1000 | [diff] [blame] | 1 | /* $OpenBSD: roaming_client.c,v 1.5 2013/05/17 00:13:14 djm Exp $ */ |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 2 | /* |
| 3 | * Copyright (c) 2004-2009 AppGate Network Security AB |
| 4 | * |
| 5 | * Permission to use, copy, modify, and distribute this software for any |
| 6 | * purpose with or without fee is hereby granted, provided that the above |
| 7 | * copyright notice and this permission notice appear in all copies. |
| 8 | * |
| 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 16 | */ |
| 17 | |
Darren Tucker | cb5a1b6 | 2010-01-08 20:09:01 +1100 | [diff] [blame] | 18 | #include "includes.h" |
| 19 | |
Tim Rice | f377567 | 2010-01-16 16:48:39 -0800 | [diff] [blame] | 20 | #include "openbsd-compat/sys-queue.h" |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 21 | #include <sys/types.h> |
| 22 | #include <sys/socket.h> |
| 23 | |
Darren Tucker | 11b5c07 | 2010-01-09 16:40:48 +1100 | [diff] [blame] | 24 | #ifdef HAVE_INTTYPES_H |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 25 | #include <inttypes.h> |
Darren Tucker | 11b5c07 | 2010-01-09 16:40:48 +1100 | [diff] [blame] | 26 | #endif |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 27 | #include <signal.h> |
| 28 | #include <string.h> |
| 29 | #include <unistd.h> |
| 30 | |
| 31 | #include <openssl/crypto.h> |
| 32 | #include <openssl/sha.h> |
| 33 | |
| 34 | #include "xmalloc.h" |
| 35 | #include "buffer.h" |
| 36 | #include "channels.h" |
| 37 | #include "cipher.h" |
| 38 | #include "dispatch.h" |
| 39 | #include "clientloop.h" |
| 40 | #include "log.h" |
| 41 | #include "match.h" |
| 42 | #include "misc.h" |
| 43 | #include "packet.h" |
| 44 | #include "ssh.h" |
| 45 | #include "key.h" |
| 46 | #include "kex.h" |
| 47 | #include "readconf.h" |
| 48 | #include "roaming.h" |
| 49 | #include "ssh2.h" |
| 50 | #include "sshconnect.h" |
| 51 | |
| 52 | /* import */ |
| 53 | extern Options options; |
| 54 | extern char *host; |
| 55 | extern struct sockaddr_storage hostaddr; |
| 56 | extern int session_resumed; |
| 57 | |
| 58 | static u_int32_t roaming_id; |
| 59 | static u_int64_t cookie; |
| 60 | static u_int64_t lastseenchall; |
| 61 | static u_int64_t key1, key2, oldkey1, oldkey2; |
| 62 | |
| 63 | void |
| 64 | roaming_reply(int type, u_int32_t seq, void *ctxt) |
| 65 | { |
| 66 | if (type == SSH2_MSG_REQUEST_FAILURE) { |
| 67 | logit("Server denied roaming"); |
| 68 | return; |
| 69 | } |
| 70 | verbose("Roaming enabled"); |
| 71 | roaming_id = packet_get_int(); |
| 72 | cookie = packet_get_int64(); |
| 73 | key1 = oldkey1 = packet_get_int64(); |
| 74 | key2 = oldkey2 = packet_get_int64(); |
Damien Miller | 8ed4de8 | 2011-12-19 10:52:50 +1100 | [diff] [blame] | 75 | set_out_buffer_size(packet_get_int() + get_snd_buf_size()); |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 76 | roaming_enabled = 1; |
| 77 | } |
| 78 | |
| 79 | void |
| 80 | request_roaming(void) |
| 81 | { |
| 82 | packet_start(SSH2_MSG_GLOBAL_REQUEST); |
| 83 | packet_put_cstring(ROAMING_REQUEST); |
| 84 | packet_put_char(1); |
| 85 | packet_put_int(get_recv_buf_size()); |
| 86 | packet_send(); |
| 87 | client_register_global_confirm(roaming_reply, NULL); |
| 88 | } |
| 89 | |
| 90 | static void |
| 91 | roaming_auth_required(void) |
| 92 | { |
| 93 | u_char digest[SHA_DIGEST_LENGTH]; |
| 94 | EVP_MD_CTX md; |
| 95 | Buffer b; |
| 96 | const EVP_MD *evp_md = EVP_sha1(); |
| 97 | u_int64_t chall, oldchall; |
| 98 | |
| 99 | chall = packet_get_int64(); |
| 100 | oldchall = packet_get_int64(); |
| 101 | if (oldchall != lastseenchall) { |
| 102 | key1 = oldkey1; |
| 103 | key2 = oldkey2; |
| 104 | } |
| 105 | lastseenchall = chall; |
| 106 | |
| 107 | buffer_init(&b); |
| 108 | buffer_put_int64(&b, cookie); |
| 109 | buffer_put_int64(&b, chall); |
| 110 | EVP_DigestInit(&md, evp_md); |
| 111 | EVP_DigestUpdate(&md, buffer_ptr(&b), buffer_len(&b)); |
| 112 | EVP_DigestFinal(&md, digest, NULL); |
| 113 | buffer_free(&b); |
| 114 | |
| 115 | packet_start(SSH2_MSG_KEX_ROAMING_AUTH); |
| 116 | packet_put_int64(key1 ^ get_recv_bytes()); |
| 117 | packet_put_raw(digest, sizeof(digest)); |
| 118 | packet_send(); |
| 119 | |
| 120 | oldkey1 = key1; |
| 121 | oldkey2 = key2; |
| 122 | calculate_new_key(&key1, cookie, chall); |
| 123 | calculate_new_key(&key2, cookie, chall); |
| 124 | |
Tim Rice | f377567 | 2010-01-16 16:48:39 -0800 | [diff] [blame] | 125 | debug("Received %llu bytes", (unsigned long long)get_recv_bytes()); |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 126 | debug("Sent roaming_auth packet"); |
| 127 | } |
| 128 | |
| 129 | int |
| 130 | resume_kex(void) |
| 131 | { |
| 132 | /* |
| 133 | * This should not happen - if the client sends the kex method |
| 134 | * resume@appgate.com then the kex is done in roaming_resume(). |
| 135 | */ |
| 136 | return 1; |
| 137 | } |
| 138 | |
| 139 | static int |
| 140 | roaming_resume(void) |
| 141 | { |
| 142 | u_int64_t recv_bytes; |
| 143 | char *str = NULL, *kexlist = NULL, *c; |
| 144 | int i, type; |
| 145 | int timeout_ms = options.connection_timeout * 1000; |
| 146 | u_int len; |
| 147 | u_int32_t rnd = 0; |
| 148 | |
| 149 | resume_in_progress = 1; |
| 150 | |
| 151 | /* Exchange banners */ |
| 152 | ssh_exchange_identification(timeout_ms); |
| 153 | packet_set_nonblocking(); |
| 154 | |
| 155 | /* Send a kexinit message with resume@appgate.com as only kex algo */ |
| 156 | packet_start(SSH2_MSG_KEXINIT); |
| 157 | for (i = 0; i < KEX_COOKIE_LEN; i++) { |
| 158 | if (i % 4 == 0) |
| 159 | rnd = arc4random(); |
| 160 | packet_put_char(rnd & 0xff); |
| 161 | rnd >>= 8; |
| 162 | } |
| 163 | packet_put_cstring(KEX_RESUME); |
| 164 | for (i = 1; i < PROPOSAL_MAX; i++) { |
| 165 | /* kex algorithm added so start with i=1 and not 0 */ |
| 166 | packet_put_cstring(""); /* Not used when we resume */ |
| 167 | } |
| 168 | packet_put_char(1); /* first kex_packet follows */ |
| 169 | packet_put_int(0); /* reserved */ |
| 170 | packet_send(); |
| 171 | |
| 172 | /* Assume that resume@appgate.com will be accepted */ |
| 173 | packet_start(SSH2_MSG_KEX_ROAMING_RESUME); |
| 174 | packet_put_int(roaming_id); |
| 175 | packet_send(); |
| 176 | |
| 177 | /* Read the server's kexinit and check for resume@appgate.com */ |
| 178 | if ((type = packet_read()) != SSH2_MSG_KEXINIT) { |
| 179 | debug("expected kexinit on resume, got %d", type); |
| 180 | goto fail; |
| 181 | } |
| 182 | for (i = 0; i < KEX_COOKIE_LEN; i++) |
| 183 | (void)packet_get_char(); |
| 184 | kexlist = packet_get_string(&len); |
| 185 | if (!kexlist |
| 186 | || (str = match_list(KEX_RESUME, kexlist, NULL)) == NULL) { |
| 187 | debug("server doesn't allow resume"); |
| 188 | goto fail; |
| 189 | } |
Darren Tucker | a627d42 | 2013-06-02 07:31:17 +1000 | [diff] [blame] | 190 | free(str); |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 191 | for (i = 1; i < PROPOSAL_MAX; i++) { |
| 192 | /* kex algorithm taken care of so start with i=1 and not 0 */ |
Darren Tucker | a627d42 | 2013-06-02 07:31:17 +1000 | [diff] [blame] | 193 | free(packet_get_string(&len)); |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 194 | } |
| 195 | i = packet_get_char(); /* first_kex_packet_follows */ |
| 196 | if (i && (c = strchr(kexlist, ','))) |
| 197 | *c = 0; |
| 198 | if (i && strcmp(kexlist, KEX_RESUME)) { |
| 199 | debug("server's kex guess (%s) was wrong, skipping", kexlist); |
| 200 | (void)packet_read(); /* Wrong guess - discard packet */ |
| 201 | } |
| 202 | |
| 203 | /* |
| 204 | * Read the ROAMING_AUTH_REQUIRED challenge from the server and |
| 205 | * send ROAMING_AUTH |
| 206 | */ |
| 207 | if ((type = packet_read()) != SSH2_MSG_KEX_ROAMING_AUTH_REQUIRED) { |
| 208 | debug("expected roaming_auth_required, got %d", type); |
| 209 | goto fail; |
| 210 | } |
| 211 | roaming_auth_required(); |
| 212 | |
| 213 | /* Read ROAMING_AUTH_OK from the server */ |
| 214 | if ((type = packet_read()) != SSH2_MSG_KEX_ROAMING_AUTH_OK) { |
| 215 | debug("expected roaming_auth_ok, got %d", type); |
| 216 | goto fail; |
| 217 | } |
| 218 | recv_bytes = packet_get_int64() ^ oldkey2; |
Tim Rice | f377567 | 2010-01-16 16:48:39 -0800 | [diff] [blame] | 219 | debug("Peer received %llu bytes", (unsigned long long)recv_bytes); |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 220 | resend_bytes(packet_get_connection_out(), &recv_bytes); |
| 221 | |
| 222 | resume_in_progress = 0; |
| 223 | |
| 224 | session_resumed = 1; /* Tell clientloop */ |
| 225 | |
| 226 | return 0; |
| 227 | |
| 228 | fail: |
Darren Tucker | a627d42 | 2013-06-02 07:31:17 +1000 | [diff] [blame] | 229 | free(kexlist); |
Darren Tucker | 8cbd403 | 2010-01-08 19:13:25 +1100 | [diff] [blame] | 230 | if (packet_get_connection_in() == packet_get_connection_out()) |
| 231 | close(packet_get_connection_in()); |
| 232 | else { |
| 233 | close(packet_get_connection_in()); |
| 234 | close(packet_get_connection_out()); |
| 235 | } |
| 236 | return 1; |
| 237 | } |
| 238 | |
| 239 | int |
| 240 | wait_for_roaming_reconnect(void) |
| 241 | { |
| 242 | static int reenter_guard = 0; |
| 243 | int timeout_ms = options.connection_timeout * 1000; |
| 244 | int c; |
| 245 | |
| 246 | if (reenter_guard != 0) |
| 247 | fatal("Server refused resume, roaming timeout may be exceeded"); |
| 248 | reenter_guard = 1; |
| 249 | |
| 250 | fprintf(stderr, "[connection suspended, press return to resume]"); |
| 251 | fflush(stderr); |
| 252 | packet_backup_state(); |
| 253 | /* TODO Perhaps we should read from tty here */ |
| 254 | while ((c = fgetc(stdin)) != EOF) { |
| 255 | if (c == 'Z' - 64) { |
| 256 | kill(getpid(), SIGTSTP); |
| 257 | continue; |
| 258 | } |
| 259 | if (c != '\n' && c != '\r') |
| 260 | continue; |
| 261 | |
| 262 | if (ssh_connect(host, &hostaddr, options.port, |
| 263 | options.address_family, 1, &timeout_ms, |
| 264 | options.tcp_keep_alive, options.use_privileged_port, |
| 265 | options.proxy_command) == 0 && roaming_resume() == 0) { |
| 266 | packet_restore_state(); |
| 267 | reenter_guard = 0; |
| 268 | fprintf(stderr, "[connection resumed]\n"); |
| 269 | fflush(stderr); |
| 270 | return 0; |
| 271 | } |
| 272 | |
| 273 | fprintf(stderr, "[reconnect failed, press return to retry]"); |
| 274 | fflush(stderr); |
| 275 | } |
| 276 | fprintf(stderr, "[exiting]\n"); |
| 277 | fflush(stderr); |
| 278 | exit(0); |
| 279 | } |