Skip to content

Commit

Permalink
Accept packets on old ESP setup during changeover
Browse files Browse the repository at this point in the history
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
  • Loading branch information
David Woodhouse authored and David Woodhouse committed Jan 26, 2015
1 parent 376fead commit 2c61747
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 53 deletions.
20 changes: 18 additions & 2 deletions esp.c
Expand Up @@ -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 */
Expand All @@ -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;

Expand Down Expand Up @@ -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. */
Expand Down
42 changes: 17 additions & 25 deletions gnutls-esp.c
Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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));
Expand All @@ -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;
Expand All @@ -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"));
Expand All @@ -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"),
Expand Down
3 changes: 2 additions & 1 deletion library.c
Expand Up @@ -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);

Expand Down
16 changes: 9 additions & 7 deletions oncp.c
Expand Up @@ -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;
}
Expand Down Expand Up @@ -1223,24 +1224,25 @@ 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;

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;
Expand Down
6 changes: 4 additions & 2 deletions openconnect-internal.h
Expand Up @@ -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 */
Expand Down Expand Up @@ -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 */
Expand Down
32 changes: 16 additions & 16 deletions openssl-esp.c
Expand Up @@ -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);
Expand All @@ -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)) {
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand All @@ -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);
Expand All @@ -153,19 +153,19 @@ 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"));
openconnect_report_ssl_errors(vpninfo);
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"));
Expand Down

0 comments on commit 2c61747

Please sign in to comment.