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
This commit is contained in:
Justine Tunney 2021-07-06 13:39:18 -07:00
parent e51034bab3
commit 36b2710e1a
4 changed files with 212 additions and 53 deletions

View file

@ -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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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: