/* * Key exchange * * Client <-> Server * * -> "CKEY " || host || " " || ts || Cpk || Hk(ts || Cpk) * <- "SKEY " || Spk || Hk(Spk || Hk(ts || Cpk)) * -> "CACK " || Hk("CACK" || Hk(Spk || Hk(ts || Cpk))) * <- "FLAGS " || flags || " " || Hk(flags || Hk("CACK" || Hk(Spk || Hk(ts || Cpk)))) * * session_key = Hk(DH) * * ts: current timestamp, 4 big-endian bytes * (Cpk, Csk): client public/secret ephemeral key pair * (Spk, Ssk): server public/secret ephemeral key pair * Hk: keyed hash function, key derived from the PSK * * DH function: Curve25519 * Keyed hash function: Blake2b * KDF: Scrypt */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_NETINET_IN_H #include #endif #ifdef HAVE_NETINET_TCP_H #include #endif #ifdef HAVE_ARPA_INET_H #include #endif #include #include "vtun.h" #include "lib.h" #include "lock.h" #include "auth.h" static int derive_key(struct vtun_host *host) { unsigned char salt[crypto_pwhash_scryptsalsa208sha256_SALTBYTES]; int ret = -1; size_t bin_len; if (host->akey != NULL) { return 0; } if ((host->akey = sodium_malloc(crypto_generichash_KEYBYTES)) == NULL) { return -1; } memset(salt, 0xd1, sizeof salt); if (crypto_pwhash_scryptsalsa208sha256(host->akey, HOST_KEYBYTES, host->passwd, strlen(host->passwd), salt, crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE, crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE) == 0) { ret = 0; } sodium_memzero(host->passwd, strlen(host->passwd)); free(host->passwd); host->passwd = NULL; vtun_syslog(LOG_DEBUG, "Key ready for host [%s]", host->host); return ret; } /* * Functions to convert binary flags to character string. * string format: * C - compression, S - speed for shaper and so on. */ static char *bf2cf(struct vtun_host *host) { static char str[20], * ptr = str; *(ptr++) = '<'; switch (host->flags & VTUN_PROT_MASK) { case VTUN_TCP: *(ptr++) = 'T'; break; case VTUN_UDP: *(ptr++) = 'U'; break; } switch (host->flags & VTUN_TYPE_MASK) { case VTUN_TTY: *(ptr++) = 't'; break; case VTUN_PIPE: *(ptr++) = 'p'; break; case VTUN_ETHER: *(ptr++) = 'e'; break; case VTUN_TUN: *(ptr++) = 'u'; break; } if ((host->flags & VTUN_SHAPE) /* && host->spd_in */) ptr += sprintf(ptr, "S%d", host->spd_in); if (host->flags & VTUN_ZLIB) ptr += sprintf(ptr, "C%d", host->zlevel); if (host->flags & VTUN_LZO) ptr += sprintf(ptr, "L%d", host->zlevel); if (host->flags & VTUN_KEEP_ALIVE) *(ptr++) = 'K'; if (host->flags & VTUN_ENCRYPT) { ptr += sprintf(ptr, "E%d", host->cipher); } strcat(ptr, ">"); return str; } /* * return 0 on success, otherwise -1 * Example: FLAGS: */ static int cf2bf(char *str, struct vtun_host *host) { char *ptr, *p; int s; if (strlen(str) >= 20) { return -1; } if ((ptr = strchr(str, '<'))) { vtun_syslog(LOG_DEBUG, "Remote Server sends %s.", ptr); ptr++; while (*ptr) { switch (*ptr++) { case 't': host->flags |= VTUN_TTY; break; case 'p': host->flags |= VTUN_PIPE; break; case 'e': host->flags |= VTUN_ETHER; break; case 'u': host->flags |= VTUN_TUN; break; case 'U': host->flags &= ~VTUN_PROT_MASK; host->flags |= VTUN_UDP; break; case 'T': host->flags &= ~VTUN_PROT_MASK; host->flags |= VTUN_TCP; break; case 'K': host->flags |= VTUN_KEEP_ALIVE; break; case 'C': if ((s = strtol(ptr, &p, 10)) == ERANGE || ptr == p) { return -1; } host->flags |= VTUN_ZLIB; host->zlevel = s; ptr = p; break; case 'L': if ((s = strtol(ptr, &p, 10)) == ERANGE || ptr == p) { return -1; } host->flags |= VTUN_LZO; host->zlevel = s; ptr = p; break; case 'E': /* new form is 'E10', old form is 'E', so remove the ptr==p check */ if ((s = strtol(ptr, &p, 10)) == ERANGE) { vtun_syslog(LOG_ERR, "Garbled encryption method. Bailing out."); return -1; } host->flags |= VTUN_ENCRYPT; host->cipher = s; ptr = p; break; case 'S': if ((s = strtol(ptr, &p, 10)) == ERANGE || ptr == p) { return -1; } if (s > 0) { host->flags |= VTUN_SHAPE; host->spd_out = s; } ptr = p; break; case 'F': /* reserved for Feature transmit */ break; case '>': return 0; default: return -1; } } } return -1; } /* Authentication (Server side) */ struct vtun_host *auth_server(int fd) { char buf[VTUN_MESG_SIZE], *str1, *str2, *str3; unsigned char cack[crypto_generichash_BYTES]; unsigned char client_pk[crypto_scalarmult_BYTES]; unsigned char server_pk[crypto_scalarmult_BYTES]; unsigned char server_sk[crypto_scalarmult_SCALARBYTES]; unsigned char ckey[4 + crypto_scalarmult_BYTES + crypto_generichash_BYTES]; unsigned char dhkey[crypto_scalarmult_BYTES]; unsigned char skey[crypto_scalarmult_BYTES + crypto_generichash_BYTES]; char skey_hex[2 * (crypto_scalarmult_BYTES + crypto_generichash_BYTES) + 1]; unsigned char hash[crypto_generichash_BYTES]; unsigned char flhash[crypto_generichash_BYTES]; char flhash_hex[2 * crypto_generichash_BYTES + 1]; struct vtun_host *host = NULL; char *flags; char *host_name = NULL; crypto_generichash_state st; time_t client_now; size_t bin_len; int stage; int success = 0; set_title("authentication"); print_p(fd, "VTUN server ver %s\n", VTUN_VER); stage = ST_STEP2; while (readn_t(fd, buf, VTUN_MESG_SIZE, vtun.timeout) > 0) { buf[sizeof(buf) - 1] = '\0'; strtok(buf, "\r\n"); if (!(str1 = strtok(buf, " :"))) { break; } if (!(str2 = strtok(NULL, " :"))) { break; } switch (stage) { case ST_STEP2: if (!strcmp(str1, "CKEY")) { if (!(str3 = strtok(NULL, " \t"))) { break; } sodium_hex2bin(ckey, sizeof ckey, str3, strlen(str3), "", &bin_len, NULL); if (bin_len != sizeof ckey) { break; } client_now = ((time_t) ckey[0]) << 24 | ((time_t) ckey[1]) << 16 | ((time_t) ckey[2]) << 9 | ((time_t) ckey[3]); (void) client_now; host_name = str2; if ((host = find_host(host_name)) == NULL || derive_key(host) != 0) { break; } crypto_generichash(hash, sizeof hash, ckey, 4 + crypto_scalarmult_BYTES, host->akey, crypto_generichash_KEYBYTES); if (sodium_memcmp(hash, ckey + 4 + crypto_scalarmult_BYTES, sizeof hash) != 0) { break; } memcpy(client_pk, ckey + 4, sizeof client_pk); randombytes_buf(server_sk, crypto_scalarmult_SCALARBYTES); crypto_scalarmult_base(server_pk, server_sk); memcpy(skey, server_pk, sizeof server_pk); memcpy(skey + crypto_scalarmult_BYTES, hash, sizeof hash); crypto_generichash(skey + crypto_scalarmult_BYTES, crypto_scalarmult_BYTES, skey, sizeof skey, host->akey, crypto_generichash_KEYBYTES); sodium_bin2hex(skey_hex, sizeof skey_hex, skey, sizeof skey); print_p(fd, "SKEY: %s\n", skey_hex); stage = ST_STEP3; continue; } break; case ST_STEP3: if (!strcmp(str1, "CACK")) { sodium_hex2bin(cack, sizeof cack, str2, strlen(str2), "", &bin_len, NULL); if (bin_len != sizeof cack) { break; } crypto_generichash_init(&st, host->akey, crypto_generichash_KEYBYTES, crypto_generichash_BYTES); crypto_generichash_update(&st, (const unsigned char *) "CACK", 4); crypto_generichash_update(&st, skey, sizeof skey); crypto_generichash_final(&st, hash, sizeof hash); if (sodium_memcmp(hash, cack, sizeof hash) != 0) { break; } /* Lock host */ if (lock_host(host) < 0) { /* Multiple connections are denied */ host = NULL; break; } flags = bf2cf(host); crypto_generichash_init(&st, host->akey, crypto_generichash_KEYBYTES, crypto_generichash_BYTES); crypto_generichash_update(&st, (const unsigned char *) flags, strlen(flags)); crypto_generichash_update(&st, cack, sizeof cack); crypto_generichash_final(&st, flhash, sizeof flhash); sodium_bin2hex(flhash_hex, sizeof flhash_hex, flhash, sizeof flhash); print_p(fd, "FLAGS: %s %s\n", flags, flhash_hex); crypto_scalarmult(dhkey, server_sk, client_pk); sodium_memzero(server_sk, sizeof server_sk); if ((host->key = sodium_malloc(HOST_KEYBYTES)) == NULL) { abort(); } crypto_generichash(host->key, HOST_KEYBYTES, dhkey, sizeof dhkey, host->akey, crypto_generichash_KEYBYTES); sodium_memzero(dhkey, sizeof dhkey); success = 1; } break; } break; } if (success == 0) { print_p(fd, "ERR\n"); host = NULL; } return host; } /* Authentication (Client side) */ int auth_client(int fd, struct vtun_host *host) { char buf[VTUN_MESG_SIZE], *str1, *str2, *str3; unsigned char cack[crypto_generichash_BYTES]; char cack_hex[2 * crypto_generichash_BYTES + 1]; unsigned char flhash[crypto_generichash_BYTES]; unsigned char hash[crypto_generichash_BYTES]; unsigned char client_pk[crypto_scalarmult_BYTES]; unsigned char client_sk[crypto_scalarmult_SCALARBYTES]; unsigned char server_pk[crypto_scalarmult_BYTES]; unsigned char dhkey[crypto_scalarmult_BYTES]; unsigned char ckey[4 + crypto_scalarmult_BYTES + crypto_generichash_BYTES]; char ckey_hex[2 * (4 + crypto_scalarmult_BYTES + crypto_generichash_BYTES) + 1]; unsigned char skey[crypto_scalarmult_BYTES + crypto_generichash_BYTES]; crypto_generichash_state st; time_t now; size_t bin_len; int stage; int success = 0; stage = ST_INIT; if (derive_key(host) != 0) { return 0; } while (readn_t(fd, buf, VTUN_MESG_SIZE, vtun.timeout) > 0) { buf[sizeof(buf) - 1] = '\0'; strtok(buf, "\r\n"); if (!(str1 = strtok(buf, " :"))) { break; } if (!(str2 = strtok(NULL, " :"))) { break; } switch (stage) { case ST_INIT: if (!strcmp(str1, "VTUN")) { now = time(NULL); randombytes_buf(client_sk, crypto_scalarmult_SCALARBYTES); crypto_scalarmult_base(client_pk, client_sk); ckey[0] = (unsigned char)(now >> 24); ckey[1] = (unsigned char)(now >> 16); ckey[2] = (unsigned char)(now >> 8); ckey[3] = (unsigned char)(now); memcpy(ckey + 4, client_pk, crypto_scalarmult_BYTES); crypto_generichash(ckey + 4 + crypto_scalarmult_BYTES, crypto_generichash_BYTES, ckey, 4 + crypto_scalarmult_BYTES, host->akey, crypto_generichash_KEYBYTES); sodium_bin2hex(ckey_hex, sizeof ckey_hex, ckey, sizeof ckey); stage = ST_STEP2; print_p(fd, "CKEY: %s %s\n", host->host, ckey_hex); continue; } break; case ST_STEP2: if (!strcmp(str1, "SKEY")) { sodium_hex2bin(skey, sizeof skey, str2, strlen(str2), "", &bin_len, NULL); if (bin_len != sizeof skey) { break; } crypto_generichash_init(&st, host->akey, crypto_generichash_KEYBYTES, crypto_generichash_BYTES); crypto_generichash_update(&st, skey, crypto_scalarmult_BYTES); crypto_generichash_update(&st, ckey + 4 + crypto_scalarmult_BYTES, crypto_generichash_BYTES); crypto_generichash_final(&st, hash, sizeof hash); if (sodium_memcmp(hash, skey + crypto_scalarmult_BYTES, sizeof hash) != 0) { break; } memcpy(server_pk, skey, sizeof server_pk); crypto_generichash_init(&st, host->akey, crypto_generichash_KEYBYTES, crypto_generichash_BYTES); crypto_generichash_update(&st, (const unsigned char *) "CACK", 4); crypto_generichash_update(&st, skey, sizeof skey); crypto_generichash_final(&st, cack, sizeof cack); sodium_bin2hex(cack_hex, sizeof cack_hex, cack, sizeof cack); print_p(fd, "CACK: %s\n", cack_hex); stage = ST_STEP3; continue; } break; case ST_STEP3: if (!strcmp(str1, "FLAGS")) { if (!(str3 = strtok(NULL, " \t"))) { break; } sodium_hex2bin(flhash, sizeof flhash, str3, strlen(str3), "", &bin_len, NULL); if (bin_len != sizeof flhash) { break; } if (cf2bf(str2, host) != 0) { break; } crypto_generichash_init(&st, host->akey, crypto_generichash_KEYBYTES, crypto_generichash_BYTES); crypto_generichash_update(&st, (const unsigned char *) str2, strlen(str2)); crypto_generichash_update(&st, cack, sizeof cack); crypto_generichash_final(&st, hash, sizeof hash); if (sodium_memcmp(hash, flhash, sizeof hash) != 0) { break; } crypto_scalarmult(dhkey, client_sk, server_pk); sodium_memzero(client_sk, sizeof client_sk); if ((host->key = sodium_malloc(HOST_KEYBYTES)) == NULL) { abort(); } crypto_generichash(host->key, HOST_KEYBYTES, dhkey, sizeof dhkey, host->akey, crypto_generichash_KEYBYTES); sodium_memzero(dhkey, sizeof dhkey); success = 1; } break; } break; } return success; }