Skip to content

Commit

Permalink
Hook up ESP mainloop
Browse files Browse the repository at this point in the history
Very basic, and all the corner cases aren't properly handled, but it does
at least send/receive packets and the encryption/decryption routines are
doing the right thing.

Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
  • Loading branch information
David Woodhouse authored and David Woodhouse committed Jan 26, 2015
1 parent 98b7a93 commit c2cb39a
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 52 deletions.
139 changes: 134 additions & 5 deletions esp.c
Expand Up @@ -110,17 +110,146 @@ int verify_packet_seqno(struct openconnect_info *vpninfo,

int esp_setup(struct openconnect_info *vpninfo, int dtls_attempt_period)
{
if (vpninfo->dtls_state == DTLS_DISABLED)
int fd;
struct pkt *pkt;
int pktlen;

if (vpninfo->dtls_state == DTLS_DISABLED ||
vpninfo->dtls_state == DTLS_NOSECRET)
return -EINVAL;

vpn_progress(vpninfo, PRG_ERR,
_("ESP not implemented yet\n"));
return -EINVAL;
fd = udp_connect(vpninfo);
if (fd < 0)
return fd;

pkt = malloc(sizeof(*pkt) + 1 + vpninfo->pkt_trailer);
if (!pkt) {
closesocket(fd);
return -ENOMEM;
}

/* We are not connected until we get an ESP packet back */
vpninfo->dtls_state = DTLS_CONNECTING;
vpninfo->dtls_fd = fd;
monitor_fd_new(vpninfo, dtls);
monitor_read_fd(vpninfo, dtls);
monitor_except_fd(vpninfo, dtls);

pkt->len = 1;
pkt->data[0] = 0;
pktlen = encrypt_esp_packet(vpninfo, pkt);
send(fd, &pkt->esp, pktlen, 0);

pkt->len = 1;
pkt->data[0] = 0;
pktlen = encrypt_esp_packet(vpninfo, pkt);
send(fd, &pkt->esp, pktlen, 0);

free(pkt);
time(&vpninfo->new_dtls_started);

return 0;
}

int esp_mainloop(struct openconnect_info *vpninfo, int *timeout)
{
return 0;
int work_done = 0;
int ret;

while (1) {
int len = vpninfo->ip_info.mtu + vpninfo->pkt_trailer;
struct pkt *pkt;

if (!vpninfo->dtls_pkt) {
vpninfo->dtls_pkt = malloc(sizeof(struct pkt) + len);
if (!vpninfo->dtls_pkt) {
vpn_progress(vpninfo, PRG_ERR, _("Allocation failed\n"));
break;
}
}
pkt = vpninfo->dtls_pkt;
len = recv(vpninfo->dtls_fd, &pkt->esp, len + sizeof(pkt->esp), 0);
if (len <= 0)
break;

vpn_progress(vpninfo, PRG_TRACE, _("Received ESP packet of %d bytes\n"),
len);
work_done = 1;

if (len <= sizeof(pkt->esp) + 12)
continue;

len -= sizeof(pkt->esp) + 12;
pkt->len = len;

if (decrypt_esp_packet(vpninfo, pkt))
continue;

if (pkt->data[len - 1] != 0x04 && pkt->data[len - 1] != 0x29) {
/* 0x05 is LZO compressed. */
vpn_progress(vpninfo, PRG_ERR,
_("Received ESP packet with unrecognised payload type %02x\n"),
pkt->data[len-1]);
continue;
}

if (len <= 2 + pkt->data[len - 2]) {
vpn_progress(vpninfo, PRG_ERR,
_("Invalid padding length %02x in ESP\n"),
pkt->data[len - 2]);
continue;
}
/* XXX: Actually check the padding bytes too. */
pkt->len = len - 2 - pkt->data[len - 2];

if (pkt->len == 1 && pkt->data[0] == 0) {
vpn_progress(vpninfo, PRG_INFO,
_("ESP session established with server\n"));
vpninfo->dtls_state = DTLS_CONNECTED;
continue;
}
queue_packet(&vpninfo->incoming_queue, pkt);
vpninfo->dtls_pkt = NULL;
}

if (vpninfo->dtls_state != DTLS_CONNECTED)
return 0;

unmonitor_write_fd(vpninfo, dtls);
while (vpninfo->outgoing_queue) {
struct pkt *this = vpninfo->outgoing_queue;
int len;

vpninfo->outgoing_queue = this->next;
vpninfo->outgoing_qlen--;

len = encrypt_esp_packet(vpninfo, this);
if (len > 0) {
ret = send(vpninfo->dtls_fd, &this->esp, len, 0);
if (ret < 0) {
/* Not that this is likely to happen with UDP, but... */
if (errno == ENOBUFS || errno == EAGAIN || errno == EWOULDBLOCK) {
monitor_write_fd(vpninfo, dtls);
/* XXX: Keep the packet somewhere? */
free(this);
return work_done;
} else {
/* A real error in sending. Fall back to TCP? */
vpn_progress(vpninfo, PRG_ERR,
_("Failed to send ESP packet: %s\n"),
strerror(errno));
}
} else
vpn_progress(vpninfo, PRG_TRACE, _("Sent ESP packet of %d bytes\n"),
len);
} else {
/* XXX: Fall back to TCP transport? */
}
free(this);
work_done = 1;
}

return work_done;
}

void esp_close(struct openconnect_info *vpninfo)
Expand Down
57 changes: 15 additions & 42 deletions gnutls-esp.c
Expand Up @@ -85,6 +85,8 @@ int setup_esp_keys(struct openconnect_info *vpninfo)

if (vpninfo->dtls_state == DTLS_DISABLED)
return -EOPNOTSUPP;
if (!vpninfo->dtls_addr)
return -EINVAL;

switch (vpninfo->esp_enc) {
case 0x02:
Expand Down Expand Up @@ -132,27 +134,22 @@ int setup_esp_keys(struct openconnect_info *vpninfo)
return 0;
}


int decrypt_and_queue_esp_packet(struct openconnect_info *vpninfo, unsigned char *esp, int len)
/* 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)
{
struct esp_hdr *hdr = (void *)esp;
struct pkt *pkt;
unsigned char hmac_buf[20];
int err;

if (len < sizeof(*hdr) + 12)
return -EINVAL;

if (memcmp(hdr->spi, vpninfo->esp_in.spi, 4)) {
if (memcmp(pkt->esp.spi, vpninfo->esp_in.spi, 4)) {
vpn_progress(vpninfo, PRG_DEBUG,
_("Received ESP packet with invalid SPI %02x%02x%02x%02x\n"),
esp[0], esp[1], esp[2], esp[3]);
pkt->esp.spi[0], pkt->esp.spi[1], pkt->esp.spi[2], pkt->esp.spi[3]);
return -EINVAL;
}

gnutls_hmac(vpninfo->esp_in.hmac, esp, len - 12);
gnutls_hmac(vpninfo->esp_in.hmac, &pkt->esp, sizeof(pkt->esp) + pkt->len);
gnutls_hmac_output(vpninfo->esp_in.hmac, hmac_buf);
if (memcmp(hmac_buf, esp + len - 12, 12)) {
if (memcmp(hmac_buf, pkt->data + pkt->len, 12)) {
vpn_progress(vpninfo, PRG_DEBUG,
_("Received ESP packet with invalid HMAC\n"));
return -EINVAL;
Expand All @@ -162,44 +159,20 @@ int decrypt_and_queue_esp_packet(struct openconnect_info *vpninfo, unsigned char
* 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(hdr->seq)))
verify_packet_seqno(vpninfo, &vpninfo->esp_in, ntohl(pkt->esp.seq)))
return -EINVAL;

gnutls_cipher_set_iv(vpninfo->esp_in.cipher, hdr->iv, sizeof(hdr->iv));
gnutls_cipher_set_iv(vpninfo->esp_in.cipher, pkt->esp.iv, sizeof(pkt->esp.iv));

len -= sizeof(*hdr) + 12;

pkt = malloc(sizeof(struct pkt) + len);
if (!pkt)
return -ENOMEM;

err = gnutls_cipher_decrypt2(vpninfo->esp_in.cipher,
hdr->payload, len,
pkt->data, len);
err = gnutls_cipher_decrypt(vpninfo->esp_in.cipher,
pkt->data, pkt->len);
if (err) {
vpn_progress(vpninfo, PRG_ERR,
_("Decrypting ESP packet failed: %s\n"),
gnutls_strerror(err));
return -EINVAL;
}

if (pkt->data[len - 1] != 0x04 && pkt->data[len - 1] != 0x29) {
/* 0x05 is LZO compressed. */
vpn_progress(vpninfo, PRG_ERR,
_("Received ESP packet with unrecognised payload type %02x\n"),
pkt->data[len-1]);
return -EINVAL;
}
if (len <= 2 + pkt->data[len - 2]) {
vpn_progress(vpninfo, PRG_ERR,
_("Invalid padding length %02x in ESP\n"),
pkt->data[len - 2]);
return -EINVAL;
}
/* XXX: Actually check the padding bytes too. */
pkt->len = len - 2 + pkt->data[len - 2];

queue_packet(&vpninfo->incoming_queue, pkt);
return 0;
}

Expand All @@ -212,7 +185,7 @@ int encrypt_esp_packet(struct openconnect_info *vpninfo, struct pkt *pkt)
/* This gets much more fun if the IV is variable-length */
memcpy(pkt->esp.spi, vpninfo->esp_out.spi, 4);
pkt->esp.seq = htonl(vpninfo->esp_out.seq++);
err = gnutls_rnd(GNUTLS_RND_RANDOM, pkt->esp.iv, sizeof(pkt->esp.iv));
err = gnutls_rnd(GNUTLS_RND_NONCE, pkt->esp.iv, sizeof(pkt->esp.iv));
if (err) {
vpn_progress(vpninfo, PRG_ERR,
_("Failed to generate ESP packet IV: %s\n"),
Expand All @@ -235,13 +208,13 @@ int encrypt_esp_packet(struct openconnect_info *vpninfo, struct pkt *pkt)
return -EIO;
}

err = gnutls_hmac(vpninfo->esp_out.hmac, pkt->data, pkt->len + padlen + 2);
err = gnutls_hmac(vpninfo->esp_out.hmac, &pkt->esp, sizeof(pkt->esp) + pkt->len + padlen + 2);
if (err) {
vpn_progress(vpninfo, PRG_ERR,
_("Failed to calculate HMAC for ESP packet: %s\n"),
gnutls_strerror(err));
return -EIO;
}
gnutls_hmac_output(vpninfo->esp_out.hmac, pkt->data + pkt->len + padlen + 2);
gnutls_hmac_output(vpninfo->esp_out.hmac, pkt->data + pkt->len + padlen + 2);
return sizeof(pkt->esp) + pkt->len + padlen + 2 + 12;
}
7 changes: 4 additions & 3 deletions oncp.c
Expand Up @@ -798,8 +798,9 @@ static int process_attr(struct openconnect_info *vpninfo, int group, int attr,
case GRP_ATTR(8, 4):
if (attrlen != 2)
goto badlen;
vpninfo->esp_port = TLV_BE16(data);
vpn_progress(vpninfo, PRG_DEBUG, _("ESP port: %d\n"), vpninfo->esp_port);
i = TLV_BE16(data);
udp_sockaddr(vpninfo, i);
vpn_progress(vpninfo, PRG_DEBUG, _("ESP port: %d\n"), i);
break;

case GRP_ATTR(8, 5):
Expand Down Expand Up @@ -838,7 +839,7 @@ static int process_attr(struct openconnect_info *vpninfo, int group, int attr,
if (attrlen != 4)
goto badlen;
memcpy(vpninfo->esp_out.spi, data, 4);
vpn_progress(vpninfo, PRG_DEBUG, _("ESP SPI (outbound): %u\n"),
vpn_progress(vpninfo, PRG_DEBUG, _("ESP SPI (outbound): %x\n"),
TLV_BE32(data));
break;

Expand Down
3 changes: 1 addition & 2 deletions openconnect-internal.h
Expand Up @@ -280,7 +280,6 @@ struct openconnect_info {
uint32_t esp_ssl_fallback;
struct esp esp_in;
struct esp esp_out;
int esp_port;

int tncc_fd; /* For Juniper TNCC */
const char *csd_xmltag;
Expand Down Expand Up @@ -773,7 +772,7 @@ void esp_shutdown(struct openconnect_info *vpninfo);
/* gnutls-esp.c */
int setup_esp_keys(struct openconnect_info *vpninfo);
void destroy_esp_ciphers(struct esp *esp);
int decrypt_and_queue_esp_packet(struct openconnect_info *vpninfo, unsigned char *esp, int len);
int decrypt_esp_packet(struct openconnect_info *vpninfo, struct pkt *pkt);
int encrypt_esp_packet(struct openconnect_info *vpninfo, struct pkt *pkt);

/* {gnutls,openssl}.c */
Expand Down

0 comments on commit c2cb39a

Please sign in to comment.