static int init_esp_ciphers(struct openconnect_info *vpninfo, struct esp *esp, const EVP_MD *macalg, const EVP_CIPHER *encalg, int decrypt) { 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); else ret = EVP_EncryptInit_ex(&esp->cipher, encalg, NULL, esp->secrets, NULL); if (!ret) { vpn_progress(vpninfo, PRG_ERR, _("Failed to initialise ESP cipher:\n")); openconnect_report_ssl_errors(vpninfo); 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)) { vpn_progress(vpninfo, PRG_ERR, _("Failed to initialize ESP HMAC\n")); openconnect_report_ssl_errors(vpninfo); destroy_esp_ciphers(esp); } esp->seq = 0; esp->seq_backlog = 0; return 0; }
int openconnect_setup_tun_script(struct openconnect_info *vpninfo, const char *tun_script) { pid_t child; int fds[2]; STRDUP(vpninfo->vpnc_script, tun_script); vpninfo->script_tun = 1; prepare_script_env(vpninfo); if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fds)) { vpn_progress(vpninfo, PRG_ERR, _("socketpair failed: %s\n"), strerror(errno)); return -EIO; } child = fork(); if (child < 0) { vpn_progress(vpninfo, PRG_ERR, _("fork failed: %s\n"), strerror(errno)); return -EIO; } else if (!child) { if (setpgid(0, getpid()) < 0) perror(_("setpgid")); close(fds[0]); script_setenv_int(vpninfo, "VPNFD", fds[1]); apply_script_env(vpninfo->script_env); execl("/bin/sh", "/bin/sh", "-c", vpninfo->vpnc_script, NULL); perror(_("execl")); exit(1); } close(fds[1]); vpninfo->script_tun = child; vpninfo->ifname = strdup(_("(script)")); return openconnect_setup_tun_fd(vpninfo, fds[0]); }
static BIO *BIO_from_keystore(struct openconnect_info *vpninfo, const char *item) { unsigned char *content; BIO *b; int len; const char *p = item + 9; /* Skip first two slashes if the user has given it as keystore://foo ... */ if (*p == '/') p++; if (*p == '/') p++; len = keystore_fetch(p, &content); if (len < 0) { vpn_progress(vpninfo, PRG_ERR, _("Failed to load item '%s' from keystore: %s\n"), p, keystore_strerror(len)); return NULL; } if (!(b = BIO_new(BIO_s_mem())) || BIO_write(b, content, len) != len) { vpn_progress(vpninfo, PRG_ERR, _("Failed to create BIO for keystore item '%s'\n"), p); free(content); BIO_free(b); return NULL; } free(content); return b; }
int openconnect_check_peer_cert_hash(struct openconnect_info *vpninfo, const char *old_hash) { char sha1_text[41]; const char *fingerprint; unsigned min_match_len; unsigned real_min_match_len = 4; unsigned old_len, fingerprint_len; if (strchr(old_hash, ':')) { if (strncmp(old_hash, "sha1:", 5) == 0) { fingerprint = vpninfo->peer_cert_sha1; min_match_len = real_min_match_len + sizeof("sha1:")-1; } else if (strncmp(old_hash, "sha256:", 7) == 0) { fingerprint = vpninfo->peer_cert_sha256; min_match_len = real_min_match_len + sizeof("sha256:")-1; } else { vpn_progress(vpninfo, PRG_ERR, _("Unknown certificate hash: %s.\n"), old_hash); return -EIO; } if (!fingerprint) return -EIO; } else { unsigned char *cert; int len, i; unsigned char sha1_bin[SHA1_SIZE]; len = openconnect_get_peer_cert_DER(vpninfo, &cert); if (len < 0) return len; if (openconnect_sha1(sha1_bin, cert, len)) return -EIO; for (i = 0; i < sizeof(sha1_bin); i++) sprintf(&sha1_text[i*2], "%02x", sha1_bin[i]); fingerprint = sha1_text; min_match_len = real_min_match_len; } old_len = strlen(old_hash); fingerprint_len = strlen(fingerprint); /* allow partial matches */ if (old_len < fingerprint_len) { if (strncasecmp(old_hash, fingerprint, MAX(min_match_len, old_len))) { if (old_len < min_match_len) { vpn_progress(vpninfo, PRG_ERR, _("The size of the provided fingerprint is less than the minimum required (%u).\n"), real_min_match_len); } return 1; } } else { if (strcasecmp(old_hash, fingerprint)) return 1; } return 0; }
int openconnect_parse_url(struct openconnect_info *vpninfo, const char *url) { char *scheme = NULL; int ret; UTF8CHECK(url); openconnect_set_hostname(vpninfo, NULL); free(vpninfo->urlpath); vpninfo->urlpath = NULL; ret = internal_parse_url(url, &scheme, &vpninfo->hostname, &vpninfo->port, &vpninfo->urlpath, 443); if (ret) { vpn_progress(vpninfo, PRG_ERR, _("Failed to parse server URL '%s'\n"), url); return ret; } if (scheme && strcmp(scheme, "https")) { vpn_progress(vpninfo, PRG_ERR, _("Only https:// permitted for server URL\n")); ret = -EINVAL; } free(scheme); return ret; }
static int reload_pem_cert(struct openconnect_info *vpninfo) { BIO *b = BIO_new(BIO_s_file_internal()); char buf[200]; if (!b) return -ENOMEM; if (BIO_read_filename(b, vpninfo->cert) <= 0) { err: BIO_free(b); vpn_progress(vpninfo, PRG_ERR, _("Failed to reload X509 cert for expiry check\n")); openconnect_report_ssl_errors(vpninfo); return -EIO; } vpninfo->cert_x509 = PEM_read_bio_X509_AUX(b, NULL, NULL, NULL); BIO_free(b); if (!vpninfo->cert_x509) goto err; X509_NAME_oneline(X509_get_subject_name(vpninfo->cert_x509), buf, sizeof(buf)); vpn_progress(vpninfo, PRG_INFO, _("Using client certificate '%s'\n"), buf); return 0; }
int openconnect_SSL_read(struct openconnect_info *vpninfo, char *buf, size_t len) { int done; while ((done = SSL_read(vpninfo->https_ssl, buf, len)) == -1) { int err = SSL_get_error(vpninfo->https_ssl, done); fd_set wr_set, rd_set; int maxfd = vpninfo->ssl_fd; FD_ZERO(&wr_set); FD_ZERO(&rd_set); if (err == SSL_ERROR_WANT_READ) FD_SET(vpninfo->ssl_fd, &rd_set); else if (err == SSL_ERROR_WANT_WRITE) FD_SET(vpninfo->ssl_fd, &wr_set); else { vpn_progress(vpninfo, PRG_ERR, _("Failed to read from SSL socket\n")); openconnect_report_ssl_errors(vpninfo); return -EIO; } cmd_fd_set(vpninfo, &rd_set, &maxfd); select(maxfd + 1, &rd_set, &wr_set, NULL, NULL); if (is_cancel_pending(vpninfo, &rd_set)) { vpn_progress(vpninfo, PRG_ERR, _("SSL read cancelled\n")); return -EINTR; } } return done; }
static void print_gss_err(struct openconnect_info *vpninfo, const char *where, gss_OID mech, OM_uint32 err_maj, OM_uint32 err_min) { OM_uint32 major, minor, msg_ctx = 0; gss_buffer_desc status; do { major = gss_display_status(&minor, err_maj, GSS_C_GSS_CODE, mech, &msg_ctx, &status); if (GSS_ERROR(major)) break; vpn_progress(vpninfo, PRG_ERR, "%s: %s\n", where, (char *)status.value); gss_release_buffer(&minor, &status); } while (msg_ctx); msg_ctx = 0; do { major = gss_display_status(&minor, err_min, GSS_C_MECH_CODE, mech, &msg_ctx, &status); if (GSS_ERROR(major)) break; vpn_progress(vpninfo, PRG_ERR, "%s: %s\n", where, (char *)status.value); gss_release_buffer(&minor, &status); } while (msg_ctx); }
static int load_tpm_certificate(struct openconnect_info *vpninfo) { ENGINE *e; EVP_PKEY *key; UI_METHOD *meth = NULL; int ret = 0; ENGINE_load_builtin_engines(); e = ENGINE_by_id("tpm"); if (!e) { vpn_progress(vpninfo, PRG_ERR, _("Can't load TPM engine.\n")); openconnect_report_ssl_errors(vpninfo); return -EINVAL; } if (!ENGINE_init(e) || !ENGINE_set_default_RSA(e) || !ENGINE_set_default_RAND(e)) { vpn_progress(vpninfo, PRG_ERR, _("Failed to init TPM engine\n")); openconnect_report_ssl_errors(vpninfo); ENGINE_free(e); return -EINVAL; } if (vpninfo->cert_password) { if (!ENGINE_ctrl_cmd(e, "PIN", strlen(vpninfo->cert_password), vpninfo->cert_password, NULL, 0)) { vpn_progress(vpninfo, PRG_ERR, _("Failed to set TPM SRK password\n")); openconnect_report_ssl_errors(vpninfo); } vpninfo->cert_password = NULL; free(vpninfo->cert_password); } else { /* Provide our own UI method to handle the PIN callback. */ meth = create_openssl_ui(vpninfo); } key = ENGINE_load_private_key(e, vpninfo->sslkey, meth, NULL); if (meth) UI_destroy_method(meth); if (!key) { vpn_progress(vpninfo, PRG_ERR, _("Failed to load TPM private key\n")); openconnect_report_ssl_errors(vpninfo); ret = -EINVAL; goto out; } if (!SSL_CTX_use_PrivateKey(vpninfo->https_ctx, key)) { vpn_progress(vpninfo, PRG_ERR, _("Add key from TPM failed\n")); openconnect_report_ssl_errors(vpninfo); ret = -EINVAL; } EVP_PKEY_free(key); out: ENGINE_finish(e); ENGINE_free(e); return ret; }
int dtls_try_handshake(struct openconnect_info *vpninfo) { int err = gnutls_handshake(vpninfo->dtls_ssl); if (!err) { #ifdef HAVE_GNUTLS_DTLS_SET_DATA_MTU /* Make sure GnuTLS's idea of the MTU is sufficient to take a full VPN MTU (with 1-byte header) in a data record. */ err = gnutls_dtls_set_data_mtu(vpninfo->dtls_ssl, vpninfo->ip_info.mtu + 1); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to set DTLS MTU: %s\n"), gnutls_strerror(err)); goto error; } #else /* If we don't have gnutls_dtls_set_data_mtu() then make sure we leave enough headroom by adding the worst-case overhead. We only support AES128-CBC and DES-CBC3-SHA anyway, so working out the worst case isn't hard. */ gnutls_dtls_set_mtu(vpninfo->dtls_ssl, vpninfo->ip_info.mtu + 1 /* packet + header */ + 13 /* DTLS header */ + 20 /* biggest supported MAC (SHA1) */ + 16 /* biggest supported IV (AES-128) */ + 16 /* max padding */); #endif vpninfo->dtls_state = DTLS_CONNECTED; vpn_progress(vpninfo, PRG_INFO, _("Established DTLS connection (using GnuTLS). Ciphersuite %s.\n"), vpninfo->dtls_cipher); vpninfo->dtls_times.last_rekey = vpninfo->dtls_times.last_rx = vpninfo->dtls_times.last_tx = time(NULL); /* XXX: For OpenSSL we explicitly prevent retransmits here. */ return 0; } if (err == GNUTLS_E_AGAIN) { if (time(NULL) < vpninfo->new_dtls_started + 12) return 0; vpn_progress(vpninfo, PRG_DEBUG, _("DTLS handshake timed out\n")); } vpn_progress(vpninfo, PRG_ERR, _("DTLS handshake failed: %s\n"), gnutls_strerror(err)); error: dtls_close(vpninfo); vpninfo->dtls_state = DTLS_SLEEPING; time(&vpninfo->new_dtls_started); return -EINVAL; }
static int start_dtls_handshake(struct openconnect_info *vpninfo, int dtls_fd) { gnutls_session_t dtls_ssl; gnutls_datum_t master_secret, session_id; int err; int cipher; for (cipher = 0; cipher < sizeof(gnutls_dtls_ciphers)/sizeof(gnutls_dtls_ciphers[0]); cipher++) { if (!strcmp(vpninfo->dtls_cipher, gnutls_dtls_ciphers[cipher].name)) goto found_cipher; } vpn_progress(vpninfo, PRG_ERR, _("Unknown DTLS parameters for requested CipherSuite '%s'\n"), vpninfo->dtls_cipher); vpninfo->dtls_attempt_period = 0; return -EINVAL; found_cipher: gnutls_init(&dtls_ssl, GNUTLS_CLIENT|GNUTLS_DATAGRAM|GNUTLS_NONBLOCK); err = gnutls_priority_set_direct(dtls_ssl, gnutls_dtls_ciphers[cipher].prio, NULL); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to set DTLS priority: %s\n"), gnutls_strerror(err)); gnutls_deinit(dtls_ssl); vpninfo->dtls_attempt_period = 0; return -EINVAL; } gnutls_transport_set_ptr(dtls_ssl, (gnutls_transport_ptr_t)(intptr_t)dtls_fd); gnutls_record_disable_padding(dtls_ssl); master_secret.data = vpninfo->dtls_secret; master_secret.size = sizeof(vpninfo->dtls_secret); session_id.data = vpninfo->dtls_session_id; session_id.size = sizeof(vpninfo->dtls_session_id); err = gnutls_session_set_premaster(dtls_ssl, GNUTLS_CLIENT, gnutls_dtls_ciphers[cipher].version, GNUTLS_KX_RSA, gnutls_dtls_ciphers[cipher].cipher, gnutls_dtls_ciphers[cipher].mac, GNUTLS_COMP_NULL, &master_secret, &session_id); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to set DTLS session parameters: %s\n"), gnutls_strerror(err)); gnutls_deinit(dtls_ssl); vpninfo->dtls_attempt_period = 0; return -EINVAL; } vpninfo->dtls_ssl = dtls_ssl; return 0; }
int openconnect_SSL_gets(struct openconnect_info *vpninfo, char *buf, size_t len) { int i = 0; int ret; if (len < 2) return -EINVAL; while (1) { ret = SSL_read(vpninfo->https_ssl, buf + i, 1); if (ret == 1) { if (buf[i] == '\n') { buf[i] = 0; if (i && buf[i-1] == '\r') { buf[i-1] = 0; i--; } return i; } i++; if (i >= len - 1) { buf[i] = 0; return i; } } else { fd_set rd_set, wr_set; int maxfd = vpninfo->ssl_fd; FD_ZERO(&rd_set); FD_ZERO(&wr_set); ret = SSL_get_error(vpninfo->https_ssl, ret); if (ret == SSL_ERROR_WANT_READ) FD_SET(vpninfo->ssl_fd, &rd_set); else if (ret == SSL_ERROR_WANT_WRITE) FD_SET(vpninfo->ssl_fd, &wr_set); else { vpn_progress(vpninfo, PRG_ERR, _("Failed to read from SSL socket\n")); openconnect_report_ssl_errors(vpninfo); ret = -EIO; break; } cmd_fd_set(vpninfo, &rd_set, &maxfd); select(maxfd + 1, &rd_set, &wr_set, NULL, NULL); if (is_cancel_pending(vpninfo, &rd_set)) { vpn_progress(vpninfo, PRG_ERR, _("SSL read cancelled\n")); ret = -EINTR; break; } } } buf[i] = 0; return i ?: ret; }
int os_write_tun(struct openconnect_info *vpninfo, struct pkt *pkt) { unsigned char *data = pkt->data; int len = pkt->len; #ifdef TUN_HAS_AF_PREFIX if (!vpninfo->script_tun) { struct ip *iph = (void *)data; int type; if (iph->ip_v == 6) type = AF_INET6; else if (iph->ip_v == 4) type = AF_INET; else { static int complained = 0; if (!complained) { complained = 1; vpn_progress(vpninfo, PRG_ERR, _("Unknown packet (len %d) received: %02x %02x %02x %02x...\n"), len, data[0], data[1], data[2], data[3]); } return 0; } data -= sizeof(int); len += sizeof(int); *(int *)data = htonl(type); } #endif if (write(vpninfo->tun_fd, data, len) < 0) { /* Handle death of "script" socket */ if (vpninfo->script_tun && errno == ENOTCONN) { vpninfo->quit_reason = "Client connection terminated"; return -1; } /* The tun device in the Linux kernel returns -ENOMEM when * the queue is full, so theoretically we could check for * that and retry too. But it doesn't let us poll() for * the no-longer-full situation, so let's not bother. */ if (errno == ENOBUFS || errno == EAGAIN || errno == EWOULDBLOCK) { monitor_write_fd(vpninfo, tun); return -1; } vpn_progress(vpninfo, PRG_ERR, _("Failed to write incoming packet: %s\n"), strerror(errno)); } return 0; }
static int link_proto(struct openconnect_info *vpninfo, int unit_nr, const char *devname, uint64_t flags) { int ip_fd, mux_id, tun2_fd; struct lifreq ifr; tun2_fd = open("/dev/tun", O_RDWR); if (tun2_fd < 0) { vpn_perror(vpninfo, _("Could not open /dev/tun for plumbing")); return -EIO; } if (ioctl(tun2_fd, I_PUSH, "ip") < 0) { vpn_perror(vpninfo, _("Can't push IP")); close(tun2_fd); return -EIO; } sprintf(ifr.lifr_name, "tun%d", unit_nr); ifr.lifr_ppa = unit_nr; ifr.lifr_flags = flags; if (ioctl(tun2_fd, SIOCSLIFNAME, &ifr) < 0) { vpn_perror(vpninfo, _("Can't set ifname")); close(tun2_fd); return -1; } ip_fd = open(devname, O_RDWR); if (ip_fd < 0) { vpn_progress(vpninfo, PRG_ERR, _("Can't open %s: %s"), devname, strerror(errno)); close(tun2_fd); return -1; } mux_id = ioctl(ip_fd, I_LINK, tun2_fd); if (mux_id < 0) { vpn_progress(vpninfo, PRG_ERR, _("Can't plumb %s for IPv%d: %s\n"), ifr.lifr_name, (flags == IFF_IPV4) ? 4 : 6, strerror(errno)); close(tun2_fd); close(ip_fd); return -1; } close(tun2_fd); return ip_fd; }
/* MTU setting code for both Linux and BSD systems */ static void ifreq_set_ifname(struct openconnect_info *vpninfo, struct ifreq *ifr) { char *ifname = openconnect_utf8_to_legacy(vpninfo, vpninfo->ifname); strncpy(ifr->ifr_name, ifname, sizeof(ifr->ifr_name) - 1); if (ifname != vpninfo->ifname) free(ifname); } static int set_tun_mtu(struct openconnect_info *vpninfo) { struct ifreq ifr; int net_fd; net_fd = socket(PF_INET, SOCK_DGRAM, 0); if (net_fd < 0) { vpn_perror(vpninfo, _("open net")); return -EINVAL; } memset(&ifr, 0, sizeof(ifr)); ifreq_set_ifname(vpninfo, &ifr); ifr.ifr_mtu = vpninfo->ip_info.mtu; if (ioctl(net_fd, SIOCSIFMTU, &ifr) < 0) vpn_perror(vpninfo, _("SIOCSIFMTU")); close(net_fd); return 0; } #ifdef IFF_TUN /* Linux */ intptr_t os_setup_tun(struct openconnect_info *vpninfo) { int tun_fd = -1; struct ifreq ifr; int tunerr; tun_fd = open("/dev/net/tun", O_RDWR); if (tun_fd < 0) { /* Android has /dev/tun instead of /dev/net/tun Since other systems might have too, just try it as a fallback instead of using ifdef __ANDROID__ */ tunerr = errno; tun_fd = open("/dev/tun", O_RDWR); } if (tun_fd < 0) { /* If the error on /dev/tun is ENOENT, that's boring. Use the error we got on /dev/net/tun instead */ if (errno != ENOENT) tunerr = errno; vpn_progress(vpninfo, PRG_ERR, _("Failed to open tun device: %s\n"), strerror(tunerr)); return -EIO; } memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = IFF_TUN | IFF_NO_PI; if (vpninfo->ifname) ifreq_set_ifname(vpninfo, &ifr); if (ioctl(tun_fd, TUNSETIFF, (void *) &ifr) < 0) { int err = errno; vpn_progress(vpninfo, PRG_ERR, _("Failed to bind local tun device (TUNSETIFF): %s\n"), strerror(err)); if (err == EPERM) { vpn_progress(vpninfo, PRG_ERR, _("To configure local networking, openconnect must be running as root\n" "See http://www.infradead.org/openconnect/nonroot.html for more information\n")); } close(tun_fd); return -EIO; } if (!vpninfo->ifname) vpninfo->ifname = strdup(ifr.ifr_name); /* Ancient vpnc-scripts might not get this right */ set_tun_mtu(vpninfo); return tun_fd; }
static int load_xml_conf_file(struct openconnect_info *vpninfo, char **ptr, size_t *size) { struct stat st; int fd; char *xmlfile = NULL; fd = open(vpninfo->xmlconfig, O_RDONLY); if (fd < 0) { vpn_progress(vpninfo, PRG_ERR, _("Failed to open XML config file: %s\n"), strerror(errno)); return 0; } if (fstat(fd, &st)) { vpn_progress(vpninfo, PRG_ERR, _("Failed to fstat() XML config file: %s\n"), strerror(errno)); goto err; } xmlfile = malloc(st.st_size); if (!xmlfile) { vpn_progress(vpninfo, PRG_ERR, _("Failed to allocate %lu bytes for XML config file\n"), (unsigned long)st.st_size); goto err; } if (read(fd, xmlfile, st.st_size) != st.st_size) { vpn_progress(vpninfo, PRG_ERR, _("Failed to read XML config file: %s\n"), strerror(errno)); goto err; } *ptr = xmlfile; *size = st.st_size; close(fd); return 1; err: close(fd); free(xmlfile); return -1; }
int print_esp_keys(struct openconnect_info *vpninfo, const char *name, struct esp *esp) { int i; const char *enctype, *mactype; char enckey[256], mackey[256]; int enclen, maclen; switch(vpninfo->esp_enc) { case 0x02: enctype = "AES-128-CBC (RFC3602)"; enclen = 16; break; case 0x05: enctype = "AES-256-CBC (RFC3602)"; enclen = 32; break; default: return -EINVAL; } switch(vpninfo->esp_hmac) { case 0x01: mactype = "HMAC-MD5-96 (RFC2403)"; maclen = 16; break; case 0x02: mactype = "HMAC-SHA-1-96 (RFC2404)"; maclen = 20; break; default: return -EINVAL; } for (i = 0; i < enclen; i++) sprintf(enckey + (2 * i), "%02x", esp->secrets[i]); for (i = 0; i < maclen; i++) sprintf(mackey + (2 * i), "%02x", esp->secrets[enclen + i]); vpn_progress(vpninfo, PRG_TRACE, _("Parameters for %s ESP: SPI 0x%08x\n"), name, (unsigned)ntohl(esp->spi)); vpn_progress(vpninfo, PRG_TRACE, _("ESP encryption type %s key 0x%s\n"), enctype, enckey); vpn_progress(vpninfo, PRG_TRACE, _("ESP authentication type %s key 0x%s\n"), mactype, mackey); return 0; }
int encrypt_esp_packet(struct openconnect_info *vpninfo, struct pkt *pkt) { int i, padlen; const int blksize = 16; unsigned int hmac_len = 20; int crypt_len; HMAC_CTX hmac_ctx; /* This gets much more fun if the IV is variable-length */ pkt->esp.spi = vpninfo->esp_out.spi; pkt->esp.seq = htonl(vpninfo->esp_out.seq++); if (!RAND_bytes((void *)&pkt->esp.iv, sizeof(pkt->esp.iv))) { vpn_progress(vpninfo, PRG_ERR, _("Failed to generate random IV for ESP packet:\n")); openconnect_report_ssl_errors(vpninfo); return -EIO; } padlen = blksize - 1 - ((pkt->len + 1) % blksize); for (i=0; i<padlen; i++) pkt->data[pkt->len + i] = i + 1; pkt->data[pkt->len + padlen] = padlen; pkt->data[pkt->len + padlen + 1] = 0x04; /* Legacy IP */ if (!EVP_EncryptInit_ex(&vpninfo->esp_out.cipher, NULL, NULL, NULL, pkt->esp.iv)) { vpn_progress(vpninfo, PRG_ERR, _("Failed to set up encryption context for ESP packet:\n")); openconnect_report_ssl_errors(vpninfo); return -EINVAL; } crypt_len = pkt->len + padlen + 2; if (!EVP_EncryptUpdate(&vpninfo->esp_out.cipher, pkt->data, &crypt_len, pkt->data, crypt_len)) { vpn_progress(vpninfo, PRG_ERR, _("Failed to encrypt ESP packet:\n")); openconnect_report_ssl_errors(vpninfo); return -EINVAL; } HMAC_CTX_copy(&hmac_ctx, &vpninfo->esp_out.hmac); HMAC_Update(&hmac_ctx, (void *)&pkt->esp, sizeof(pkt->esp) + crypt_len); HMAC_Final(&hmac_ctx, pkt->data + crypt_len, &hmac_len); HMAC_CTX_cleanup(&hmac_ctx); return sizeof(pkt->esp) + crypt_len + 12; }
static int pem_pw_cb(char *buf, int len, int w, void *v) { struct openconnect_info *vpninfo = v; char *pass = NULL; int plen; if (vpninfo->cert_password) { pass = vpninfo->cert_password; vpninfo->cert_password = NULL; } else if (request_passphrase(vpninfo, "openconnect_pem", &pass, _("Enter PEM pass phrase:"))) return -1; plen = strlen(pass); if (len <= plen) { vpn_progress(vpninfo, PRG_ERR, _("PEM password too long (%d >= %d)\n"), plen, len); free(pass); return -1; } memcpy(buf, pass, plen+1); free(pass); return plen; }
static int cancellable_connect(struct openconnect_info *vpninfo, int sockfd, const struct sockaddr *addr, socklen_t addrlen) { struct sockaddr_storage peer; socklen_t peerlen = sizeof(peer); fd_set wr_set, rd_set; int maxfd = sockfd; set_sock_nonblock(sockfd); if (vpninfo->protect_socket) vpninfo->protect_socket(vpninfo->cbdata, sockfd); if (connect(sockfd, addr, addrlen) < 0 && !connect_pending()) return -1; do { FD_ZERO(&wr_set); FD_ZERO(&rd_set); FD_SET(sockfd, &wr_set); cmd_fd_set(vpninfo, &rd_set, &maxfd); select(maxfd + 1, &rd_set, &wr_set, NULL, NULL); if (is_cancel_pending(vpninfo, &rd_set)) { vpn_progress(vpninfo, PRG_ERR, _("Socket connect cancelled\n")); errno = EINTR; return -1; } } while (!FD_ISSET(sockfd, &wr_set) && !vpninfo->got_pause_cmd); /* Check whether connect() succeeded or failed by using getpeername(). See http://cr.yp.to/docs/connect.html */ return getpeername(sockfd, (void *)&peer, &peerlen); }
/* In GnuTLS 2.12 since we don't have a normal privkey and hence can't just use gnutls_privkey_sign_data() with it, we have to jump through hoops to prepare the hash in exactly the right way and call our internal TPM signing function. */ int gtls2_tpm_sign_dummy_data(struct openconnect_info *vpninfo, const gnutls_datum_t *data, gnutls_datum_t *sig) { static const unsigned char ber_encode[15] = { 0x30, 0x21, /* SEQUENCE, length 31 */ 0x30, 0x09, /* SEQUENCE, length 9 */ 0x06, 0x05, /* OBJECT_ID, length 5 */ 0x2b, 0x0e, 0x03, 0x02, 0x1a, /* SHA1 OID: 1.3.14.3.2.26 */ 0x05, 0x00, /* NULL (parameters) */ 0x04, 0x14, /* OCTET_STRING, length 20 */ /* followed by the 20-byte sha1 */ }; gnutls_datum_t hash; unsigned char digest[sizeof(ber_encode) + SHA1_SIZE]; size_t shalen = SHA1_SIZE; int err; err = gnutls_fingerprint(GNUTLS_DIG_SHA1, data, &digest[sizeof(ber_encode)], &shalen); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to SHA1 input data for signing: %s\n"), gnutls_strerror(err)); return err; } memcpy(digest, ber_encode, sizeof(ber_encode)); hash.data = digest; hash.size = sizeof(digest); return tpm_sign_fn(NULL, vpninfo, &hash, sig); }
static int local_config_tun(struct openconnect_info *vpninfo, int mtu_only) { if (!mtu_only) vpn_progress(vpninfo, PRG_ERR, _("No vpnc-script configured. Need Solaris IP-setting code\n")); return 0; }
ssize_t read_file_into_string(struct openconnect_info *vpninfo, const char *fname, char **ptr) { int fd, len; struct stat st; char *buf; fd = openconnect_open_utf8(vpninfo, fname, O_RDONLY|O_BINARY); if (fd < 0) { vpn_progress(vpninfo, PRG_ERR, _("Failed to open %s: %s\n"), fname, strerror(errno)); return -ENOENT; } if (fstat(fd, &st)) { vpn_progress(vpninfo, PRG_ERR, _("Failed to fstat() %s: %s\n"), fname, strerror(errno)); close(fd); return -EIO; } len = st.st_size; buf = malloc(len + 1); if (!buf) { vpn_progress(vpninfo, PRG_ERR, _("Failed to allocate %d bytes for %s\n"), len + 1, fname); close(fd); return -ENOMEM; } if (read(fd, buf, len) != len) { vpn_progress(vpninfo, PRG_ERR, _("Failed to read %s: %s\n"), fname, strerror(errno)); free(buf); close(fd); return -EIO; } buf[len] = 0; close(fd); *ptr = buf; return len; }
static SSL_SESSION *generate_dtls_session(struct openconnect_info *vpninfo, int dtlsver, const SSL_CIPHER *cipher) { struct oc_text_buf *buf = buf_alloc(); SSL_SESSION *dtls_session; const unsigned char *asn; uint16_t cid; buf_append_bytes(buf, "\x30\x80", 2); // SEQUENCE, indeterminate length buf_append_INTEGER(buf, 1 /* SSL_SESSION_ASN1_VERSION */); buf_append_INTEGER(buf, dtlsver); store_be16(&cid, SSL_CIPHER_get_id(cipher) & 0xffff); buf_append_OCTET_STRING(buf, &cid, 2); buf_append_OCTET_STRING(buf, vpninfo->dtls_session_id, sizeof(vpninfo->dtls_session_id)); buf_append_OCTET_STRING(buf, vpninfo->dtls_secret, sizeof(vpninfo->dtls_secret)); /* If the length actually fits in one byte (which it should), do * it that way. Else, leave it indeterminate and add two * end-of-contents octets to mark the end of the SEQUENCE. */ if (!buf_error(buf) && buf->pos <= 0x80) buf->data[1] = buf->pos - 2; else buf_append_bytes(buf, "\0\0", 2); if (buf_error(buf)) { vpn_progress(vpninfo, PRG_ERR, _("Failed to create SSL_SESSION ASN.1 for OpenSSL: %s\n"), strerror(buf_error(buf))); buf_free(buf); return NULL; } asn = (void *)buf->data; dtls_session = d2i_SSL_SESSION(NULL, &asn, buf->pos); buf_free(buf); if (!dtls_session) { vpn_progress(vpninfo, PRG_ERR, _("OpenSSL failed to parse SSL_SESSION ASN.1\n")); openconnect_report_ssl_errors(vpninfo); return NULL; } return dtls_session; }
int script_config_tun(struct openconnect_info *vpninfo, const char *reason) { int ret; pid_t pid; if (!vpninfo->vpnc_script || vpninfo->script_tun) return 0; pid = fork(); if (!pid) { /* Child */ char *script = openconnect_utf8_to_legacy(vpninfo, vpninfo->vpnc_script); apply_script_env(vpninfo->script_env); setenv("reason", reason, 1); execl("/bin/sh", "/bin/sh", "-c", script, NULL); exit(127); } if (pid == -1 || waitpid(pid, &ret, 0) == -1) { int e = errno; vpn_progress(vpninfo, PRG_ERR, _("Failed to spawn script '%s' for %s: %s\n"), vpninfo->vpnc_script, reason, strerror(e)); return -e; } if (!WIFEXITED(ret)) { vpn_progress(vpninfo, PRG_ERR, _("Script '%s' exited abnormally (%x)\n"), vpninfo->vpnc_script, ret); return -EIO; } ret = WEXITSTATUS(ret); if (ret) { vpn_progress(vpninfo, PRG_ERR, _("Script '%s' returned error %d\n"), vpninfo->vpnc_script, ret); return -EIO; } return 0; }
/* pkt->len shall be the *payload* length. Omitting the header and the 12-byte HMAC */ 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; 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); if (memcmp(hmac_buf, pkt->data + pkt->len, 12)) { vpn_progress(vpninfo, PRG_DEBUG, _("Received ESP packet with invalid HMAC\n")); return -EINVAL; } /* Why in $DEITY's name would you ever *not* set this? Perhaps we * should do th check anyway, but only warn instead of discarding * the packet? */ if (vpninfo->esp_replay_protect && verify_packet_seqno(vpninfo, esp, ntohl(pkt->esp.seq))) return -EINVAL; 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(&esp->cipher, pkt->data, &crypt_len, pkt->data, pkt->len)) { vpn_progress(vpninfo, PRG_ERR, _("Failed to decrypt ESP packet:\n")); openconnect_report_ssl_errors(vpninfo); return -EINVAL; } return 0; }
static int check_certificate_expiry(struct openconnect_info *vpninfo) { ASN1_TIME *notAfter; const char *reason = NULL; time_t t; int i; if (!vpninfo->cert_x509) return 0; t = time(NULL); notAfter = X509_get_notAfter(vpninfo->cert_x509); i = X509_cmp_time(notAfter, &t); if (!i) { vpn_progress(vpninfo, PRG_ERR, _("Error in client cert notAfter field\n")); return -EINVAL; } else if (i < 0) { reason = _("Client certificate has expired at"); } else { t += vpninfo->cert_expire_warning; i = X509_cmp_time(notAfter, &t); if (i < 0) reason = _("Client certificate expires soon at"); } if (reason) { BIO *bp = BIO_new(BIO_s_mem()); BUF_MEM *bm; const char *expiry = _("<error>"); char zero = 0; if (bp) { ASN1_TIME_print(bp, notAfter); BIO_write(bp, &zero, 1); BIO_get_mem_ptr(bp, &bm); expiry = bm->data; } vpn_progress(vpninfo, PRG_ERR, "%s: %s\n", reason, expiry); if (bp) BIO_free(bp); } return 0; }
int openconnect_setup_dtls(struct openconnect_info *vpninfo, int attempt_period) { if (vpninfo->proto->udp_setup) return vpninfo->proto->udp_setup(vpninfo, attempt_period); vpn_progress(vpninfo, PRG_ERR, _("Built against SSL library with no Cisco DTLS support\n")); return -EINVAL; }
static int tpm_sign_fn(gnutls_privkey_t key, void *_vpninfo, const gnutls_datum_t *data, gnutls_datum_t *sig) { struct openconnect_info *vpninfo = _vpninfo; TSS_HHASH hash; int err; vpn_progress(vpninfo, PRG_TRACE, _("TPM sign function called for %d bytes.\n"), data->size); err = Tspi_Context_CreateObject(vpninfo->tpm_context, TSS_OBJECT_TYPE_HASH, TSS_HASH_OTHER, &hash); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to create TPM hash object: %s\n"), Trspi_Error_String(err)); return GNUTLS_E_PK_SIGN_FAILED; } err = Tspi_Hash_SetHashValue(hash, data->size, data->data); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to set value in TPM hash object: %s\n"), Trspi_Error_String(err)); Tspi_Context_CloseObject(vpninfo->tpm_context, hash); return GNUTLS_E_PK_SIGN_FAILED; } err = Tspi_Hash_Sign(hash, vpninfo->tpm_key, &sig->size, &sig->data); Tspi_Context_CloseObject(vpninfo->tpm_context, hash); if (err) { if (vpninfo->tpm_key_policy || err != TPM_E_AUTHFAIL) vpn_progress(vpninfo, PRG_ERR, _("TPM hash signature failed: %s\n"), Trspi_Error_String(err)); if (err == TPM_E_AUTHFAIL) return GNUTLS_E_INSUFFICIENT_CREDENTIALS; else return GNUTLS_E_PK_SIGN_FAILED; } return 0; }
static int is_pem_password_error(struct openconnect_info *vpninfo) { unsigned long err = ERR_peek_error(); openconnect_report_ssl_errors(vpninfo); #ifndef EVP_F_EVP_DECRYPTFINAL_EX #define EVP_F_EVP_DECRYPTFINAL_EX EVP_F_EVP_DECRYPTFINAL #endif /* If the user fat-fingered the passphrase, try again */ if (ERR_GET_LIB(err) == ERR_LIB_EVP && ERR_GET_FUNC(err) == EVP_F_EVP_DECRYPTFINAL_EX && ERR_GET_REASON(err) == EVP_R_BAD_DECRYPT) { vpn_progress(vpninfo, PRG_ERR, _("Loading private key failed (wrong passphrase?)\n")); ERR_clear_error(); return 1; } vpn_progress(vpninfo, PRG_ERR, _("Loading private key failed (see above errors)\n")); return 0; }