// testHttps.c -- exercises calogHttp's TLS certificate verification. An in-test HTTPS server // runs on loopback with a freshly generated SELF-SIGNED certificate (not chained to any trusted // CA). A default httpGet must therefore REJECT it (verification on), while an httpRequest with // insecure=true must ACCEPT it (verification off). Proves both the default-secure behavior and // the documented opt-out. #define _GNU_SOURCE #include "calog.h" #include "calogHttp.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define CHECK(cond, msg) checkImpl((cond), (msg), __FILE__, __LINE__) #define PUMP_LIMIT 4000 #define RESULT_SLOTS 8 static CalogT *calog = NULL; static _Atomic int64_t results[RESULT_SLOTS]; static _Atomic int32_t doneCount = 0; static _Atomic int32_t errorCount = 0; static int32_t testsRun = 0; static int32_t testsFailed = 0; static int gListenFd = -1; static void checkImpl(bool condition, const char *message, const char *file, int32_t line); static EVP_PKEY *makeSelfSigned(X509 **certOut); static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData); static void onError(uint64_t contextId, const char *message, void *userData); static void pumpUntilDone(int32_t target); static void *serverThread(void *arg); static void checkImpl(bool condition, const char *message, const char *file, int32_t line) { testsRun++; if (!condition) { testsFailed++; printf("FAIL %s:%d %s\n", file, line, message); } } static EVP_PKEY *makeSelfSigned(X509 **certOut) { EVP_PKEY *pkey; X509 *cert; pkey = EVP_RSA_gen(2048); if (pkey == NULL) { return NULL; } cert = X509_new(); if (cert == NULL) { EVP_PKEY_free(pkey); return NULL; } X509_set_version(cert, 2); ASN1_INTEGER_set(X509_get_serialNumber(cert), 1); X509_gmtime_adj(X509_getm_notBefore(cert), 0); X509_gmtime_adj(X509_getm_notAfter(cert), 3600); X509_set_pubkey(cert, pkey); X509_NAME_add_entry_by_txt(X509_get_subject_name(cert), "CN", MBSTRING_ASC, (const unsigned char *)"127.0.0.1", -1, -1, 0); X509_set_issuer_name(cert, X509_get_subject_name(cert)); if (X509_sign(cert, pkey, EVP_sha256()) == 0) { X509_free(cert); EVP_PKEY_free(pkey); return NULL; } *certOut = cert; return pkey; } static int32_t nativeDone(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { (void)args; (void)argCount; (void)userData; atomic_fetch_add(&doneCount, 1); calogValueNil(result); return calogOkE; } static int32_t nativeReport(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) { (void)userData; calogValueNil(result); if (argCount != 2 || args[0].type != calogIntE) { return calogFail(result, calogErrArgE, "report expects (tag, value)"); } if (args[0].as.i < 0 || args[0].as.i >= RESULT_SLOTS) { return calogFail(result, calogErrArgE, "report: tag out of range"); } atomic_store(&results[args[0].as.i], (args[1].type == calogIntE) ? args[1].as.i : (int64_t)args[1].as.r); return calogOkE; } static void onError(uint64_t contextId, const char *message, void *userData) { (void)contextId; (void)userData; fprintf(stderr, " [script error] %s\n", (message != NULL) ? message : "(null)"); atomic_fetch_add(&errorCount, 1); } static void pumpUntilDone(int32_t target) { struct timespec ts = { 0, 500000 }; int i; for (i = 0; i < PUMP_LIMIT; i++) { calogPump(calog); if (atomic_load(&doneCount) >= target) { calogPump(calog); return; } nanosleep(&ts, NULL); } } static void *serverThread(void *arg) { static const char response[] = "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello"; SSL_CTX *ctx; EVP_PKEY *pkey; X509 *cert; int n; (void)arg; cert = NULL; pkey = makeSelfSigned(&cert); if (pkey == NULL) { return NULL; } ctx = SSL_CTX_new(TLS_server_method()); if (ctx == NULL) { X509_free(cert); EVP_PKEY_free(pkey); return NULL; } SSL_CTX_use_certificate(ctx, cert); SSL_CTX_use_PrivateKey(ctx, pkey); // Two connections: the first (default verify) has its handshake aborted by the client when // it rejects the untrusted cert; the second (insecure=true) completes and gets the reply. for (n = 0; n < 2; n++) { SSL *ssl; int cfd; cfd = accept(gListenFd, NULL, NULL); if (cfd < 0) { break; } ssl = SSL_new(ctx); SSL_set_fd(ssl, cfd); if (SSL_accept(ssl) == 1) { char buf[1024]; (void)SSL_read(ssl, buf, sizeof(buf)); (void)SSL_write(ssl, response, (int)(sizeof(response) - 1)); SSL_shutdown(ssl); } SSL_free(ssl); close(cfd); } SSL_CTX_free(ctx); X509_free(cert); EVP_PKEY_free(pkey); return NULL; } int main(void) { CalogContextT *ctx; pthread_t server; struct sockaddr_in addr; socklen_t addrLen; char script[512]; int port; int yes; int32_t i; calog = calogCreate(); if (calog == NULL) { printf("calog create failed\n"); return 1; } calogSetErrorHandler(calog, onError, NULL); calogRegister(calog, "report", nativeReport, NULL); calogRegister(calog, "done", nativeDone, NULL); calogHttpRegister(calog); for (i = 0; i < RESULT_SLOTS; i++) { atomic_store(&results[i], -1); } gListenFd = socket(AF_INET, SOCK_STREAM, 0); if (gListenFd < 0) { printf("socket failed\n"); return 1; } yes = 1; setsockopt(gListenFd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); addr.sin_port = 0; if (bind(gListenFd, (struct sockaddr *)&addr, sizeof(addr)) != 0 || listen(gListenFd, 4) != 0) { printf("bind/listen failed\n"); return 1; } addrLen = sizeof(addr); if (getsockname(gListenFd, (struct sockaddr *)&addr, &addrLen) != 0) { printf("getsockname failed\n"); return 1; } port = (int)ntohs(addr.sin_port); pthread_create(&server, NULL, serverThread, NULL); snprintf(script, sizeof(script), "local ok = pcall(function() return httpGet('https://127.0.0.1:%d/') end)\n" "report(1, ok and 0 or 1)\n" // 1, a self-signed cert is rejected by default "local r = httpRequest({url = 'https://127.0.0.1:%d/', insecure = true})\n" "report(2, r.status)\n" // 200, insecure skips verification "report(3, r.body == 'hello' and 1 or 0)\n" // 1, body over TLS "done()", port, port); ctx = calogContextOpen(calog, &calogLuaEngine); calogContextEval(ctx, script); pumpUntilDone(1); pthread_join(server, NULL); close(gListenFd); CHECK(atomic_load(&results[1]) == 1, "httpGet rejects an untrusted self-signed certificate by default"); CHECK(atomic_load(&results[2]) == 200, "httpRequest with insecure=true completes the TLS request"); CHECK(atomic_load(&results[3]) == 1, "the body is delivered over TLS when verification is skipped"); CHECK(atomic_load(&errorCount) == 0, "no uncaught errors"); calogContextClose(ctx); calogDestroy(calog); printf("\n%d checks, %d failed\n", testsRun, testsFailed); fflush(stdout); if (testsFailed != 0) { return 1; } return 0; }