From 36b2710e1a1ab94f1e60801a736853ebd76379ce Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Tue, 6 Jul 2021 13:39:18 -0700 Subject: [PATCH] Update curl example to support ssl / https Now that we know our SSL client works, and that it's able to verify certificates, the next step will be adding it as an API to redbean. See #97 --- examples/curl.c | 256 ++++++++++++++++++++++++++++++++++++--------- libc/x/x.h | 2 +- libc/x/xslurp.c | 2 +- tool/net/redbean.c | 5 +- 4 files changed, 212 insertions(+), 53 deletions(-) diff --git a/examples/curl.c b/examples/curl.c index 5915815a4..5b96ccf70 100644 --- a/examples/curl.c +++ b/examples/curl.c @@ -8,24 +8,48 @@ ╚─────────────────────────────────────────────────────────────────*/ #endif #include "libc/bits/safemacros.internal.h" +#include "libc/calls/calls.h" #include "libc/dns/dns.h" +#include "libc/errno.h" #include "libc/fmt/conv.h" #include "libc/fmt/fmt.h" #include "libc/log/check.h" #include "libc/log/log.h" #include "libc/macros.internal.h" +#include "libc/rand/rand.h" #include "libc/runtime/gc.h" #include "libc/runtime/runtime.h" #include "libc/sock/sock.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/sysv/consts/af.h" +#include "libc/sysv/consts/dt.h" #include "libc/sysv/consts/ipproto.h" #include "libc/sysv/consts/shut.h" #include "libc/sysv/consts/sock.h" #include "libc/x/x.h" #include "net/http/http.h" #include "net/http/url.h" +#include "third_party/mbedtls/ctr_drbg.h" +#include "third_party/mbedtls/debug.h" +#include "third_party/mbedtls/error.h" +#include "third_party/mbedtls/ssl.h" + +STATIC_YOINK("zip_uri_support"); +STATIC_YOINK("usr/share/ssl/root/amazon.pem"); +STATIC_YOINK("usr/share/ssl/root/certum.pem"); +STATIC_YOINK("usr/share/ssl/root/comodo.pem"); +STATIC_YOINK("usr/share/ssl/root/digicert.pem"); +STATIC_YOINK("usr/share/ssl/root/dst.pem"); +STATIC_YOINK("usr/share/ssl/root/geotrust.pem"); +STATIC_YOINK("usr/share/ssl/root/globalsign.pem"); +STATIC_YOINK("usr/share/ssl/root/godaddy.pem"); +STATIC_YOINK("usr/share/ssl/root/google.pem"); +STATIC_YOINK("usr/share/ssl/root/isrg.pem"); +STATIC_YOINK("usr/share/ssl/root/quovadis.pem"); +STATIC_YOINK("usr/share/ssl/root/redbean.pem"); +STATIC_YOINK("usr/share/ssl/root/starfield.pem"); +STATIC_YOINK("usr/share/ssl/root/verisign.pem"); /** * @fileoverview Downloads HTTP URL to stdout. @@ -34,6 +58,34 @@ * o//examples/curl.com http://justine.lol/ape.html */ +static int GetEntropy(void *c, unsigned char *p, size_t n) { + CHECK_EQ(n, getrandom(p, n, 0)); + return 0; +} + +static int TlsSend(void *ctx, const unsigned char *buf, size_t len) { + int rc; + CHECK_NE(-1, (rc = write(*(int *)ctx, buf, len))); + return rc; +} + +static int TlsRecv(void *ctx, unsigned char *buf, size_t len, uint32_t tmo) { + int rc; + CHECK_NE(-1, (rc = read(*(int *)ctx, buf, len))); + return rc; +} + +static void TlsDebug(void *ctx, int level, const char *file, int line, + const char *message) { + flogf(level, file, line, 0, "TLS %s", message); +} + +static char *TlsError(int rc) { + static char ebuf[128]; + mbedtls_strerror(rc, ebuf, sizeof(ebuf)); + return ebuf; +} + int main(int argc, char *argv[]) { /* @@ -41,7 +93,7 @@ int main(int argc, char *argv[]) { */ const char *urlarg; if (argc != 2) { - fprintf(stderr, "USAGE: %s URL\n", argv[0]); + fprintf(stderr, "usage: %s URL\n", argv[0]); exit(1); } urlarg = argv[1]; @@ -51,18 +103,23 @@ int main(int argc, char *argv[]) { */ struct Url url; char *host, *port; + bool usessl = false; _gc(ParseUrl(urlarg, -1, &url)); _gc(url.params.p); - if (url.scheme.n && - !(url.scheme.n == 4 && !memcasecmp(url.scheme.p, "http", 4))) { - fprintf(stderr, "ERROR: NOT AN HTTP URL: %s\n", urlarg); - exit(1); + if (url.scheme.n) { + if (url.scheme.n == 5 && !memcasecmp(url.scheme.p, "https", 5)) { + usessl = true; + } else if (!(url.scheme.n == 4 && !memcasecmp(url.scheme.p, "http", 4))) { + fprintf(stderr, "error: not an http/https url: %s\n", urlarg); + exit(1); + } } host = firstnonnull(_gc(strndup(url.host.p, url.host.n)), "127.0.0.1"); - port = url.port.n ? _gc(strndup(url.port.p, url.port.n)) : "80"; + port = url.port.n ? _gc(strndup(url.port.p, url.port.n)) + : (usessl ? "443" : "80"); port = _gc(xasprintf("%hu", atoi(port))); if (!IsAcceptableHost(host, -1)) { - fprintf(stderr, "ERROR: INVALID HOST: %s\n", urlarg); + fprintf(stderr, "error: invalid host: %s\n", urlarg); exit(1); } url.fragment.p = 0, url.fragment.n = 0; @@ -87,62 +144,163 @@ int main(int argc, char *argv[]) { "Connection: close\r\n" "Content-Length: 0\r\n" "Accept: text/plain; */*\r\n" - "Accept-Encoding: identity\r\n" "User-Agent: github.com/jart/cosmopolitan\r\n" "\r\n", _gc(EncodeUrl(&url, 0)), host, port)); + /* + * Load root certificates. + */ + mbedtls_x509_crt cacert; + if (usessl) { + DIR *zd; + size_t calen; + const char *dir; + char capath[300]; + uint8_t *cabytes; + struct dirent *ze; + mbedtls_x509_crt_init(&cacert); + dir = "zip:usr/share/ssl/root"; + CHECK((zd = opendir(dir)), "%s", dir); + while ((ze = readdir(zd))) { + if (ze->d_type != DT_REG) continue; + snprintf(capath, sizeof(capath), "%s/%s", dir, ze->d_name); + CHECK((cabytes = xslurp(capath, &calen))); + CHECK_EQ(0, mbedtls_x509_crt_parse(&cacert, cabytes, calen + 1), "%s", + capath); + free(cabytes); + } + closedir(zd); + } + + /* + * Setup crypto. + */ + mbedtls_ssl_config conf; + mbedtls_ssl_context ssl; + mbedtls_ctr_drbg_context drbg; + if (usessl) { + mbedtls_ssl_init(&ssl); + mbedtls_ctr_drbg_init(&drbg); + mbedtls_ssl_config_init(&conf); + CHECK_EQ(0, mbedtls_ctr_drbg_seed(&drbg, GetEntropy, 0, + (const uint8_t *)"justine", 7)); + CHECK_EQ(0, mbedtls_ssl_config_defaults(&conf, MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT)); + mbedtls_ssl_conf_ca_chain(&conf, &cacert, 0); + mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED); + mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &drbg); + mbedtls_ssl_conf_dbg(&conf, TlsDebug, 0); + /* mbedtls_debug_set_threshold(5); */ + CHECK_EQ(0, mbedtls_ssl_setup(&ssl, &conf)); + CHECK_EQ(0, mbedtls_ssl_set_hostname(&ssl, host)); + } + /* * Perform DNS lookup. */ - struct addrinfo *addr, *addrs; + struct addrinfo *addr; struct addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_TCP, .ai_flags = AI_NUMERICSERV}; - CHECK_EQ(EAI_SUCCESS, getaddrinfo(host, port, &hints, &addrs)); - for (addr = addrs; addr; addr = addr->ai_next) { + CHECK_EQ(EAI_SUCCESS, getaddrinfo(host, port, &hints, &addr)); - /* - * Send HTTP Message. - */ - int sock; - CHECK_NE(-1, (sock = socket(addr->ai_family, addr->ai_socktype, - addr->ai_protocol))); - CHECK_NE(-1, connect(sock, addr->ai_addr, addr->ai_addrlen)); + /* + * Connect to server. + */ + int ret, sock; + CHECK_NE(-1, (sock = socket(addr->ai_family, addr->ai_socktype, + addr->ai_protocol))); + CHECK_NE(-1, connect(sock, addr->ai_addr, addr->ai_addrlen)); + if (usessl) { + mbedtls_ssl_set_bio(&ssl, &sock, TlsSend, 0, TlsRecv); + if ((ret = mbedtls_ssl_handshake(&ssl))) { + FATALF("ssl handshake failed (-0x%04x %s)", -ret, TlsError(ret)); + } + } + + /* + * Send HTTP Message. + */ + if (usessl) { + CHECK_EQ(strlen(msg), mbedtls_ssl_write(&ssl, (void *)msg, strlen(msg))); + } else { CHECK_EQ(strlen(msg), write(sock, msg, strlen(msg))); shutdown(sock, SHUT_WR); - - /* - * Handle response. - */ - ssize_t rc; - char buf[1500]; - size_t got, toto; - unsigned long need; - const char *msg, *crlfcrlf; - buf[0] = '\0'; - CHECK_NE(-1, (rc = read(sock, buf, sizeof(buf)))); - got = rc; - CHECK(startswith(buf, "HTTP/1.1 200"), "%`'.*s", got, buf); - CHECK_NOTNULL((crlfcrlf = memmem(buf, got, "\r\n\r\n", 4))); - need = strtol((char *)firstnonnull( - memmem(buf, crlfcrlf - buf, "\r\nContent-Length: ", 18), - firstnonnull(memmem(buf, crlfcrlf - buf, - "\r\ncontent-length: ", 18), - "\r\nContent-Length: -1")) + - 18, - NULL, 10); - got = MIN(got - (crlfcrlf + 4 - buf), need); - CHECK_EQ(got, write(1, crlfcrlf + 4, got)); - for (toto = got; toto < need; toto += got) { - CHECK_NE(-1, (rc = read(sock, buf, sizeof(buf)))); - if (!(got = rc)) exit(18); - got = MIN(got, need - toto); - CHECK_EQ(got, write(1, buf, got)); - } - close(sock); - break; } + + /* + * Handle response. + * TODO(jart): use response/chunk parsers + */ + char buf[1500]; + size_t got, toto; + ssize_t rc, need; + const char *crlfcrlf; + buf[0] = '\0'; + if (usessl) { + if ((rc = mbedtls_ssl_read(&ssl, (void *)buf, sizeof(buf))) < 0) { + FATALF("ssl read failed (-0x%04x %s)", -ret, TlsError(rc)); + } + } else { + CHECK_NE(-1, (rc = read(sock, buf, sizeof(buf)))); + } + got = rc; + CHECK(startswith(buf, "HTTP/1.1 200") || startswith(buf, "HTTP/1.0 200"), + "%`'.*s", got, buf); + CHECK_NOTNULL((crlfcrlf = memmem(buf, got, "\r\n\r\n", 4))); + need = strtol( + (char *)firstnonnull( + memmem(buf, crlfcrlf - buf, "\r\nContent-Length: ", 18), + firstnonnull(memmem(buf, crlfcrlf - buf, "\r\ncontent-length: ", 18), + "\r\nContent-Length: -1")) + + 18, + NULL, 10); + got = MIN(got - (crlfcrlf + 4 - buf), need); + CHECK_EQ(got, write(1, crlfcrlf + 4, got)); + for (toto = got; need == -1 || toto < need; toto += got) { + if (usessl) { + if ((rc = mbedtls_ssl_read(&ssl, (void *)buf, sizeof(buf))) < 0) { + if (rc == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { + CHECK_EQ(0, mbedtls_ssl_close_notify(&ssl)); + got = 0; + break; + } else { + FATALF("ssl read failed (-0x%04x %s)", -rc, TlsError(rc)); + } + } + } else { + CHECK_NE(-1, (rc = read(sock, buf, sizeof(buf)))); + } + if (!(got = rc)) break; + CHECK_EQ(got, write(1, buf, got)); + } + if (need != -1) { + CHECK_EQ(need, toto); + } + + /* + * Close connection. + */ + if (got) { + if (usessl) { + CHECK_EQ(0, mbedtls_ssl_close_notify(&ssl)); + CHECK_EQ(MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY, + mbedtls_ssl_read(&ssl, (void *)buf, 1)); + } else { + shutdown(sock, SHUT_RDWR); + CHECK_EQ(0, read(sock, buf, 1)); + } + } + CHECK_NE(-1, close(sock)); + if (usessl) { + mbedtls_ssl_free(&ssl); + mbedtls_x509_crt_free(&cacert); + mbedtls_ssl_config_free(&conf); + mbedtls_ctr_drbg_free(&drbg); + } + return 0; } diff --git a/libc/x/x.h b/libc/x/x.h index e842ddbd0..0a02fd3d4 100644 --- a/libc/x/x.h +++ b/libc/x/x.h @@ -75,7 +75,7 @@ char *xiso8601ts(struct timespec *) mallocesque; │ cosmopolitan § eXtended apis » input / output ─╬─│┼ ╚────────────────────────────────────────────────────────────────────────────│*/ -char *xslurp(const char *, size_t *) +void *xslurp(const char *, size_t *) paramsnonnull((1)) returnspointerwithnoaliases returnsaligned((PAGESIZE)) nodiscard; int xbarf(const char *, const void *, size_t); diff --git a/libc/x/xslurp.c b/libc/x/xslurp.c index bbaa536ce..a99bb093e 100644 --- a/libc/x/xslurp.c +++ b/libc/x/xslurp.c @@ -30,7 +30,7 @@ * @return NUL-terminated malloc'd contents, or NULL w/ errno * @note this is uninterruptible */ -char *xslurp(const char *path, size_t *opt_out_size) { +void *xslurp(const char *path, size_t *opt_out_size) { int fd; ssize_t rc; size_t i, got; diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 42baea130..d097ff3a1 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -97,6 +97,7 @@ #include "third_party/mbedtls/ecp.h" #include "third_party/mbedtls/entropy.h" #include "third_party/mbedtls/entropy_poll.h" +#include "third_party/mbedtls/iana.h" #include "third_party/mbedtls/md5.h" #include "third_party/mbedtls/oid.h" #include "third_party/mbedtls/pk.h" @@ -1464,8 +1465,8 @@ static bool TlsSetup(void) { DEBUGF("%s SSL shakealert unknown ca", DescribeClient()); return false; default: - WARNF("%s SSL shakealert %hhu", DescribeClient(), - ssl.fatal_alert); + WARNF("%s SSL shakealert %s", DescribeClient(), + GetAlertDescription(ssl.fatal_alert)); return false; } default: