diff --git a/Makefile b/Makefile index 8799015a..5d970f5c 100644 --- a/Makefile +++ b/Makefile @@ -33,10 +33,6 @@ lpass: $(patsubst %.c,%.o,$(wildcard *.c)) %.1.html: %.1.txt asciidoc -b html5 -a data-uri -a icons -a toc2 $< -http.c: certificate.h -certificate.h: thawte.pem - awk 'BEGIN {printf "#define CERTIFICATE_THAWTE \""} {printf "%s\\n", $$0} END {printf "\"\n"}' thawte.pem > certificate.h || rm -f certificate.h - install-doc: doc-man @install -v -d "$(DESTDIR)$(MANDIR)/man1" && install -m 0644 -v lpass.1 "$(DESTDIR)$(MANDIR)/man1/lpass.1" diff --git a/http.c b/http.c index 764c07ed..af010131 100644 --- a/http.c +++ b/http.c @@ -7,7 +7,8 @@ #include "http.h" #include "util.h" #include "version.h" -#include "certificate.h" +#include "pins.h" +#include "cipher.h" #include #include #include @@ -71,32 +72,69 @@ static size_t write_data(char *ptr, size_t size, size_t nmemb, void *data) return len; } -static CURLcode pin_certificate(CURL *curl, void *sslctx, void *parm) +static char *hash_subject_pubkey_info(X509 *cert) { - UNUSED(curl); - UNUSED(parm); - X509_STORE *store; - X509 *cert = NULL; - BIO *bio = NULL; - CURLcode ret = CURLE_SSL_CACERT; - - store = X509_STORE_new(); - if (!store) - goto out; - - bio = BIO_new_mem_buf(CERTIFICATE_THAWTE, -1); - while ((cert = PEM_read_bio_X509(bio, NULL, 0, NULL))) { - if (!X509_STORE_add_cert(store, cert)) { - X509_free(cert); - goto out; + _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; + } } - X509_free(cert); } - SSL_CTX_set_cert_store((SSL_CTX *)sslctx, store); - ret = CURLE_OK; -out: - BIO_free(bio); - return ret; + + 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; } char *http_post_lastpass(const char *page, const char *session, size_t *final_len, ...) @@ -162,7 +200,7 @@ char *http_post_lastpass_v(const char *page, const char *session, size_t *final_ #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_certificate); + 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); diff --git a/pins.h b/pins.h new file mode 100644 index 00000000..721d1976 --- /dev/null +++ b/pins.h @@ -0,0 +1,9 @@ +#ifndef PINS_H +#define PINS_H +const char *PK_PINS[] = { + /* current lastpass.com primary (Thawte) */ + "HXXQgxueCIU5TTLHob/bPbwcKOKw6DkfsTWYHbxbqTY=", + /* current lastpass.eu primary (AddTrust) */ + "lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=", +}; +#endif