/* ocsp_responder.c * * Copyright (C) 2006-2026 wolfSSL Inc. * * This file is part of wolfSSL. * * wolfSSL 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 3 of the License, or * (at your option) any later version. * * wolfSSL 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-1335, USA */ /* Example OCSP responder used for interoperability and stapling testing. * This code is for demonstration/testing only and is not hardened for * secure or production use. Do not use this as a reference implementation * for deploying an OCSP responder in production. */ #ifdef HAVE_CONFIG_H #include #endif #ifndef WOLFSSL_USER_SETTINGS #include #endif #include #undef TEST_OPENSSL_COEXIST /* can't use this option with this example */ #undef OPENSSL_COEXIST /* can't use this option with this example */ #include #include #include #include #include #include /* Check if we have the required features */ #if defined(HAVE_OCSP) && defined(HAVE_OCSP_RESPONDER) && \ !defined(NO_FILESYSTEM) #include #include #include /* Define mygetopt variables (used by mygetopt_long in test.h) */ #ifndef NO_MAIN_DRIVER int myoptind = 0; char* myoptarg = NULL; #endif #ifdef _WIN32 #include #include #define SOCKET_T SOCKET #define close(s) closesocket(s) #else #include #include #include #include #include #include #include #define SOCKET_T int #ifndef INVALID_SOCKET #define INVALID_SOCKET (-1) #endif #define SOCKET_ERROR (-1) #endif /* Default values */ #define DEFAULT_PORT 8888 #define MAX_REQUEST_SIZE 65536 #define MAX_RESPONSE_SIZE 65536 #define MAX_HTTP_HEADER 4096 #define MAX_PATH_LEN 256 #define MAX_CERTS 16 #define LOG_MSG(...) \ do { \ printf(__VA_ARGS__); \ fflush(stdout); \ } while(0) #ifndef _WIN32 /* Signal handler flag */ static volatile int got_signal = 0; static void sig_handler(int sig) { (void)sig; got_signal = 1; } /* Simple logging macro */ #define LOG_ERROR(...) \ do { \ if (got_signal) \ fprintf(stderr, "Shutdown requested, exiting loop\n"); \ else \ fprintf(stderr, __VA_ARGS__); \ } while (0) #else /* Simple logging macro */ #define LOG_ERROR(...) \ do { \ fprintf(stderr, __VA_ARGS__); \ } while (0) #endif /* Index file entry structure */ typedef struct IndexEntry { char status; /* V=valid, R=revoked, E=expired */ time_t expirationTime; time_t revocationTime; char serial[64]; char filename[256]; char subject[512]; struct IndexEntry* next; } IndexEntry; /* Program options */ typedef struct { word16 port; const char* certFile; const char* responderCertFile; const char* keyFile; const char* indexFile; const char* readyFile; int nrequests; int verbose; int sendCerts; } OcspResponderOptions; /* Usage help */ static void Usage(void) { LOG_MSG("OCSP Responder Example\n\n"); LOG_MSG("Usage: ocsp_responder [options]\n\n"); LOG_MSG("Options:\n"); LOG_MSG(" -? Help\n"); LOG_MSG(" -p Port (default %d)\n", DEFAULT_PORT); LOG_MSG(" -c CA certificate (issuer)\n"); LOG_MSG(" -r Responder certificate" " (for authorized responder)\n"); LOG_MSG(" -k Signing private key\n"); LOG_MSG(" -i Index file for cert status\n"); LOG_MSG(" -R Ready file for external monitor\n"); LOG_MSG(" -n Exit after n requests\n"); LOG_MSG(" -v Verbose\n"); LOG_MSG(" -x Exclude certs from response\n"); } /* Load file into buffer, auto-detect PEM vs DER */ static int LoadFile(const char* filename, byte** buf, word32* bufSz, int* isPem) { int ret; size_t sz = 0; ret = load_file(filename, buf, &sz); if (ret != 0) { LOG_ERROR("Error opening file: %s\n", filename); return ret; } /* Check if PEM format by looking for -----BEGIN */ if (isPem) { /* Reallocate with space for null terminator for XSTRSTR */ byte* tmp = (byte*)XREALLOC(*buf, (word32)sz + 1, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (tmp == NULL) { XFREE(*buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); *buf = NULL; return MEMORY_E; } *buf = tmp; (*buf)[sz] = '\0'; *isPem = (XSTRSTR((char*)*buf, "-----BEGIN") != NULL) ? 1 : 0; } *bufSz = (word32)sz; return 0; } /* Convert PEM to DER */ static int ConvertPemToDer(const byte* pem, word32 pemSz, byte** der, word32* derSz, int type) { int ret; /* Allocate buffer large enough for DER (always smaller than PEM) */ *der = (byte*)XMALLOC(pemSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (*der == NULL) { return MEMORY_E; } if (type == PRIVATEKEY_TYPE) { ret = wc_KeyPemToDer(pem, (int)pemSz, *der, (int)pemSz, NULL); } else { ret = wc_CertPemToDer(pem, (int)pemSz, *der, (int)pemSz, type); } if (ret <= 0) { XFREE(*der, NULL, DYNAMIC_TYPE_TMP_BUFFER); *der = NULL; if (ret == 0) ret = BAD_FUNC_ARG; return ret; } *derSz = (word32)ret; return 0; } /* Load certificate in DER format */ static int LoadCertDer(const char* filename, byte** der, word32* derSz) { byte* buf = NULL; word32 bufSz = 0; int isPem = 0; int ret; ret = LoadFile(filename, &buf, &bufSz, &isPem); if (ret != 0) { return ret; } if (isPem) { ret = ConvertPemToDer(buf, bufSz, der, derSz, CERT_TYPE); XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); return ret; } else { *der = buf; *derSz = bufSz; return 0; } } /* Load private key in DER format */ static int LoadKeyDer(const char* filename, byte** der, word32* derSz) { byte* buf = NULL; word32 bufSz = 0; int isPem = 0; int ret; ret = LoadFile(filename, &buf, &bufSz, &isPem); if (ret != 0) { return ret; } if (isPem) { ret = ConvertPemToDer(buf, bufSz, der, derSz, PRIVATEKEY_TYPE); XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); return ret; } else { *der = buf; *derSz = bufSz; return 0; } } /* Free index entries */ static void FreeIndexEntries(IndexEntry* head) { while (head) { IndexEntry* next = head->next; XFREE(head, NULL, DYNAMIC_TYPE_TMP_BUFFER); head = next; } } /* Parse OpenSSL index.txt file * Format: status\texpiration\trevocation\tserial\tfilename\tsubject * V = valid, R = revoked, E = expired */ static IndexEntry* ParseIndexFile(const char* filename) { XFILE f = XBADFILE; char line[1024]; IndexEntry* head = NULL; IndexEntry* tail = NULL; IndexEntry* entry = NULL; IndexEntry* ret = NULL; if (filename == NULL) { LOG_ERROR("Invalid filename parameter\n"); goto cleanup; } f = XFOPEN(filename, "r"); if (f == XBADFILE) { LOG_ERROR("Error opening index file: %s\n", filename); goto cleanup; } while (XFGETS(line, sizeof(line), f) != NULL) { char* p = line; char* field; int fieldNum = 0; /* Skip empty lines */ if (line[0] == '\n' || line[0] == '\r' || line[0] == '\0') continue; entry = (IndexEntry*)XMALLOC(sizeof(IndexEntry), NULL, DYNAMIC_TYPE_TMP_BUFFER); if (entry == NULL) { LOG_ERROR("Memory allocation failed for index entry\n"); goto cleanup; } XMEMSET(entry, 0, sizeof(IndexEntry)); /* Parse tab-separated fields */ while ((field = XSTRSEP(&p, "\t")) != NULL && fieldNum < 6) { switch (fieldNum) { case 0: /* Status */ entry->status = field[0]; break; case 1: /* Expiration time (YYMMDDHHMMSSZ format) */ /* Parse if needed */ break; case 2: /* Revocation time */ /* Parse if needed, empty for valid certs */ if (field[0] != '\0') { struct tm tm; XMEMSET(&tm, 0, sizeof(tm)); if (wc_GetDateAsCalendarTime((const byte*)field, (int)XSTRLEN(field), ASN_UTC_TIME, &tm) != 0) { LOG_ERROR("Invalid revocation time" " format: %s\n", field); entry->revocationTime = (time_t)-1; break; } entry->revocationTime = XMKTIME(&tm); } break; case 3: /* Serial (hex) */ XSTRNCPY(entry->serial, field, sizeof(entry->serial) - 1); break; case 4: /* Filename */ XSTRNCPY(entry->filename, field, sizeof(entry->filename) - 1); break; case 5: /* Subject */ /* Remove trailing newline */ { size_t len = XSTRLEN(field); if (len > 0 && (field[len-1] == '\n' || field[len-1] == '\r')) field[len-1] = '\0'; if (len > 1 && (field[len-2] == '\n' || field[len-2] == '\r')) field[len-2] = '\0'; } XSTRNCPY(entry->subject, field, sizeof(entry->subject) - 1); break; } fieldNum++; } /* Validate that we got at least status and serial */ if (fieldNum < 4 || entry->serial[0] == '\0' || entry->revocationTime == (time_t)-1) { LOG_ERROR("Invalid index entry - missing required fields\n"); XFREE(entry, NULL, DYNAMIC_TYPE_TMP_BUFFER); entry = NULL; goto cleanup; } /* Add to list */ entry->next = NULL; if (tail) { tail->next = entry; tail = entry; } else { head = tail = entry; } entry = NULL; } /* Success */ ret = head; head = NULL; cleanup: if (f != XBADFILE) XFCLOSE(f); if (entry != NULL) XFREE(entry, NULL, DYNAMIC_TYPE_TMP_BUFFER); FreeIndexEntries(head); return ret; } /* Lookup certificate status by serial number */ static int PopulateResponderFromIndex(OcspResponder* responder, IndexEntry* index, DecodedCert* caCert) { IndexEntry* entry; char caSubjectBuf[WC_ASN_NAME_MAX]; word32 caSubjSz = sizeof(caSubjectBuf); int count = 0; int ret; if (responder == NULL || index == NULL || caCert == NULL) { return BAD_FUNC_ARG; } ret = wc_GetDecodedCertSubject(caCert, caSubjectBuf, &caSubjSz); if (ret != 0 || caSubjSz == 0) { LOG_ERROR("Could not get CA subject\n"); return BAD_FUNC_ARG; } for (entry = index; entry != NULL; entry = entry->next) { byte serial[64]; word32 serialLen = 0; enum Ocsp_Cert_Status status; time_t revTime = 0; enum WC_CRL_Reason revReason = WC_CRL_REASON_UNSPECIFIED; word32 validity = 86400; char* p = entry->serial; word32 i; /* Convert hex string to bytes */ { word32 hexLen = (word32)XSTRLEN(entry->serial); word32 j; /* Reject odd-length hex strings */ if (hexLen % 2 != 0) { LOG_ERROR("Invalid hex serial length (odd): %u\n", hexLen); return BAD_FUNC_ARG; } serialLen = hexLen / 2; if (serialLen == 0 || serialLen > sizeof(serial)) { return BAD_FUNC_ARG; } /* Validate all characters are hex digits */ for (j = 0; j < hexLen; j++) { char c = p[j]; if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { LOG_ERROR("Invalid hex character in serial: '%c'\n", c); return BAD_FUNC_ARG; } } } for (i = 0; i < serialLen; i++) { int high = ('0' <= p[i*2] && p[i*2] <= '9') ? (p[i*2] - '0') : ('A' <= p[i*2] && p[i*2] <= 'F') ? (p[i*2] - 'A' + 10) : (p[i*2] - 'a' + 10); int low = ('0' <= p[i*2+1] && p[i*2+1] <= '9') ? (p[i*2+1] - '0') : ('A' <= p[i*2+1] && p[i*2+1] <= 'F') ? (p[i*2+1] - 'A' + 10) : (p[i*2+1] - 'a' + 10); serial[i] = (byte)((high << 4) | low); } /* Determine status */ if (entry->status == 'V') { status = CERT_GOOD; } else if (entry->status == 'R') { status = CERT_REVOKED; revTime = entry->revocationTime; revReason = WC_CRL_REASON_UNSPECIFIED; validity = 0; } else { status = CERT_UNKNOWN; validity = 0; } ret = wc_OcspResponder_SetCertStatus(responder, caSubjectBuf, caSubjSz, serial, serialLen, status, revTime, revReason, validity); if (ret == 0) { count++; } } return count; } /* Receive a complete HTTP request, looping until the full body arrives */ static int RecvHttpRequest(SOCKET_T fd, byte* buf, int bufSz) { int totalLen = 0; int contentLen = 0; int headerSz = 0; while (totalLen < bufSz - 1) { int n = (int)recv(fd, (char*)buf + totalLen, (size_t)(bufSz - 1 - totalLen), 0); if (n <= 0) break; totalLen += n; buf[totalLen] = '\0'; /* Once we find end-of-headers, parse Content-Length */ if (headerSz == 0) { const char* hdrEnd = XSTRSTR((char*)buf, "\r\n\r\n"); if (hdrEnd != NULL) { const char* cl; headerSz = (int)(hdrEnd + 4 - (char*)buf); cl = XSTRSTR((char*)buf, "Content-Length:"); if (cl == NULL) cl = XSTRSTR((char*)buf, "content-length:"); if (cl != NULL) contentLen = atoi(cl + 15); } } /* Check if we have the full body */ if (headerSz > 0 && totalLen >= headerSz + contentLen) break; } return totalLen; } /* Parse HTTP request and extract OCSP request body */ static int ParseHttpRequest(const byte* httpReq, int httpReqSz, const byte** body, int* bodySz, char* path, int pathSz) { /* Initialize outputs */ *body = NULL; *bodySz = 0; if (path && pathSz > 0) path[0] = '\0'; /* Check for POST method */ if (XSTRNCMP((char*)httpReq, "POST ", 5) == 0) { const char* contentLen; /* Extract path */ const char* pathStart = (const char*)httpReq + 5; const char* pathEnd = XSTRSTR(pathStart, " "); if (pathEnd && path && pathSz > 0) { int len = (int)(pathEnd - pathStart); if (len >= pathSz) len = pathSz - 1; XMEMCPY(path, pathStart, (size_t)len); path[len] = '\0'; } /* Find Content-Length */ contentLen = XSTRSTR((char*)httpReq, "Content-Length:"); if (contentLen == NULL) { contentLen = XSTRSTR((char*)httpReq, "content-length:"); } if (contentLen) { *bodySz = atoi(contentLen + 15); /* Reject obviously invalid or unreasonably large Content-Length */ if (*bodySz < 0 || *bodySz > MAX_REQUEST_SIZE) { LOG_ERROR("Invalid or too large Content-Length: %d\n", *bodySz); *body = NULL; *bodySz = 0; return -1; } } /* Find body (after \r\n\r\n) */ *body = (const byte*)XSTRSTR((char*)httpReq, "\r\n\r\n"); if (*body) { int offset; *body += 4; offset = (int)(*body - httpReq); /* Validate that the body pointer is within the received buffer */ if (offset < 0 || offset > httpReqSz) { LOG_ERROR("Invalid HTTP body offset\n"); *body = NULL; *bodySz = 0; return -1; } /* Use Content-Length if available, otherwise use remaining data */ if (*bodySz == 0) { /* TODO We should only enter here when "Connection-close" * is present. There should be some checks to confirm that. */ *bodySz = httpReqSz - offset; } /* Ensure that the claimed body length fits in the received data */ if (offset + *bodySz > httpReqSz) { LOG_ERROR("Incomplete HTTP body: expected %d bytes, have %d\n", *bodySz, httpReqSz - offset); *body = NULL; *bodySz = 0; return -1; } return 0; } } /* Check for GET method with URL-encoded request */ else if (XSTRNCMP((char*)httpReq, "GET ", 4) == 0) { /* GET requests have base64-encoded OCSP request in URL */ /* For simplicity, we'll require POST for now */ LOG_ERROR("GET method not fully supported, use POST\n"); return -1; } return -1; } /* Send HTTP response with OCSP response body */ static int SendHttpResponse(SOCKET_T clientfd, const byte* ocspResp, int ocspRespSz) { char header[MAX_HTTP_HEADER]; int headerLen; int sent; headerLen = XSNPRINTF(header, sizeof(header), "HTTP/1.0 200 OK\r\n" "Content-Type: application/ocsp-response\r\n" "Content-Length: %d\r\n" "Connection: close\r\n" "\r\n", ocspRespSz); if (headerLen < 0 || headerLen >= (int)sizeof(header)) { LOG_ERROR("HTTP header truncated\n"); return -1; } /* Send header */ { int totalSent = 0; while (totalSent < headerLen) { sent = (int)send(clientfd, header + totalSent, (size_t)(headerLen - totalSent), 0); if (sent <= 0) { LOG_ERROR("Failed to send HTTP header\n"); return -1; } totalSent += sent; } } /* Send body */ { int totalSent = 0; while (totalSent < ocspRespSz) { sent = (int)send(clientfd, (const char*)ocspResp + totalSent, (size_t)(ocspRespSz - totalSent), 0); if (sent <= 0) { LOG_ERROR("Failed to send OCSP response\n"); return -1; } totalSent += sent; } } return 0; } /* Send HTTP error response */ static int SendHttpError(SOCKET_T clientfd, int statusCode, const char* statusMsg) { char response[512]; int len; int sent; len = XSNPRINTF(response, sizeof(response), "HTTP/1.0 %d %s\r\n" "Content-Type: text/plain\r\n" "Content-Length: %d\r\n" "Connection: close\r\n" "\r\n" "%s", statusCode, statusMsg, (int)XSTRLEN(statusMsg), statusMsg); /* Handle XSNPRINTF error or truncation to avoid sending * out-of-bounds data. */ if (len < 0 || len >= (int)sizeof(response)) { LOG_ERROR("HTTP error response truncated\n"); return -1; } sent = (int)send(clientfd, response, (size_t)len, 0); return (sent == len) ? 0 : -1; } /* Map error codes to OCSP response status */ static enum Ocsp_Response_Status MapErrorToOcspStatus(int err) { switch (err) { case ASN_PARSE_E: return OCSP_MALFORMED_REQUEST; case ASN_SIG_HASH_E: /* Unsupported hash algorithm */ return OCSP_INTERNAL_ERROR; case ASN_NO_SIGNER_E: return OCSP_UNAUTHORIZED; default: return OCSP_INTERNAL_ERROR; } } /* Main OCSP responder function */ THREAD_RETURN WOLFSSL_THREAD ocsp_responder_test(void* args) { func_args* myargs = (func_args*)args; int argc = myargs->argc; char** argv = myargs->argv; int ret; int ch; OcspResponderOptions opts; OcspResponder* responder = NULL; IndexEntry* indexEntries = NULL; DecodedCert caCert; SOCKET_T sockfd = INVALID_SOCKET; SOCKET_T clientfd = INVALID_SOCKET; byte* caCertDer = NULL; word32 caCertDerSz = 0; byte* responderCertDer = NULL; word32 responderCertDerSz = 0; byte* caKeyDer = NULL; word32 caKeyDerSz = 0; byte httpBuf[MAX_REQUEST_SIZE]; byte respBuf[MAX_RESPONSE_SIZE]; word32 respSz; int requestsProcessed = 0; word32 caSubjectSz = 0; static const struct mygetopt_long_config long_options[] = { { "help", 0, '?' }, { NULL, 0, 0 } }; /* Initialize options */ XMEMSET(&opts, 0, sizeof(opts)); opts.port = DEFAULT_PORT; opts.nrequests = 0; opts.verbose = 0; opts.sendCerts = 1; opts.readyFile = NULL; /* Initialize caCert */ XMEMSET(&caCert, 0, sizeof(caCert)); /* Parse command line arguments */ while ((ch = mygetopt_long(argc, argv, "?p:c:r:k:i:R:n:vx", long_options, 0)) != -1) { switch (ch) { case '?': Usage(); ret = 0; goto cleanup; case 'p': opts.port = (word16)atoi(myoptarg); break; case 'c': opts.certFile = myoptarg; break; case 'r': opts.responderCertFile = myoptarg; break; case 'k': opts.keyFile = myoptarg; break; case 'i': opts.indexFile = myoptarg; break; case 'R': opts.readyFile = myoptarg; break; case 'n': opts.nrequests = atoi(myoptarg); break; case 'v': opts.verbose = 1; break; case 'x': opts.sendCerts = 0; break; default: Usage(); ret = MY_EX_USAGE; goto cleanup; } } myoptind = 0; /* reset for test cases */ /* Validate required options */ if (opts.certFile == NULL) { LOG_ERROR("Error: CA certificate required (-c)\n"); Usage(); ret = MY_EX_USAGE; goto cleanup; } if (opts.keyFile == NULL) { LOG_ERROR("Error: CA key required (-k)\n"); Usage(); ret = MY_EX_USAGE; goto cleanup; } /* Load CA certificate */ ret = LoadCertDer(opts.certFile, &caCertDer, &caCertDerSz); if (ret != 0) { LOG_ERROR("Error loading CA certificate: %s\n", opts.certFile); ret = -1; goto cleanup; } if (opts.verbose) { LOG_MSG("Loaded CA certificate: %s (%d bytes)\n", opts.certFile, caCertDerSz); } /* Load responder certificate if provided */ if (opts.responderCertFile != NULL) { ret = LoadCertDer(opts.responderCertFile, &responderCertDer, &responderCertDerSz); if (ret != 0) { LOG_ERROR("Error loading responder certificate: %s\n", opts.responderCertFile); ret = -1; goto cleanup; } if (opts.verbose) { LOG_MSG("Loaded responder certificate: %s (%d bytes)\n", opts.responderCertFile, responderCertDerSz); } } /* Load CA key */ ret = LoadKeyDer(opts.keyFile, &caKeyDer, &caKeyDerSz); if (ret != 0) { LOG_ERROR("Error loading signing key: %s\n", opts.keyFile); ret = -1; goto cleanup; } if (opts.verbose) { LOG_MSG("Loaded signing key: %s (%d bytes)\n", opts.keyFile, caKeyDerSz); } /* Parse CA certificate to get subject */ wc_InitDecodedCert(&caCert, caCertDer, caCertDerSz, NULL); ret = wc_ParseCert(&caCert, CERT_TYPE, 0, NULL); if (ret != 0) { LOG_ERROR("Error parsing CA certificate: %d\n", ret); ret = -1; goto cleanup; } (void)wc_GetDecodedCertSubject(&caCert, NULL, &caSubjectSz); (void)caSubjectSz; /* Not used in current implementation */ /* Load index file if provided */ if (opts.indexFile) { indexEntries = ParseIndexFile(opts.indexFile); if (indexEntries == NULL) { LOG_ERROR("Warning: Could not parse index file: %s\n", opts.indexFile); } else if (opts.verbose) { LOG_MSG("Loaded index file: %s\n", opts.indexFile); } } /* Create OCSP responder */ responder = wc_OcspResponder_new(NULL, opts.sendCerts); if (responder == NULL) { LOG_ERROR("Error creating OCSP responder\n"); ret = -1; goto cleanup; } /* Add signer to responder */ if (opts.responderCertFile != NULL) { /* Authorized responder: use responder cert as signer, * CA cert as issuer */ ret = wc_OcspResponder_AddSigner(responder, responderCertDer, responderCertDerSz, caKeyDer, caKeyDerSz, caCertDer, caCertDerSz); if (ret != 0) { LOG_ERROR("Error adding authorized responder to" " responder: %d\n", ret); goto cleanup; } if (opts.verbose) { LOG_MSG("Added authorized responder with CA issuer\n"); } } else { /* CA as signer (self-signed) */ ret = wc_OcspResponder_AddSigner(responder, caCertDer, caCertDerSz, caKeyDer, caKeyDerSz, NULL, 0); if (ret != 0) { LOG_ERROR("Error adding CA to responder: %d\n", ret); goto cleanup; } if (opts.verbose) { LOG_MSG("Added CA to responder\n"); } } /* Populate responder with certificate statuses from index */ if (indexEntries != NULL) { int statusCount = PopulateResponderFromIndex(responder, indexEntries, &caCert); if (statusCount < 0) { LOG_ERROR("Error populating responder from index:" " %d\n", statusCount); } else if (opts.verbose) { LOG_MSG("Populated responder with %d certificate" " statuses\n", statusCount); } } /* Create and listen on server socket */ tcp_listen(&sockfd, &opts.port, 1, 0, 0); #ifndef SINGLE_THREADED /* Signal readiness via tcp_ready if provided (for in-process testing) */ if (myargs->signal != NULL) { tcp_ready* ready = myargs->signal; #ifdef WOLFSSL_COND THREAD_CHECK_RET(wolfSSL_CondStart(&ready->cond)); #endif ready->ready = 1; ready->port = opts.port; #ifdef WOLFSSL_COND THREAD_CHECK_RET(wolfSSL_CondSignal(&ready->cond)); THREAD_CHECK_RET(wolfSSL_CondEnd(&ready->cond)); #endif } #endif /* !SINGLE_THREADED */ /* Write ready file if requested */ if (opts.readyFile != NULL) { XFILE rf = XFOPEN(opts.readyFile, "w"); if (rf != XBADFILE) { fprintf(rf, "%d\n", (int)opts.port); XFCLOSE(rf); if (opts.verbose) { LOG_MSG("Ready file created: %s\n", opts.readyFile); } } else { LOG_ERROR("Warning: Failed to create ready file:" " %s\n", opts.readyFile); } } #ifndef _WIN32 /* Install signal handlers for clean shutdown */ { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_handler; /* Do NOT set SA_RESTART so accept() is interrupted */ sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); } if (opts.verbose) { LOG_MSG("Signal handlers installed\n"); } #endif /* Main loop */ while ((opts.nrequests == 0 || requestsProcessed < opts.nrequests) #ifndef _WIN32 && !got_signal #endif ) { struct sockaddr_in clientAddr; socklen_t clientAddrLen = sizeof(clientAddr); int recvLen; const byte* ocspReq; int ocspReqSz; char path[MAX_PATH_LEN]; /* Accept connection */ clientfd = accept(sockfd, (struct sockaddr*)&clientAddr, &clientAddrLen); if (clientfd == INVALID_SOCKET) { LOG_ERROR("accept() failed\n"); continue; } if (opts.verbose) { LOG_MSG("Connection from %s:%d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port)); } /* Receive HTTP request */ recvLen = RecvHttpRequest(clientfd, httpBuf, (int)sizeof(httpBuf)); if (recvLen <= 0) { LOG_ERROR("recv() failed\n"); close(clientfd); continue; } httpBuf[recvLen] = '\0'; if (opts.verbose) { LOG_MSG("Received %d bytes\n", recvLen); } /* Parse HTTP request */ ret = ParseHttpRequest(httpBuf, recvLen, &ocspReq, &ocspReqSz, path, sizeof(path)); if (ret != 0 || ocspReq == NULL || ocspReqSz <= 0) { LOG_ERROR("Invalid HTTP request\n"); SendHttpError(clientfd, 400, "Bad Request"); close(clientfd); continue; } if (opts.verbose) { LOG_MSG("OCSP request: %d bytes, path: %s\n", ocspReqSz, path); } /* Process OCSP request and generate response */ respSz = sizeof(respBuf); ret = wc_OcspResponder_WriteResponse(responder, ocspReq, (word32)ocspReqSz, respBuf, &respSz); if (ret != 0) { enum Ocsp_Response_Status errStatus; LOG_ERROR("Error generating OCSP response: %d\n", ret); /* Generate appropriate OCSP error response */ errStatus = MapErrorToOcspStatus(ret); respSz = sizeof(respBuf); ret = wc_OcspResponder_WriteErrorResponse(errStatus, respBuf, &respSz); if (ret != 0) { /* If we can't even encode an error response, send HTTP error */ LOG_ERROR("Error encoding OCSP error response: %d\n", ret); SendHttpError(clientfd, 500, "Internal Server Error"); close(clientfd); continue; } if (opts.verbose) { LOG_MSG("Generated OCSP error response (status=%d): %d bytes\n", errStatus, respSz); } } if (opts.verbose) { LOG_MSG("Generated OCSP response: %d bytes\n", respSz); } /* Send HTTP response */ ret = SendHttpResponse(clientfd, respBuf, (int)respSz); if (ret != 0) { LOG_ERROR("Error sending response\n"); } close(clientfd); clientfd = INVALID_SOCKET; requestsProcessed++; if (opts.verbose) { LOG_MSG("Processed request %d\n", requestsProcessed); } } ret = 0; cleanup: if (clientfd != INVALID_SOCKET) close(clientfd); if (sockfd != INVALID_SOCKET) close(sockfd); wc_FreeDecodedCert(&caCert); if (responder) wc_OcspResponder_free(responder); if (indexEntries) FreeIndexEntries(indexEntries); if (caCertDer) XFREE(caCertDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (responderCertDer) XFREE(responderCertDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (caKeyDer) XFREE(caKeyDer, NULL, DYNAMIC_TYPE_TMP_BUFFER); myargs->return_code = ret; #ifndef WOLFSSL_THREAD_VOID_RETURN return (THREAD_RETURN)0; #else return; #endif } #ifndef NO_MAIN_DRIVER int main(int argc, char** argv) { func_args args; int ret; printf("The ocsp_responder.c example is only meant for testing. " "Do not use this in a production environment.\n"); StartTCP(); #ifdef HAVE_WNR if (wc_InitNetRandom(wnrConfigFile, NULL, 5000) != 0) { err_sys("Whitewood netRandom global config failed"); } #endif ret = wolfSSL_Init(); if (ret != WOLFSSL_SUCCESS) { err_sys("wolfSSL_Init failed"); } XMEMSET(&args, 0, sizeof(args)); args.argc = argc; args.argv = argv; args.return_code = 0; ocsp_responder_test(&args); wolfSSL_Cleanup(); #ifdef HAVE_WNR if (wc_FreeNetRandom() < 0) err_sys("Failed to free netRandom context"); #endif return args.return_code; } #endif /* !NO_MAIN_DRIVER */ #else /* HAVE_OCSP && HAVE_OCSP_RESPONDER && !NO_FILESYSTEM */ #ifndef NO_MAIN_DRIVER int main(int argc, char** argv) { (void)argc; (void)argv; printf("OCSP Responder requires HAVE_OCSP, HAVE_OCSP_RESPONDER," " and filesystem support\n"); return 0; } #endif THREAD_RETURN WOLFSSL_THREAD ocsp_responder_test(void* args) { func_args* myargs = (func_args*)args; printf("OCSP Responder requires HAVE_OCSP, HAVE_OCSP_RESPONDER," " and filesystem support\n"); myargs->return_code = 0; WOLFSSL_RETURN_FROM_THREAD(0); } #endif /* HAVE_OCSP && HAVE_OCSP_RESPONDER && !NO_FILESYSTEM */