/* * http posting routines * * Copyright (C) 2014-2018 LastPass. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. If you * do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source * files in the program, then also delete it here. * * See LICENSE.OpenSSL for more details regarding this exception. */ #include "http.h" #include "log.h" #include "util.h" #include "version.h" #include "pins.h" #include "cipher.h" #include #include #include #include #include #include #include struct mem_chunk { char *ptr; size_t len; }; #ifndef TEST_BUILD static bool interrupted = false; static sig_t previous_handler = SIG_DFL; static void interruption_detected(int signal) { UNUSED(signal); interrupted = true; } static void set_interrupt_detect(void) { interrupted = false; previous_handler = signal(SIGINT, interruption_detected); } static void unset_interrupt_detect(void) { interrupted = false; signal(SIGINT, previous_handler); } static int check_interruption(void *p, double dltotal, double dlnow, double ultotal, double ulnow) { UNUSED(p); UNUSED(dltotal); UNUSED(dlnow); UNUSED(ultotal); UNUSED(ulnow); return interrupted; } static size_t write_data(char *ptr, size_t size, size_t nmemb, void *data) { size_t len, new_len; struct mem_chunk *mem = (struct mem_chunk *)data; if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && nmemb > 0 && SIZE_MAX / nmemb < size) { errno = ENOMEM; return 0; } len = size * nmemb; new_len = len + mem->len + 1; if (new_len <= mem->len || new_len <= len || new_len < 1) { errno = ENOMEM; return 0; } mem->ptr = xrealloc(mem->ptr, new_len); memcpy(mem->ptr + mem->len, ptr, len); mem->len += len; mem->ptr[mem->len] = '\0'; return len; } static char *hash_subject_pubkey_info(X509 *cert) { _cleanup_free_ unsigned char *spki = NULL; char *hash = NULL; EVP_PKEY *pkey; int len; pkey = X509_get_pubkey(cert); if (!pkey) return NULL; len = i2d_PUBKEY(pkey, &spki); if (len <= 0) goto free_pkey; hash = cipher_sha256_b64(spki, len); free_pkey: EVP_PKEY_free(pkey); return hash; } static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) { int i, j; /* * Preverify checks the platform's certificate store; don't * allow any chain that doesn't already validate according to * that. */ if (!preverify_ok) return 0; /* check each certificate in the chain against our built-in pinlist. */ STACK_OF(X509) *chain = X509_STORE_CTX_get_chain(ctx); if (!chain) die("No certificate chain available"); bool found = false; for (i=0; i < sk_X509_num(chain); i++) { _cleanup_free_ char *spki_hash = NULL; spki_hash = hash_subject_pubkey_info(sk_X509_value(chain, i)); if (!spki_hash) continue; for (j=0; j < (int) ARRAY_SIZE(PK_PINS); j++) { if (strcmp(PK_PINS[j], spki_hash) == 0) { found = true; break; } } } return found; } static CURLcode pin_keys(CURL *curl, void *sslctx, void *parm) { UNUSED(curl); UNUSED(parm); SSL_CTX_set_verify((SSL_CTX *)sslctx, SSL_VERIFY_PEER, verify_callback); return CURLE_OK; } #endif static void vhttp_post_add_params(struct http_param_set *param_set, va_list args) { char **argv_ptr; char *arg; size_t count = 0; if (!param_set->argv) { param_set->n_alloced = 2; param_set->argv = xcalloc(param_set->n_alloced, sizeof(char *)); } argv_ptr = param_set->argv; while (*argv_ptr) { argv_ptr++; count++; } while ((arg = va_arg(args, char *))) { if (count == param_set->n_alloced - 1) { param_set->n_alloced += 2; param_set->argv = xreallocarray(param_set->argv, param_set->n_alloced, sizeof(char *)); argv_ptr = ¶m_set->argv[count]; } *argv_ptr++ = arg; count++; } *argv_ptr = 0; } int http_init() { curl_global_cleanup(); return curl_global_init(CURL_GLOBAL_DEFAULT); } void http_post_add_params(struct http_param_set *param_set, ...) { va_list args; va_start(args, param_set); vhttp_post_add_params(param_set, args); va_end(args); } char *http_post_lastpass(const char *page, const struct session *session, size_t *final_len, ...) { va_list args; struct http_param_set params = { .argv = NULL, .n_alloced = 0 }; va_start(args, final_len); vhttp_post_add_params(¶ms, args); char *result = http_post_lastpass_param_set(page, session, final_len, ¶ms); free(params.argv); return result; } #ifndef TEST_BUILD char *http_post_lastpass_v_noexit(const char *server, const char *page, const struct session *session, size_t *final_len, char **argv, int *curl_ret, long *http_code) { _cleanup_free_ char *url = NULL; _cleanup_free_ char *postdata = NULL; _cleanup_free_ char *cookie = NULL; _cleanup_fclose_ FILE *logstream = NULL; char *param, *encoded_param; CURL *curl = NULL; char separator; size_t len, new_len; int ret; struct mem_chunk result; const char *login_server; /* if we have a session, use that server, otherwise use whatever was passed */ login_server = session ? session->server : server; /* if nothing passed, use lastpass */ if (!login_server) login_server = LASTPASS_SERVER; xasprintf(&url, "https://%s/%s", login_server, page); lpass_log(LOG_DEBUG, "Making request to %s\n", url); curl = curl_easy_init(); if (!curl) die("Could not init curl"); len = 0; for (separator = '=', param = *argv; param; separator = (separator == '=') ? '&' : '=', param = *(++argv)) { encoded_param = curl_easy_escape(curl, param, 0); if (!encoded_param) die("Could not escape %s with curl", param); new_len = strlen(encoded_param) + 1 /* separator */; postdata = xrealloc(postdata, len + new_len + 1 /* null */); snprintf(postdata + len, new_len + 1, "%s%c", encoded_param, separator); len += new_len; curl_free(encoded_param); } if (len && postdata) postdata[len - 1] = '\0'; memset(&result, 0, sizeof(result)); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_USERAGENT, LASTPASS_CLI_USERAGENT); /* TODO: Make this optional via either env vars and/or an option for * lpass -4 or lpass -6 */ curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); if (lpass_log_level() >= LOG_VERBOSE) { logstream = lpass_log_open(); if (logstream) { curl_easy_setopt(curl, CURLOPT_STDERR, logstream); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); } } #if defined(DO_NOT_ENABLE_ME_MITM_PROXY_FOR_DEBUGGING_ONLY) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); curl_easy_setopt(curl, CURLOPT_PROXY, "http://localhost:8080"); #else curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, pin_keys); #endif curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, check_interruption); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); if (postdata) curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postdata); if (session) { xasprintf(&cookie, "PHPSESSID=%s", session->sessionid); curl_easy_setopt(curl, CURLOPT_COOKIE, cookie); } set_interrupt_detect(); ret = curl_easy_perform(curl); unset_interrupt_detect(); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, http_code); curl_easy_cleanup(curl); *curl_ret = ret; if (ret != CURLE_OK) { result.len = 0; free(result.ptr); result.ptr = NULL; } else if (!result.ptr) result.ptr = xstrdup(""); if (final_len) *final_len = result.len; return result.ptr; } #endif char *http_post_lastpass_v(const char *server, const char *page, const struct session *session, size_t *final_len, char **argv) { char *result; int ret; long http_code; result = http_post_lastpass_v_noexit(server, page, session, final_len, argv, &ret, &http_code); if (ret != CURLE_OK && ret != CURLE_ABORTED_BY_CALLBACK) die("%s.", curl_easy_strerror(ret)); return result; } char *http_post_lastpass_param_set(const char *page, const struct session *session, size_t *final_len, struct http_param_set *param_set) { return http_post_lastpass_v(NULL, page, session, final_len, param_set->argv); }