diff --git a/esp.c b/esp.c index d742416d..d6736757 100644 --- a/esp.c +++ b/esp.c @@ -196,7 +196,7 @@ int esp_setup(struct openconnect_info *vpninfo, int dtls_attempt_period) if (fd < 0) return fd; - print_esp_keys(vpninfo, _("incoming"), &vpninfo->esp_in); + print_esp_keys(vpninfo, _("incoming"), &vpninfo->esp_in[vpninfo->current_esp_in]); print_esp_keys(vpninfo, _("outgoing"), &vpninfo->esp_out); /* We are not connected until we get an ESP packet back */ @@ -213,6 +213,8 @@ int esp_setup(struct openconnect_info *vpninfo, int dtls_attempt_period) int esp_mainloop(struct openconnect_info *vpninfo, int *timeout) { + struct esp *esp = &vpninfo->esp_in[vpninfo->current_esp_in]; + struct esp *old_esp = &vpninfo->esp_in[vpninfo->current_esp_in ^ 1]; int work_done = 0; int ret; @@ -242,8 +244,22 @@ int esp_mainloop(struct openconnect_info *vpninfo, int *timeout) len -= sizeof(pkt->esp) + 12; pkt->len = len; - if (decrypt_esp_packet(vpninfo, pkt)) + if (pkt->esp.spi == esp->spi) { + if (decrypt_esp_packet(vpninfo, esp, pkt)) + continue; + } else if (pkt->esp.spi == old_esp->spi && + ntohl(pkt->esp.seq) + esp->seq < vpninfo->old_esp_maxseq) { + vpn_progress(vpninfo, PRG_TRACE, + _("Consider SPI 0x%x, seq %u against outgoing ESP setup\n"), + ntohl(old_esp->spi), ntohl(pkt->esp.seq)); + if (decrypt_esp_packet(vpninfo, old_esp, pkt)) + continue; + } else { + vpn_progress(vpninfo, PRG_DEBUG, + _("Received ESP packet with invalid SPI 0x%08x\n"), + ntohl(pkt->esp.spi)); continue; + } if (pkt->data[len - 1] != 0x04 && pkt->data[len - 1] != 0x29) { /* 0x05 is LZO compressed. */ diff --git a/gnutls-esp.c b/gnutls-esp.c index 5df6b729..d6effed2 100644 --- a/gnutls-esp.c +++ b/gnutls-esp.c @@ -45,14 +45,9 @@ static int init_esp_ciphers(struct openconnect_info *vpninfo, struct esp *esp, gnutls_datum_t enc_key; int err; - /* ∄ gnutls_cipher_get_key_size() */ - if (encalg == GNUTLS_CIPHER_AES_128_CBC) - enc_key.size = 16; - else if (encalg == GNUTLS_CIPHER_AES_256_CBC) - enc_key.size = 32; - else - return -EINVAL; - + destroy_esp_ciphers(esp); + + enc_key.size = gnutls_cipher_get_key_size(encalg); enc_key.data = esp->secrets; err = gnutls_cipher_init(&esp->cipher, encalg, &enc_key, NULL); @@ -79,6 +74,7 @@ static int init_esp_ciphers(struct openconnect_info *vpninfo, struct esp *esp, int setup_esp_keys(struct openconnect_info *vpninfo) { + struct esp *esp_in; gnutls_mac_algorithm_t macalg; gnutls_cipher_algorithm_t encalg; int ret; @@ -110,8 +106,12 @@ int setup_esp_keys(struct openconnect_info *vpninfo) return -EINVAL; } - if ((ret = gnutls_rnd(GNUTLS_RND_NONCE, &vpninfo->esp_in.spi, sizeof(vpninfo->esp_in.spi))) || - (ret = gnutls_rnd(GNUTLS_RND_RANDOM, &vpninfo->esp_in.secrets, sizeof(vpninfo->esp_in.secrets)))) { + vpninfo->old_esp_maxseq = vpninfo->esp_in[vpninfo->current_esp_in].seq + 32; + vpninfo->current_esp_in ^= 1; + esp_in = &vpninfo->esp_in[vpninfo->current_esp_in]; + + if ((ret = gnutls_rnd(GNUTLS_RND_NONCE, &esp_in->spi, sizeof(esp_in->spi))) || + (ret = gnutls_rnd(GNUTLS_RND_RANDOM, &esp_in->secrets, sizeof(esp_in->secrets)))) { vpn_progress(vpninfo, PRG_ERR, _("Failed to generate random keys for ESP: %s\n"), gnutls_strerror(ret)); @@ -122,7 +122,7 @@ int setup_esp_keys(struct openconnect_info *vpninfo) if (ret) return ret; - ret = init_esp_ciphers(vpninfo, &vpninfo->esp_in, macalg, encalg); + ret = init_esp_ciphers(vpninfo, esp_in, macalg, encalg); if (ret) { destroy_esp_ciphers(&vpninfo->esp_out); return ret; @@ -135,20 +135,13 @@ int setup_esp_keys(struct openconnect_info *vpninfo) } /* pkt->len shall be the *payload* length. Omitting the header and the 12-byte HMAC */ -int decrypt_esp_packet(struct openconnect_info *vpninfo, struct pkt *pkt) +int decrypt_esp_packet(struct openconnect_info *vpninfo, struct esp *esp, struct pkt *pkt) { unsigned char hmac_buf[20]; int err; - if (pkt->esp.spi != vpninfo->esp_in.spi) { - vpn_progress(vpninfo, PRG_DEBUG, - _("Received ESP packet with invalid SPI 0x%08x\n"), - ntohl(pkt->esp.spi)); - return -EINVAL; - } - - gnutls_hmac(vpninfo->esp_in.hmac, &pkt->esp, sizeof(pkt->esp) + pkt->len); - gnutls_hmac_output(vpninfo->esp_in.hmac, hmac_buf); + gnutls_hmac(esp->hmac, &pkt->esp, sizeof(pkt->esp) + pkt->len); + gnutls_hmac_output(esp->hmac, hmac_buf); if (memcmp(hmac_buf, pkt->data + pkt->len, 12)) { vpn_progress(vpninfo, PRG_DEBUG, _("Received ESP packet with invalid HMAC\n")); @@ -159,13 +152,12 @@ int decrypt_esp_packet(struct openconnect_info *vpninfo, struct pkt *pkt) * should do th check anyway, but only warn instead of discarding * the packet? */ if (vpninfo->esp_replay_protect && - verify_packet_seqno(vpninfo, &vpninfo->esp_in, ntohl(pkt->esp.seq))) + verify_packet_seqno(vpninfo, esp, ntohl(pkt->esp.seq))) return -EINVAL; - gnutls_cipher_set_iv(vpninfo->esp_in.cipher, pkt->esp.iv, sizeof(pkt->esp.iv)); + gnutls_cipher_set_iv(esp->cipher, pkt->esp.iv, sizeof(pkt->esp.iv)); - err = gnutls_cipher_decrypt(vpninfo->esp_in.cipher, - pkt->data, pkt->len); + err = gnutls_cipher_decrypt(esp->cipher, pkt->data, pkt->len); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Decrypting ESP packet failed: %s\n"), diff --git a/library.c b/library.c index d74ba035..e6d4d45c 100644 --- a/library.c +++ b/library.c @@ -254,7 +254,8 @@ void openconnect_vpninfo_free(struct openconnect_info *vpninfo) #ifdef DTLS_GNUTLS gnutls_free(vpninfo->gnutls_dtls_cipher); #endif - destroy_esp_ciphers(&vpninfo->esp_in); + destroy_esp_ciphers(&vpninfo->esp_in[0]); + destroy_esp_ciphers(&vpninfo->esp_in[1]); destroy_esp_ciphers(&vpninfo->esp_out); free(vpninfo->dtls_addr); diff --git a/oncp.c b/oncp.c index ea1916e1..0bf750c0 100644 --- a/oncp.c +++ b/oncp.c @@ -1181,13 +1181,14 @@ int oncp_connect(struct openconnect_info *vpninfo) put_len16(reqbuf, kmp); if (!setup_esp_keys(vpninfo)) { + struct esp *esp = &vpninfo->esp_in[vpninfo->current_esp_in]; /* Since we'll want to do this in the oncp_mainloop too, where it's easier * *not* to have an oc_text_buf and build it up manually, and since it's * all fixed size and fairly simple anyway, just hard-code the packet */ buf_append_bytes(reqbuf, esp_kmp_hdr, sizeof(esp_kmp_hdr)); - buf_append_bytes(reqbuf, &vpninfo->esp_in.spi, sizeof(vpninfo->esp_in.spi)); + buf_append_bytes(reqbuf, &esp->spi, sizeof(esp->spi)); buf_append_bytes(reqbuf, esp_kmp_part2, sizeof(esp_kmp_part2)); - buf_append_bytes(reqbuf, vpninfo->esp_in.secrets, sizeof(vpninfo->esp_in.secrets)); + buf_append_bytes(reqbuf, &esp->secrets, sizeof(esp->secrets)); if (buf_error(reqbuf)) goto enomem; } @@ -1223,16 +1224,17 @@ static int oncp_receive_espkeys(struct openconnect_info *vpninfo, int len) ret = parse_conf_pkt(vpninfo, vpninfo->cstp_pkt->oncp_hdr + 2, len + 20, 301); if (!ret && !setup_esp_keys(vpninfo)) { + struct esp *esp = &vpninfo->esp_in[vpninfo->current_esp_in]; unsigned char *p = vpninfo->cstp_pkt->oncp_hdr + 2; memcpy(p, esp_kmp_hdr, sizeof(esp_kmp_hdr)); p += sizeof(esp_kmp_hdr); - memcpy(p, &vpninfo->esp_in.spi, sizeof(vpninfo->esp_in.spi)); - p += sizeof(vpninfo->esp_in.spi); + memcpy(p, &esp->spi, sizeof(esp->spi)); + p += sizeof(esp->spi); memcpy(p, esp_kmp_part2, sizeof(esp_kmp_part2)); p += sizeof(esp_kmp_part2); - memcpy(p, vpninfo->esp_in.secrets, sizeof(vpninfo->esp_in.secrets)); - p += sizeof(vpninfo->esp_in.secrets); + memcpy(p, esp->secrets, sizeof(esp->secrets)); + p += sizeof(esp->secrets); vpninfo->cstp_pkt->len = p - vpninfo->cstp_pkt->data; vpninfo->cstp_pkt->oncp_hdr[0] = (p - vpninfo->cstp_pkt->oncp_hdr - 2); vpninfo->cstp_pkt->oncp_hdr[1] = (p - vpninfo->cstp_pkt->oncp_hdr - 2) >> 8; @@ -1240,7 +1242,7 @@ static int oncp_receive_espkeys(struct openconnect_info *vpninfo, int len) queue_packet(&vpninfo->oncp_control_queue, vpninfo->cstp_pkt); vpninfo->cstp_pkt = NULL; - print_esp_keys(vpninfo, _("new incoming"), &vpninfo->esp_in); + print_esp_keys(vpninfo, _("new incoming"), esp); print_esp_keys(vpninfo, _("new outgoing"), &vpninfo->esp_out); } return ret; diff --git a/openconnect-internal.h b/openconnect-internal.h index c66056fe..5d89e3bf 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -279,7 +279,9 @@ struct openconnect_info { uint32_t esp_lifetime_bytes; uint32_t esp_lifetime_seconds; uint32_t esp_ssl_fallback; - struct esp esp_in; + int current_esp_in; + int old_esp_maxseq; + struct esp esp_in[2]; struct esp esp_out; int tncc_fd; /* For Juniper TNCC */ @@ -774,7 +776,7 @@ int print_esp_keys(struct openconnect_info *vpninfo, const char *name, struct es /* gnutls-esp.c */ int setup_esp_keys(struct openconnect_info *vpninfo); void destroy_esp_ciphers(struct esp *esp); -int decrypt_esp_packet(struct openconnect_info *vpninfo, struct pkt *pkt); +int decrypt_esp_packet(struct openconnect_info *vpninfo, struct esp *esp, struct pkt *pkt); int encrypt_esp_packet(struct openconnect_info *vpninfo, struct pkt *pkt); /* {gnutls,openssl}.c */ diff --git a/openssl-esp.c b/openssl-esp.c index e79bdb6c..a35b5991 100644 --- a/openssl-esp.c +++ b/openssl-esp.c @@ -38,6 +38,8 @@ static int init_esp_ciphers(struct openconnect_info *vpninfo, struct esp *esp, { int ret; + destroy_esp_ciphers(esp); + EVP_CIPHER_CTX_init(&esp->cipher); if (decrypt) ret = EVP_DecryptInit_ex(&esp->cipher, encalg, NULL, esp->secrets, NULL); @@ -51,7 +53,7 @@ static int init_esp_ciphers(struct openconnect_info *vpninfo, struct esp *esp, return -EIO; } EVP_CIPHER_CTX_set_padding(&esp->cipher, 0); - + HMAC_CTX_init(&esp->hmac); if (!HMAC_Init_ex(&esp->hmac, esp->secrets + EVP_CIPHER_key_length(encalg), EVP_MD_size(macalg), macalg, NULL)) { @@ -68,6 +70,7 @@ static int init_esp_ciphers(struct openconnect_info *vpninfo, struct esp *esp, int setup_esp_keys(struct openconnect_info *vpninfo) { + struct esp *esp_in; const EVP_CIPHER *encalg; const EVP_MD *macalg; int ret; @@ -99,8 +102,12 @@ int setup_esp_keys(struct openconnect_info *vpninfo) return -EINVAL; } - if (!RAND_pseudo_bytes((void *)&vpninfo->esp_in.spi, sizeof(vpninfo->esp_in.spi)) || - !RAND_bytes((void *)&vpninfo->esp_in.secrets, sizeof(vpninfo->esp_in.secrets))) { + vpninfo->old_esp_maxseq = vpninfo->esp_in[vpninfo->current_esp_in].seq + 32; + vpninfo->current_esp_in ^= 1; + esp_in = &vpninfo->esp_in[vpninfo->current_esp_in]; + + if (!RAND_pseudo_bytes((void *)&esp_in->spi, sizeof(esp_in->spi)) || + !RAND_bytes((void *)&esp_in->secrets, sizeof(esp_in->secrets))) { vpn_progress(vpninfo, PRG_ERR, _("Failed to generate random keys for ESP:\n")); openconnect_report_ssl_errors(vpninfo); @@ -111,7 +118,7 @@ int setup_esp_keys(struct openconnect_info *vpninfo) if (ret) return ret; - ret = init_esp_ciphers(vpninfo, &vpninfo->esp_in, macalg, encalg, 1); + ret = init_esp_ciphers(vpninfo, esp_in, macalg, encalg, 1); if (ret) { destroy_esp_ciphers(&vpninfo->esp_out); return ret; @@ -124,21 +131,14 @@ int setup_esp_keys(struct openconnect_info *vpninfo) } /* pkt->len shall be the *payload* length. Omitting the header and the 12-byte HMAC */ -int decrypt_esp_packet(struct openconnect_info *vpninfo, struct pkt *pkt) +int decrypt_esp_packet(struct openconnect_info *vpninfo, struct esp *esp, struct pkt *pkt) { unsigned char hmac_buf[20]; unsigned int hmac_len = sizeof(hmac_buf); int crypt_len = pkt->len; HMAC_CTX hmac_ctx; - if (pkt->esp.spi != vpninfo->esp_in.spi) { - vpn_progress(vpninfo, PRG_DEBUG, - _("Received ESP packet with invalid SPI 0x%08x\n"), - ntohl(pkt->esp.spi)); - return -EINVAL; - } - - HMAC_CTX_copy(&hmac_ctx, &vpninfo->esp_in.hmac); + HMAC_CTX_copy(&hmac_ctx, &esp->hmac); HMAC_Update(&hmac_ctx, (void *)&pkt->esp, sizeof(pkt->esp) + pkt->len); HMAC_Final(&hmac_ctx, hmac_buf, &hmac_len); HMAC_CTX_cleanup(&hmac_ctx); @@ -153,11 +153,11 @@ int decrypt_esp_packet(struct openconnect_info *vpninfo, struct pkt *pkt) * should do th check anyway, but only warn instead of discarding * the packet? */ if (vpninfo->esp_replay_protect && - verify_packet_seqno(vpninfo, &vpninfo->esp_in, ntohl(pkt->esp.seq))) + verify_packet_seqno(vpninfo, esp, ntohl(pkt->esp.seq))) return -EINVAL; - if (!EVP_DecryptInit_ex(&vpninfo->esp_in.cipher, NULL, NULL, NULL, + if (!EVP_DecryptInit_ex(&esp->cipher, NULL, NULL, NULL, pkt->esp.iv)) { vpn_progress(vpninfo, PRG_ERR, _("Failed to set up decryption context for ESP packet:\n")); @@ -165,7 +165,7 @@ int decrypt_esp_packet(struct openconnect_info *vpninfo, struct pkt *pkt) return -EINVAL; } - if (!EVP_DecryptUpdate(&vpninfo->esp_in.cipher, pkt->data, &crypt_len, + if (!EVP_DecryptUpdate(&esp->cipher, pkt->data, &crypt_len, pkt->data, pkt->len)) { vpn_progress(vpninfo, PRG_ERR, _("Failed to decrypt ESP packet:\n"));