Files
wolfssl/examples/ocsp_responder/ocsp_responder.c
T
2026-03-13 17:28:00 -06:00

1191 lines
36 KiB
C

/* 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 <config.h>
#endif
#ifndef WOLFSSL_USER_SETTINGS
#include <wolfssl/options.h>
#endif
#include <wolfssl/wolfcrypt/settings.h>
#undef TEST_OPENSSL_COEXIST /* can't use this option with this example */
#undef OPENSSL_COEXIST /* can't use this option with this example */
#include <wolfssl/ssl.h>
#include <wolfssl/ocsp.h>
#include <wolfssl/test.h>
#include <wolfssl/error-ssl.h>
#include <wolfssl/wolfcrypt/asn.h>
#include <examples/ocsp_responder/ocsp_responder.h>
/* Check if we have the required features */
#if defined(HAVE_OCSP) && defined(HAVE_OCSP_RESPONDER) && \
!defined(NO_FILESYSTEM)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Define mygetopt variables (used by mygetopt_long in test.h) */
#ifndef NO_MAIN_DRIVER
int myoptind = 0;
char* myoptarg = NULL;
#endif
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#define SOCKET_T SOCKET
#define close(s) closesocket(s)
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#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 <num> Port (default %d)\n", DEFAULT_PORT);
LOG_MSG(" -c <file> CA certificate (issuer)\n");
LOG_MSG(" -r <file> Responder certificate"
" (for authorized responder)\n");
LOG_MSG(" -k <file> Signing private key\n");
LOG_MSG(" -i <file> Index file for cert status\n");
LOG_MSG(" -R <file> Ready file for external monitor\n");
LOG_MSG(" -n <num> 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;
DerBuffer* derBuf = NULL;
ret = wc_PemToDer(pem, pemSz, type, &derBuf, NULL, NULL, NULL);
if (ret != 0 || derBuf == NULL) {
return ret;
}
*der = (byte*)XMALLOC(derBuf->length, NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (*der == NULL) {
wc_FreeDer(&derBuf);
return MEMORY_E;
}
XMEMCPY(*der, derBuf->buffer, derBuf->length);
*derSz = derBuf->length;
wc_FreeDer(&derBuf);
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 = 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 = 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);
}
}
#ifdef USE_WINDOWS_API
if (opts.port == 0) {
/* Generate random port for testing */
opts.port = GetRandomPort();
}
#endif /* USE_WINDOWS_API */
/* 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 */