wolfssl-examples/http-message-signatures/http_client_signed.c

318 lines
9.4 KiB
C

/* http_client_signed.c
*
* Minimal HTTP client demonstrating RFC 9421 signed requests using
* wolfCrypt Ed25519.
*
* Connects to localhost:8080 (http_server_verify), signs HTTP requests,
* and sends them. Includes a tamper test to demonstrate rejection.
*
* Build wolfSSL with:
* ./configure --enable-ed25519 --enable-coding && make && sudo make install
*
* 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 2 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-1301, USA
*/
#ifndef WOLFSSL_USER_SETTINGS
#include <wolfssl/options.h>
#endif
#include <wolfssl/wolfcrypt/settings.h>
#include <wolfssl/wolfcrypt/ed25519.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "common/wc_http_sig.h"
#if defined(HAVE_ED25519) && defined(HAVE_ED25519_SIGN)
/* Portable case-insensitive string comparison (avoids POSIX strcasecmp
* which is unavailable on some toolchains). */
static int ci_strcmp(const char* a, const char* b)
{
while (*a && *b) {
int ca = (*a >= 'A' && *a <= 'Z') ? *a + ('a' - 'A') : *a;
int cb = (*b >= 'A' && *b <= 'Z') ? *b + ('a' - 'A') : *b;
if (ca != cb) return ca - cb;
a++; b++;
}
return (unsigned char)*a - (unsigned char)*b;
}
#define SERVER_PORT 8080
#define MAX_HTTP_SZ 4096
/* RFC 9421 Appendix B.1.4 — Ed25519 private key seed (demo only) */
static const byte kDemoPrivKey[ED25519_KEY_SIZE] = {
0x9f, 0x83, 0x62, 0xf8, 0x7a, 0x48, 0x4a, 0x95,
0x4e, 0x6e, 0x74, 0x0c, 0x5b, 0x4c, 0x0e, 0x84,
0x22, 0x91, 0x39, 0xa2, 0x0a, 0xa8, 0xab, 0x56,
0xff, 0x66, 0x58, 0x6f, 0x6a, 0x7d, 0x29, 0xc5
};
static int do_connect(void)
{
int fd;
struct sockaddr_in addr;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) { perror("socket"); return -1; }
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = htons(SERVER_PORT);
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("connect (is the server running?)");
close(fd);
return -1;
}
return fd;
}
static int read_response(int fd)
{
char buf[1024];
int n, status = 0;
n = (int)recv(fd, buf, sizeof(buf) - 1, 0);
if (n <= 0) {
printf("[Client] No response received\n");
return -1;
}
buf[n] = '\0';
if (sscanf(buf, "HTTP/1.1 %d", &status) != 1)
status = 0;
printf("[Client] Response: %d", status);
{
const char* body = strstr(buf, "\r\n\r\n");
if (body) {
body += 4;
if (*body) printf(" — %s", body);
}
}
return status;
}
/* Build an HTTP/1.1 request string with Signature headers.
* If tamperHdr is not NULL, replaces that header's value with tamperVal
* in the raw HTTP output (simulating a man-in-the-middle modification). */
static int build_http_request(
const char* method, const char* path, const char* query,
const char* authority,
wc_HttpHeader* headers, int hdrCount,
const char* sigValue, word32 sigValueLen,
const char* sigInputValue, word32 sigInputLen,
const char* tamperHdr, const char* tamperVal,
char* out, int outSz)
{
int pos = 0, n;
int i;
#define SAFE_SNPRINTF(pos, out, outSz, ...) do { \
if ((pos) >= (outSz)) return -1; \
n = snprintf((out) + (pos), (outSz) - (pos), __VA_ARGS__); \
if (n < 0 || n >= (outSz) - (pos)) return -1; \
(pos) += n; \
} while (0)
SAFE_SNPRINTF(pos, out, outSz, "%s %s%s HTTP/1.1\r\n",
method, path, query ? query : "");
SAFE_SNPRINTF(pos, out, outSz, "Host: %s\r\n", authority);
for (i = 0; i < hdrCount; i++) {
const char* val = headers[i].value;
if (tamperHdr && ci_strcmp(headers[i].name, tamperHdr) == 0)
val = tamperVal;
SAFE_SNPRINTF(pos, out, outSz, "%s: %s\r\n",
headers[i].name, val);
}
SAFE_SNPRINTF(pos, out, outSz, "Signature-Input: %.*s\r\n",
(int)sigInputLen, sigInputValue);
SAFE_SNPRINTF(pos, out, outSz, "Signature: %.*s\r\n",
(int)sigValueLen, sigValue);
SAFE_SNPRINTF(pos, out, outSz, "\r\n");
#undef SAFE_SNPRINTF
return pos;
}
static int demo_request(ed25519_key* key,
const char* label,
const char* method,
const char* path,
const char* query,
const char* authority,
wc_HttpHeader* headers, int hdrCount,
const char* tamperHdr, const char* tamperVal,
int expectStatus)
{
char sigBuf[512];
word32 sigBufSz = sizeof(sigBuf);
char inputBuf[1024];
word32 inputBufSz = sizeof(inputBuf);
char httpReq[MAX_HTTP_SZ];
int httpLen, fd, status, ret;
printf("\n--- %s ---\n", label);
printf("[Client] Signing: %s %s%s\n", method, path, query ? query : "");
ret = wc_HttpSig_Sign(method, authority, path, query,
headers, hdrCount,
key, "test-key-ed25519", 0,
sigBuf, &sigBufSz,
inputBuf, &inputBufSz);
if (ret != 0) {
printf("[Client] Sign failed: %d (%s)\n", ret, wc_GetErrorString(ret));
return -1;
}
if (tamperHdr)
printf("[Client] Tampering: changing %s header after signing\n",
tamperHdr);
httpLen = build_http_request(
method, path, query, authority,
headers, hdrCount,
sigBuf, sigBufSz,
inputBuf, inputBufSz,
tamperHdr, tamperVal,
httpReq, sizeof(httpReq));
if (httpLen < 0) {
printf("[Client] Failed to build HTTP request\n");
return -1;
}
fd = do_connect();
if (fd < 0) return -1;
send(fd, httpReq, (size_t)httpLen, 0);
status = read_response(fd);
close(fd);
if (status == expectStatus) {
printf("[Client] Result: correct (expected %d)\n", expectStatus);
return 0;
} else {
printf("[Client] Result: UNEXPECTED (got %d, expected %d)\n",
status, expectStatus);
return -1;
}
}
int main(void)
{
int ret, failures = 0;
ed25519_key key;
wc_HttpHeader headers[2];
headers[0].name = "Date";
headers[0].value = "Thu, 19 Mar 2026 12:00:00 GMT";
headers[1].name = "Content-Type";
headers[1].value = "application/json";
printf("=== RFC 9421 HTTP Client - Signed Requests ===\n");
printf("[Client] Connecting to localhost:%d\n", SERVER_PORT);
ret = wc_ed25519_init(&key);
if (ret != 0) {
printf("Failed to init key: %d\n", ret);
return 1;
}
ret = wc_ed25519_import_private_only(kDemoPrivKey, ED25519_KEY_SIZE, &key);
if (ret != 0) {
printf("Failed to import private key: %d\n", ret);
wc_ed25519_free(&key);
return 1;
}
{
byte pubBuf[ED25519_PUB_KEY_SIZE];
word32 pubSz = ED25519_PUB_KEY_SIZE;
ret = wc_ed25519_make_public(&key, pubBuf, pubSz);
if (ret != 0) {
printf("Failed to derive public key: %d\n", ret);
wc_ed25519_free(&key);
return 1;
}
ret = wc_ed25519_import_private_key(kDemoPrivKey, ED25519_KEY_SIZE,
pubBuf, pubSz, &key);
if (ret != 0) {
printf("Failed to import keypair: %d\n", ret);
wc_ed25519_free(&key);
return 1;
}
}
/* Demo 1: Valid signed request */
ret = demo_request(&key,
"Demo 1: Valid signed request",
"POST", "/api/resource", "?action=update",
"localhost:8080",
headers, 2,
NULL, NULL,
200);
if (ret != 0) failures++;
/* Demo 2: Tampered header (Date modified after signing) */
ret = demo_request(&key,
"Demo 2: Tampered request (modified Date)",
"POST", "/api/resource", "?action=update",
"localhost:8080",
headers, 2,
"Date", "Fri, 20 Mar 2026 12:00:00 GMT",
401);
if (ret != 0) failures++;
/* Demo 3: Valid GET without query */
ret = demo_request(&key,
"Demo 3: Valid GET request",
"GET", "/status", NULL,
"localhost:8080",
headers, 1,
NULL, NULL,
200);
if (ret != 0) failures++;
printf("\n=== Results: %d/3 demos passed ===\n", 3 - failures);
wc_ed25519_free(&key);
return failures > 0 ? 1 : 0;
}
#else
int main(void)
{
printf("This example requires wolfSSL compiled with --enable-ed25519\n");
return 1;
}
#endif