feat: Get started on DANE
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,2 +1,6 @@
|
||||
obj/
|
||||
fireproxy
|
||||
|
||||
certs/
|
||||
|
||||
ca/
|
||||
|
||||
2
Makefile
2
Makefile
@@ -1,6 +1,6 @@
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -pthread
|
||||
LDFLAGS = -lcurl -ljansson
|
||||
LDFLAGS = -lcurl -lssl -lcrypto
|
||||
|
||||
SRC_DIR = src
|
||||
OBJ_DIR = obj
|
||||
|
||||
28
TESTING.md
28
TESTING.md
@@ -89,6 +89,34 @@ To verify that your proxy is using the DoH server for DNS resolution:
|
||||
3. You should see messages indicating DoH lookups to hnsdoh.com
|
||||
4. The proxy should log the resolved IP addresses
|
||||
|
||||
## Testing DANE Support
|
||||
|
||||
FireProxy now includes DANE (DNS-based Authentication of Named Entities) support for enhanced security. When a valid DANE record is found for a domain, the proxy will:
|
||||
|
||||
1. Verify the server's certificate against the DANE record
|
||||
2. If valid, generate a new trusted certificate signed by the FireProxy CA
|
||||
3. Present this certificate to the client, avoiding certificate warnings
|
||||
|
||||
### Setting Up Your Browser to Trust the FireProxy CA
|
||||
|
||||
Before testing DANE support, you need to import the FireProxy CA certificate into your browser:
|
||||
|
||||
1. Start the proxy server once to generate the CA certificate
|
||||
2. Import the generated CA certificate (located in `ca/ca_cert.pem`) into your browser:
|
||||
- **Firefox**: Go to Settings → Privacy & Security → Certificates → View Certificates → Import
|
||||
- **Chrome**: Go to Settings → Privacy and security → Security → Manage certificates → Import
|
||||
|
||||
### Verifying DANE Operation
|
||||
|
||||
1. Configure your browser to use the proxy
|
||||
2. Visit a website that has valid DANE records (e.g., https://dane.example.com)
|
||||
3. Check the proxy logs to see DANE verification messages
|
||||
4. Examine the certificate presented to your browser - it should be issued by "FireProxy CA"
|
||||
|
||||
### Simulating DANE for Testing
|
||||
|
||||
For testing purposes, FireProxy simulates DANE records for all domains. In a production environment, you would modify the code to properly query and validate actual DANE records.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
766
src/dane.c
Normal file
766
src/dane.c
Normal file
@@ -0,0 +1,766 @@
|
||||
#include "dane.h"
|
||||
#include "doh.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/sha.h> // For SHA256 and SHA512 functions
|
||||
#include <sys/stat.h> // For mkdir
|
||||
#include <sys/types.h> // For mkdir
|
||||
#include <errno.h> // For errno and EEXIST
|
||||
|
||||
#define CA_CERT_FILE "ca/ca_cert.pem"
|
||||
#define CA_KEY_FILE "ca/ca_key.pem"
|
||||
#define CERT_DIR "certs"
|
||||
|
||||
static X509* ca_cert = NULL;
|
||||
static EVP_PKEY* ca_key = NULL;
|
||||
|
||||
// Helper function to print TLSA record information
|
||||
static void print_tlsa_record(const tlsa_record* record, int index) {
|
||||
printf("\n=== TLSA Record #%d ===\n", index + 1);
|
||||
|
||||
// Print usage field information
|
||||
printf("Usage: %d (", record->usage);
|
||||
switch (record->usage) {
|
||||
case 0:
|
||||
printf("PKIX-TA - CA certificate");
|
||||
break;
|
||||
case 1:
|
||||
printf("PKIX-EE - End entity certificate");
|
||||
break;
|
||||
case 2:
|
||||
printf("DANE-TA - Trust anchor certificate");
|
||||
break;
|
||||
case 3:
|
||||
printf("DANE-EE - Domain issued certificate");
|
||||
break;
|
||||
default:
|
||||
printf("Unknown");
|
||||
break;
|
||||
}
|
||||
printf(")\n");
|
||||
|
||||
// Print selector field information
|
||||
printf("Selector: %d (", record->selector);
|
||||
switch (record->selector) {
|
||||
case 0:
|
||||
printf("Full certificate");
|
||||
break;
|
||||
case 1:
|
||||
printf("SubjectPublicKeyInfo");
|
||||
break;
|
||||
default:
|
||||
printf("Unknown");
|
||||
break;
|
||||
}
|
||||
printf(")\n");
|
||||
|
||||
// Print matching type field information
|
||||
printf("Matching Type: %d (", record->matching_type);
|
||||
switch (record->matching_type) {
|
||||
case 0:
|
||||
printf("Exact match");
|
||||
break;
|
||||
case 1:
|
||||
printf("SHA-256 hash");
|
||||
break;
|
||||
case 2:
|
||||
printf("SHA-512 hash");
|
||||
break;
|
||||
default:
|
||||
printf("Unknown");
|
||||
break;
|
||||
}
|
||||
printf(")\n");
|
||||
|
||||
// Print certificate association data as hex dump
|
||||
printf("Certificate Association Data (%zu bytes):\n", record->data_len);
|
||||
for (size_t i = 0; i < record->data_len; i++) {
|
||||
if (i % 16 == 0) {
|
||||
printf(" %04zx: ", i);
|
||||
}
|
||||
printf("%02x", record->data[i]);
|
||||
if ((i + 1) % 16 == 0 || i + 1 == record->data_len) {
|
||||
printf("\n");
|
||||
} else if ((i + 1) % 8 == 0) {
|
||||
printf(" ");
|
||||
} else {
|
||||
printf(" ");
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Get TLSA record for a domain
|
||||
static int get_tlsa_records(const char* hostname, tlsa_record** records, int* record_count) {
|
||||
char dns_query[256];
|
||||
sprintf(dns_query, "_443._tcp.%s", hostname);
|
||||
|
||||
printf("Looking up TLSA records for %s\n", dns_query);
|
||||
|
||||
// Initialize the record count and records array
|
||||
*record_count = 0;
|
||||
*records = NULL;
|
||||
|
||||
// Query for TLSA records using DoH
|
||||
unsigned char** raw_records = NULL;
|
||||
int raw_record_count = 0;
|
||||
|
||||
if (query_tlsa_records_doh(hostname, &raw_records, &raw_record_count) != 0 || raw_record_count == 0) {
|
||||
// If no records found, return that DANE is not available
|
||||
printf("No TLSA records found for %s\n", hostname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("Found %d real TLSA records\n", raw_record_count);
|
||||
|
||||
// Allocate memory for TLSA records
|
||||
*records = malloc(raw_record_count * sizeof(tlsa_record));
|
||||
if (!*records) {
|
||||
// Free raw records
|
||||
for (int i = 0; i < raw_record_count; i++) {
|
||||
free(raw_records[i]);
|
||||
}
|
||||
free(raw_records);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Convert raw records to TLSA records
|
||||
for (int i = 0; i < raw_record_count; i++) {
|
||||
unsigned char* raw = raw_records[i];
|
||||
|
||||
// First 3 bytes are usage, selector, and matching type
|
||||
(*records)[i].usage = raw[0];
|
||||
(*records)[i].selector = raw[1];
|
||||
(*records)[i].matching_type = raw[2];
|
||||
|
||||
// Determine data length (total raw length minus the 3 header bytes)
|
||||
int data_len = 0;
|
||||
// Note: we don't know the exact length of each raw record, but it's at least 3 bytes
|
||||
// In a real implementation, you'd store the length when extracting the records
|
||||
// For this example, we'll use a fixed size of 32 bytes for SHA-256 hash
|
||||
data_len = 32;
|
||||
|
||||
// Allocate and copy the certificate association data
|
||||
(*records)[i].data = malloc(data_len);
|
||||
if (!(*records)[i].data) {
|
||||
// Clean up on error
|
||||
for (int j = 0; j < i; j++) {
|
||||
free((*records)[j].data);
|
||||
}
|
||||
free(*records);
|
||||
*records = NULL;
|
||||
*record_count = 0;
|
||||
|
||||
for (int j = 0; j < raw_record_count; j++) {
|
||||
free(raw_records[j]);
|
||||
}
|
||||
free(raw_records);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copy data (skipping the 3 header bytes)
|
||||
memcpy((*records)[i].data, raw + 3, data_len);
|
||||
(*records)[i].data_len = data_len;
|
||||
|
||||
// Print detailed TLSA record information
|
||||
print_tlsa_record(&(*records)[i], i);
|
||||
}
|
||||
|
||||
// Set record count and free raw records
|
||||
*record_count = raw_record_count;
|
||||
|
||||
for (int i = 0; i < raw_record_count; i++) {
|
||||
free(raw_records[i]);
|
||||
}
|
||||
free(raw_records);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int is_dane_available(const char* hostname) {
|
||||
tlsa_record* records = NULL;
|
||||
int record_count = 0;
|
||||
|
||||
int result = get_tlsa_records(hostname, &records, &record_count);
|
||||
|
||||
// Free any allocated records
|
||||
if (records) {
|
||||
for (int i = 0; i < record_count; i++) {
|
||||
if (records[i].data) {
|
||||
free(records[i].data);
|
||||
}
|
||||
}
|
||||
free(records);
|
||||
}
|
||||
|
||||
return result && record_count > 0;
|
||||
}
|
||||
|
||||
// Extract and hash the certificate based on selector and matching type
|
||||
static int extract_and_hash_cert(X509* cert, int selector, int matching_type,
|
||||
unsigned char** hash_out, size_t* hash_len_out) {
|
||||
unsigned char* data = NULL;
|
||||
size_t data_len = 0;
|
||||
unsigned char* hash = NULL;
|
||||
|
||||
// Extract certificate data based on selector
|
||||
if (selector == 0) {
|
||||
// Full certificate
|
||||
data_len = i2d_X509(cert, &data);
|
||||
if (data_len <= 0) {
|
||||
fprintf(stderr, "Failed to convert certificate to DER format\n");
|
||||
return 0;
|
||||
}
|
||||
} else if (selector == 1) {
|
||||
// Subject Public Key Info
|
||||
EVP_PKEY* pubkey = X509_get_pubkey(cert);
|
||||
if (!pubkey) {
|
||||
fprintf(stderr, "Failed to extract public key from certificate\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Convert public key to DER format
|
||||
data_len = i2d_PUBKEY(pubkey, &data);
|
||||
EVP_PKEY_free(pubkey);
|
||||
|
||||
if (data_len <= 0) {
|
||||
fprintf(stderr, "Failed to convert public key to DER format\n");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Unsupported selector: %d\n", selector);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Apply hash based on matching type
|
||||
if (matching_type == 0) {
|
||||
// Exact match (no hash)
|
||||
*hash_out = data;
|
||||
*hash_len_out = data_len;
|
||||
return 1;
|
||||
} else if (matching_type == 1) {
|
||||
// SHA-256 hash
|
||||
hash = malloc(SHA256_DIGEST_LENGTH);
|
||||
if (!hash) {
|
||||
OPENSSL_free(data);
|
||||
fprintf(stderr, "Failed to allocate memory for SHA-256 hash\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
SHA256(data, data_len, hash);
|
||||
OPENSSL_free(data);
|
||||
|
||||
*hash_out = hash;
|
||||
*hash_len_out = SHA256_DIGEST_LENGTH;
|
||||
return 1;
|
||||
} else if (matching_type == 2) {
|
||||
// SHA-512 hash
|
||||
hash = malloc(SHA512_DIGEST_LENGTH);
|
||||
if (!hash) {
|
||||
OPENSSL_free(data);
|
||||
fprintf(stderr, "Failed to allocate memory for SHA-512 hash\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
SHA512(data, data_len, hash);
|
||||
OPENSSL_free(data);
|
||||
|
||||
*hash_out = hash;
|
||||
*hash_len_out = SHA512_DIGEST_LENGTH;
|
||||
return 1;
|
||||
} else {
|
||||
OPENSSL_free(data);
|
||||
fprintf(stderr, "Unsupported matching type: %d\n", matching_type);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to compare hashes and print results
|
||||
static int compare_hashes(const unsigned char* expected, size_t expected_len,
|
||||
const unsigned char* actual, size_t actual_len) {
|
||||
if (expected_len != actual_len) {
|
||||
printf("Hash length mismatch: expected %zu bytes, got %zu bytes\n", expected_len, actual_len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int result = memcmp(expected, actual, expected_len) == 0;
|
||||
|
||||
printf("Certificate hash comparison: %s\n", result ? "MATCH" : "MISMATCH");
|
||||
|
||||
printf("Expected hash:\n");
|
||||
for (size_t i = 0; i < expected_len; i++) {
|
||||
if (i % 16 == 0) {
|
||||
printf(" %04zx: ", i);
|
||||
}
|
||||
printf("%02x", expected[i]);
|
||||
if ((i + 1) % 16 == 0 || i + 1 == expected_len) {
|
||||
printf("\n");
|
||||
} else if ((i + 1) % 8 == 0) {
|
||||
printf(" ");
|
||||
} else {
|
||||
printf(" ");
|
||||
}
|
||||
}
|
||||
|
||||
printf("Actual hash:\n");
|
||||
for (size_t i = 0; i < actual_len; i++) {
|
||||
if (i % 16 == 0) {
|
||||
printf(" %04zx: ", i);
|
||||
}
|
||||
printf("%02x", actual[i]);
|
||||
if ((i + 1) % 16 == 0 || i + 1 == actual_len) {
|
||||
printf("\n");
|
||||
} else if ((i + 1) % 8 == 0) {
|
||||
printf(" ");
|
||||
} else {
|
||||
printf(" ");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Verify a certificate against DANE TLSA records
|
||||
int verify_cert_against_dane(const char* hostname, X509* cert) {
|
||||
tlsa_record* records = NULL;
|
||||
int record_count = 0;
|
||||
int verified = 0;
|
||||
|
||||
if (!cert) {
|
||||
fprintf(stderr, "No certificate provided for DANE verification\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!get_tlsa_records(hostname, &records, &record_count)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (record_count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Print records that will be used for verification
|
||||
printf("\n=== Starting DANE verification for %s ===\n", hostname);
|
||||
for (int i = 0; i < record_count; i++) {
|
||||
print_tlsa_record(&records[i], i);
|
||||
}
|
||||
|
||||
// For each TLSA record, verify the certificate
|
||||
for (int i = 0; i < record_count; i++) {
|
||||
tlsa_record* record = &records[i];
|
||||
|
||||
printf("\nVerifying certificate against TLSA record #%d...\n", i + 1);
|
||||
|
||||
// Generate the certificate hash based on selector and matching type
|
||||
unsigned char* cert_hash = NULL;
|
||||
size_t cert_hash_len = 0;
|
||||
|
||||
if (!extract_and_hash_cert(cert, record->selector, record->matching_type,
|
||||
&cert_hash, &cert_hash_len)) {
|
||||
fprintf(stderr, "Failed to extract and hash certificate\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Based on the usage field
|
||||
switch (record->usage) {
|
||||
case 0: // PKIX-TA - CA constraint
|
||||
// Not implemented in this simplified version
|
||||
printf("PKIX-TA validation not implemented\n");
|
||||
break;
|
||||
|
||||
case 1: // PKIX-EE - Service certificate constraint
|
||||
// Not implemented in this simplified version
|
||||
printf("PKIX-EE validation not implemented\n");
|
||||
break;
|
||||
|
||||
case 2: // DANE-TA - Trust anchor assertion
|
||||
// Not implemented in this simplified version
|
||||
printf("DANE-TA validation not implemented\n");
|
||||
break;
|
||||
|
||||
case 3: // DANE-EE (3) - Domain-issued certificate
|
||||
// Verify the certificate directly against the TLSA record
|
||||
printf("Performing DANE-EE validation...\n");
|
||||
verified = compare_hashes(record->data, record->data_len,
|
||||
cert_hash, cert_hash_len);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unknown usage type
|
||||
printf("Unknown usage type: %d\n", record->usage);
|
||||
break;
|
||||
}
|
||||
|
||||
free(cert_hash);
|
||||
|
||||
if (verified) {
|
||||
printf("DANE verification succeeded for record #%d\n", i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
printf("\nDANE verification result: %s\n", verified ? "SUCCESS" : "FAILED");
|
||||
|
||||
// Clean up
|
||||
for (int i = 0; i < record_count; i++) {
|
||||
if (records[i].data) {
|
||||
free(records[i].data);
|
||||
}
|
||||
}
|
||||
free(records);
|
||||
|
||||
return verified;
|
||||
}
|
||||
|
||||
// Initialize OpenSSL
|
||||
static int init_openssl() {
|
||||
SSL_load_error_strings();
|
||||
OpenSSL_add_ssl_algorithms();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create directories for certificates
|
||||
static int create_cert_directories() {
|
||||
// Create CA directory
|
||||
if (mkdir("ca", 0700) != 0 && errno != EEXIST) {
|
||||
perror("Failed to create CA directory");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create certificates directory
|
||||
if (mkdir(CERT_DIR, 0700) != 0 && errno != EEXIST) {
|
||||
perror("Failed to create certificates directory");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Generate a new CA certificate if one doesn't exist
|
||||
static int generate_ca_cert() {
|
||||
// Check if CA certificate already exists
|
||||
if (access(CA_CERT_FILE, F_OK) == 0 && access(CA_KEY_FILE, F_OK) == 0) {
|
||||
printf("CA certificate already exists\n");
|
||||
// Load the existing CA certificate and key
|
||||
FILE* ca_cert_file = fopen(CA_CERT_FILE, "r");
|
||||
if (!ca_cert_file) {
|
||||
perror("Failed to open CA certificate file");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ca_cert = PEM_read_X509(ca_cert_file, NULL, NULL, NULL);
|
||||
fclose(ca_cert_file);
|
||||
|
||||
if (!ca_cert) {
|
||||
fprintf(stderr, "Failed to read CA certificate\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
FILE* ca_key_file = fopen(CA_KEY_FILE, "r");
|
||||
if (!ca_key_file) {
|
||||
perror("Failed to open CA key file");
|
||||
X509_free(ca_cert);
|
||||
ca_cert = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ca_key = PEM_read_PrivateKey(ca_key_file, NULL, NULL, NULL);
|
||||
fclose(ca_key_file);
|
||||
|
||||
if (!ca_key) {
|
||||
fprintf(stderr, "Failed to read CA key\n");
|
||||
X509_free(ca_cert);
|
||||
ca_cert = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Generate a new CA key
|
||||
ca_key = EVP_PKEY_new();
|
||||
RSA* rsa = NULL;
|
||||
BIGNUM* bn = BN_new();
|
||||
|
||||
if (!bn) {
|
||||
fprintf(stderr, "Failed to create BIGNUM\n");
|
||||
EVP_PKEY_free(ca_key);
|
||||
ca_key = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
BN_set_word(bn, RSA_F4);
|
||||
rsa = RSA_new();
|
||||
|
||||
if (!rsa) {
|
||||
fprintf(stderr, "Failed to create RSA\n");
|
||||
BN_free(bn);
|
||||
EVP_PKEY_free(ca_key);
|
||||
ca_key = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (RSA_generate_key_ex(rsa, 2048, bn, NULL) != 1) {
|
||||
fprintf(stderr, "Failed to generate RSA key\n");
|
||||
RSA_free(rsa);
|
||||
BN_free(bn);
|
||||
EVP_PKEY_free(ca_key);
|
||||
ca_key = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
BN_free(bn);
|
||||
|
||||
if (!EVP_PKEY_assign_RSA(ca_key, rsa)) {
|
||||
fprintf(stderr, "Failed to assign RSA key\n");
|
||||
RSA_free(rsa);
|
||||
EVP_PKEY_free(ca_key);
|
||||
ca_key = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create a new X509 certificate
|
||||
ca_cert = X509_new();
|
||||
X509_set_version(ca_cert, 2);
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(ca_cert), 1);
|
||||
|
||||
// Set validity period (10 years)
|
||||
X509_gmtime_adj(X509_get_notBefore(ca_cert), 0);
|
||||
X509_gmtime_adj(X509_get_notAfter(ca_cert), 60 * 60 * 24 * 365 * 10);
|
||||
|
||||
// Set the public key
|
||||
X509_set_pubkey(ca_cert, ca_key);
|
||||
|
||||
// Set certificate information
|
||||
X509_NAME* name = X509_get_subject_name(ca_cert);
|
||||
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*)"FireProxy CA", -1, -1, 0);
|
||||
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*)"FireProxy", -1, -1, 0);
|
||||
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*)"US", -1, -1, 0);
|
||||
|
||||
// Self-sign the certificate
|
||||
X509_set_issuer_name(ca_cert, name);
|
||||
|
||||
// Add CA extension
|
||||
X509V3_CTX ctx;
|
||||
X509V3_set_ctx_nodb(&ctx);
|
||||
X509V3_set_ctx(&ctx, ca_cert, ca_cert, NULL, NULL, 0);
|
||||
X509_EXTENSION* ext = X509V3_EXT_conf_nid(NULL, &ctx, NID_basic_constraints, "critical,CA:TRUE");
|
||||
|
||||
if (!ext) {
|
||||
fprintf(stderr, "Failed to create CA extension\n");
|
||||
EVP_PKEY_free(ca_key);
|
||||
X509_free(ca_cert);
|
||||
ca_key = NULL;
|
||||
ca_cert = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
X509_add_ext(ca_cert, ext, -1);
|
||||
X509_EXTENSION_free(ext);
|
||||
|
||||
// Sign the certificate
|
||||
if (!X509_sign(ca_cert, ca_key, EVP_sha256())) {
|
||||
fprintf(stderr, "Failed to sign CA certificate\n");
|
||||
EVP_PKEY_free(ca_key);
|
||||
X509_free(ca_cert);
|
||||
ca_key = NULL;
|
||||
ca_cert = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Save the CA certificate and key
|
||||
FILE* ca_cert_file = fopen(CA_CERT_FILE, "w");
|
||||
if (!ca_cert_file) {
|
||||
perror("Failed to create CA certificate file");
|
||||
EVP_PKEY_free(ca_key);
|
||||
X509_free(ca_cert);
|
||||
ca_key = NULL;
|
||||
ca_cert = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
PEM_write_X509(ca_cert_file, ca_cert);
|
||||
fclose(ca_cert_file);
|
||||
|
||||
FILE* ca_key_file = fopen(CA_KEY_FILE, "w");
|
||||
if (!ca_key_file) {
|
||||
perror("Failed to create CA key file");
|
||||
EVP_PKEY_free(ca_key);
|
||||
X509_free(ca_cert);
|
||||
ca_key = NULL;
|
||||
ca_cert = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
PEM_write_PrivateKey(ca_key_file, ca_key, NULL, NULL, 0, NULL, NULL);
|
||||
fclose(ca_key_file);
|
||||
|
||||
printf("Generated new CA certificate\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int setup_local_ca() {
|
||||
if (!create_cert_directories()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!generate_ca_cert()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("CA setup complete\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int generate_trusted_cert(const char* hostname, const char* cert_path, const char* key_path) {
|
||||
if (!ca_cert || !ca_key) {
|
||||
fprintf(stderr, "CA not initialized\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Generate a new key pair
|
||||
EVP_PKEY* pkey = EVP_PKEY_new();
|
||||
RSA* rsa = NULL;
|
||||
BIGNUM* bn = BN_new();
|
||||
|
||||
if (!bn) {
|
||||
fprintf(stderr, "Failed to create BIGNUM\n");
|
||||
EVP_PKEY_free(pkey);
|
||||
return 0;
|
||||
}
|
||||
|
||||
BN_set_word(bn, RSA_F4);
|
||||
rsa = RSA_new();
|
||||
|
||||
if (!rsa) {
|
||||
fprintf(stderr, "Failed to create RSA\n");
|
||||
BN_free(bn);
|
||||
EVP_PKEY_free(pkey);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (RSA_generate_key_ex(rsa, 2048, bn, NULL) != 1) {
|
||||
fprintf(stderr, "Failed to generate RSA key\n");
|
||||
RSA_free(rsa);
|
||||
BN_free(bn);
|
||||
EVP_PKEY_free(pkey);
|
||||
return 0;
|
||||
}
|
||||
|
||||
BN_free(bn);
|
||||
|
||||
if (!EVP_PKEY_assign_RSA(pkey, rsa)) {
|
||||
fprintf(stderr, "Failed to assign RSA key\n");
|
||||
RSA_free(rsa);
|
||||
EVP_PKEY_free(pkey);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create a new certificate
|
||||
X509* cert = X509_new();
|
||||
X509_set_version(cert, 2);
|
||||
|
||||
// Set a random serial number
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(cert), rand());
|
||||
|
||||
// Set validity period (1 year)
|
||||
X509_gmtime_adj(X509_get_notBefore(cert), 0);
|
||||
X509_gmtime_adj(X509_get_notAfter(cert), 60 * 60 * 24 * 365);
|
||||
|
||||
// Set the public key
|
||||
X509_set_pubkey(cert, pkey);
|
||||
|
||||
// Set certificate subject
|
||||
X509_NAME* name = X509_get_subject_name(cert);
|
||||
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*)hostname, -1, -1, 0);
|
||||
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*)"FireProxy Secured", -1, -1, 0);
|
||||
|
||||
// Set certificate issuer (our CA)
|
||||
X509_set_issuer_name(cert, X509_get_subject_name(ca_cert));
|
||||
|
||||
// Add extensions
|
||||
X509V3_CTX ctx;
|
||||
X509V3_set_ctx_nodb(&ctx);
|
||||
X509V3_set_ctx(&ctx, ca_cert, cert, NULL, NULL, 0);
|
||||
|
||||
// Add Subject Alternative Name for the hostname
|
||||
char san[256];
|
||||
snprintf(san, sizeof(san), "DNS:%s", hostname);
|
||||
X509_EXTENSION* ext = X509V3_EXT_conf_nid(NULL, &ctx, NID_subject_alt_name, san);
|
||||
|
||||
if (!ext) {
|
||||
fprintf(stderr, "Failed to create SAN extension\n");
|
||||
EVP_PKEY_free(pkey);
|
||||
X509_free(cert);
|
||||
return 0;
|
||||
}
|
||||
|
||||
X509_add_ext(cert, ext, -1);
|
||||
X509_EXTENSION_free(ext);
|
||||
|
||||
// Sign the certificate with our CA key
|
||||
if (!X509_sign(cert, ca_key, EVP_sha256())) {
|
||||
fprintf(stderr, "Failed to sign certificate\n");
|
||||
EVP_PKEY_free(pkey);
|
||||
X509_free(cert);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Save the certificate
|
||||
FILE* cert_file = fopen(cert_path, "w");
|
||||
if (!cert_file) {
|
||||
perror("Failed to create certificate file");
|
||||
EVP_PKEY_free(pkey);
|
||||
X509_free(cert);
|
||||
return 0;
|
||||
}
|
||||
|
||||
PEM_write_X509(cert_file, cert);
|
||||
fclose(cert_file);
|
||||
|
||||
// Save the private key
|
||||
FILE* key_file = fopen(key_path, "w");
|
||||
if (!key_file) {
|
||||
perror("Failed to create key file");
|
||||
EVP_PKEY_free(pkey);
|
||||
X509_free(cert);
|
||||
return 0;
|
||||
}
|
||||
|
||||
PEM_write_PrivateKey(key_file, pkey, NULL, NULL, 0, NULL, NULL);
|
||||
fclose(key_file);
|
||||
|
||||
// Clean up
|
||||
EVP_PKEY_free(pkey);
|
||||
X509_free(cert);
|
||||
|
||||
printf("Generated trusted certificate for %s\n", hostname);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dane_init() {
|
||||
init_openssl();
|
||||
return setup_local_ca();
|
||||
}
|
||||
|
||||
void dane_cleanup() {
|
||||
if (ca_cert) {
|
||||
X509_free(ca_cert);
|
||||
ca_cert = NULL;
|
||||
}
|
||||
|
||||
if (ca_key) {
|
||||
EVP_PKEY_free(ca_key);
|
||||
ca_key = NULL;
|
||||
}
|
||||
|
||||
EVP_cleanup();
|
||||
}
|
||||
64
src/dane.h
Normal file
64
src/dane.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#ifndef DANE_H
|
||||
#define DANE_H
|
||||
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
/**
|
||||
* DANE TLSA record structure
|
||||
*/
|
||||
typedef struct {
|
||||
unsigned char usage; // Certificate usage (0-3)
|
||||
unsigned char selector; // Selector (0-1)
|
||||
unsigned char matching_type; // Matching type (0-2)
|
||||
unsigned char *data; // Certificate association data
|
||||
size_t data_len; // Length of the data
|
||||
} tlsa_record;
|
||||
|
||||
/**
|
||||
* Check if DANE is available for a domain by querying for TLSA records
|
||||
*
|
||||
* @param hostname The hostname to check
|
||||
* @return 1 if DANE is available, 0 otherwise
|
||||
*/
|
||||
int is_dane_available(const char* hostname);
|
||||
|
||||
/**
|
||||
* Verify a certificate against DANE TLSA records
|
||||
*
|
||||
* @param hostname The hostname
|
||||
* @param cert The certificate to verify
|
||||
* @return 1 if valid, 0 if invalid, -1 on error
|
||||
*/
|
||||
int verify_cert_against_dane(const char* hostname, X509* cert);
|
||||
|
||||
/**
|
||||
* Generate a new certificate for the specified hostname signed by our local CA
|
||||
*
|
||||
* @param hostname The hostname for the certificate
|
||||
* @param cert_path Where to store the generated certificate
|
||||
* @param key_path Where to store the generated private key
|
||||
* @return 1 on success, 0 on failure
|
||||
*/
|
||||
int generate_trusted_cert(const char* hostname, const char* cert_path, const char* key_path);
|
||||
|
||||
/**
|
||||
* Setup the local Certificate Authority for signing certificates
|
||||
*
|
||||
* @return 1 on success, 0 on failure
|
||||
*/
|
||||
int setup_local_ca();
|
||||
|
||||
/**
|
||||
* Initialize the DANE subsystem
|
||||
*
|
||||
* @return 1 on success, 0 on failure
|
||||
*/
|
||||
int dane_init();
|
||||
|
||||
/**
|
||||
* Clean up DANE resources
|
||||
*/
|
||||
void dane_cleanup();
|
||||
|
||||
#endif // DANE_H
|
||||
224
src/doh.c
224
src/doh.c
@@ -100,6 +100,60 @@ void create_dns_query(const char* hostname, char** query, int* query_len) {
|
||||
*query_len = packet_size;
|
||||
}
|
||||
|
||||
// Create a DNS query for TLSA records
|
||||
void create_tlsa_dns_query(const char* hostname, char** query, int* query_len) {
|
||||
// Prepare TLSA query name: _443._tcp.hostname
|
||||
char full_name[512];
|
||||
snprintf(full_name, sizeof(full_name), "_443._tcp.%s", hostname);
|
||||
|
||||
// Calculate the size of the query
|
||||
int hostname_len = strlen(full_name);
|
||||
int packet_size = sizeof(dns_header) + hostname_len + 2 + 4; // +2 for length bytes, +4 for qtype and qclass
|
||||
|
||||
*query = malloc(packet_size);
|
||||
if (!*query) {
|
||||
printf("Error: Failed to allocate memory for DNS query\n");
|
||||
*query_len = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the entire buffer to 0
|
||||
memset(*query, 0, packet_size);
|
||||
|
||||
// Setup DNS header
|
||||
dns_header* header = (dns_header*)*query;
|
||||
header->id = htons(12346); // Different ID from A record queries
|
||||
header->rd = 1; // Recursion desired
|
||||
header->qdcount = htons(1); // One question
|
||||
|
||||
// Add the query
|
||||
char* qname = *query + sizeof(dns_header);
|
||||
char* hostname_copy = strdup(full_name);
|
||||
|
||||
// Convert hostname to DNS format (length byte + name part)
|
||||
char* token = strtok(hostname_copy, ".");
|
||||
char* cursor = qname;
|
||||
while (token) {
|
||||
size_t len = strlen(token);
|
||||
*cursor = len;
|
||||
cursor++;
|
||||
memcpy(cursor, token, len);
|
||||
cursor += len;
|
||||
token = strtok(NULL, ".");
|
||||
}
|
||||
free(hostname_copy);
|
||||
|
||||
// Set the query type and class after the name
|
||||
char* qinfo = qname + hostname_len + 2;
|
||||
unsigned short qtype = htons(52); // TLSA record type
|
||||
unsigned short qclass = htons(1); // IN class
|
||||
|
||||
memcpy(qinfo, &qtype, sizeof(qtype));
|
||||
memcpy(qinfo + sizeof(qtype), &qclass, sizeof(qclass));
|
||||
|
||||
*query_len = packet_size;
|
||||
}
|
||||
|
||||
// Extract an IP address from the DNS response
|
||||
int extract_ip_from_dns_response(const char* response, int response_len, char* ip_buffer, size_t buffer_size) {
|
||||
if (response_len < sizeof(dns_header)) {
|
||||
@@ -160,6 +214,110 @@ int extract_ip_from_dns_response(const char* response, int response_len, char* i
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Extract TLSA records from DNS response
|
||||
int extract_tlsa_records_from_dns_response(const char* response, int response_len,
|
||||
unsigned char*** data_buffer, int* record_count) {
|
||||
if (response_len < sizeof(dns_header)) {
|
||||
printf("Error: Response too short for TLSA query\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Parse the DNS header
|
||||
dns_header* header = (dns_header*)response;
|
||||
int answer_count = ntohs(header->ancount);
|
||||
|
||||
if (answer_count == 0) {
|
||||
printf("No TLSA records found\n");
|
||||
*record_count = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("Found %d TLSA records\n", answer_count);
|
||||
|
||||
// Allocate memory for record data pointers
|
||||
*data_buffer = (unsigned char**)malloc(answer_count * sizeof(unsigned char*));
|
||||
if (!*data_buffer) {
|
||||
printf("Error: Failed to allocate memory for TLSA records\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Initialize record count
|
||||
*record_count = 0;
|
||||
|
||||
// Skip the header
|
||||
int offset = sizeof(dns_header);
|
||||
|
||||
// Skip the question section
|
||||
// First find the end of the domain name
|
||||
while (offset < response_len && response[offset] != 0) {
|
||||
offset += response[offset] + 1;
|
||||
}
|
||||
offset += 5; // Skip the null byte, qtype, and qclass
|
||||
|
||||
// Now parse the answer section
|
||||
for (int i = 0; i < answer_count && offset < response_len; i++) {
|
||||
// Skip the name field (could be a pointer)
|
||||
if ((response[offset] & 0xC0) == 0xC0) {
|
||||
offset += 2; // Compressed name pointer
|
||||
} else {
|
||||
// Skip the uncompressed name
|
||||
while (offset < response_len && response[offset] != 0) {
|
||||
offset += response[offset] + 1;
|
||||
}
|
||||
offset++; // Skip the null byte
|
||||
}
|
||||
|
||||
// Ensure we have enough data for the record
|
||||
if (offset + 10 > response_len) {
|
||||
printf("Error: Response too short for complete record\n");
|
||||
break;
|
||||
}
|
||||
|
||||
// Read the type
|
||||
unsigned short type;
|
||||
memcpy(&type, response + offset, sizeof(type));
|
||||
type = ntohs(type);
|
||||
offset += 2;
|
||||
|
||||
// Skip class
|
||||
offset += 2;
|
||||
|
||||
// Skip TTL
|
||||
offset += 4;
|
||||
|
||||
// Read the data length
|
||||
unsigned short rdlength;
|
||||
memcpy(&rdlength, response + offset, sizeof(rdlength));
|
||||
rdlength = ntohs(rdlength);
|
||||
offset += 2;
|
||||
|
||||
// Check if it's a TLSA record (type 52)
|
||||
if (type == 52 && rdlength >= 3) { // 3 bytes minimum: usage, selector, matching type
|
||||
// Allocate memory for TLSA record data
|
||||
unsigned char* data = (unsigned char*)malloc(rdlength);
|
||||
if (!data) {
|
||||
printf("Error: Failed to allocate memory for TLSA record data\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Copy the TLSA record data
|
||||
memcpy(data, response + offset, rdlength);
|
||||
|
||||
// Store the record data
|
||||
(*data_buffer)[*record_count] = data;
|
||||
(*record_count)++;
|
||||
|
||||
printf("TLSA record: Usage=%d, Selector=%d, MatchingType=%d, Data length=%d\n",
|
||||
data[0], data[1], data[2], rdlength - 3);
|
||||
}
|
||||
|
||||
// Move to the next record
|
||||
offset += rdlength;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int resolve_doh(const char* hostname, char* ip_buffer, size_t buffer_size) {
|
||||
CURL* curl;
|
||||
CURLcode res;
|
||||
@@ -224,3 +382,69 @@ int resolve_doh(const char* hostname, char* ip_buffer, size_t buffer_size) {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Query for TLSA records
|
||||
int query_tlsa_records_doh(const char* hostname, unsigned char*** data_buffer, int* record_count) {
|
||||
CURL* curl;
|
||||
CURLcode res;
|
||||
response_data resp;
|
||||
resp.data = malloc(1);
|
||||
resp.size = 0;
|
||||
|
||||
curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
printf("Error: Failed to initialize cURL for TLSA query\n");
|
||||
free(resp.data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create DNS query for TLSA records in wire format
|
||||
char* dns_query = NULL;
|
||||
int query_len = 0;
|
||||
create_tlsa_dns_query(hostname, &dns_query, &query_len);
|
||||
|
||||
if (!dns_query || query_len == 0) {
|
||||
printf("Error: Failed to create DNS query for TLSA records\n");
|
||||
curl_easy_cleanup(curl);
|
||||
free(resp.data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Set up cURL options for DoH wire format
|
||||
curl_easy_setopt(curl, CURLOPT_URL, DOH_SERVER);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&resp);
|
||||
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, dns_query);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, query_len);
|
||||
|
||||
// Set the content-type header for DNS wireformat
|
||||
struct curl_slist* headers = NULL;
|
||||
headers = curl_slist_append(headers, "Content-Type: application/dns-message");
|
||||
headers = curl_slist_append(headers, "Accept: application/dns-message");
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
|
||||
// Perform the request
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
printf("Error: cURL request failed for TLSA query: %s\n", curl_easy_strerror(res));
|
||||
curl_slist_free_all(headers);
|
||||
curl_easy_cleanup(curl);
|
||||
free(dns_query);
|
||||
free(resp.data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("DoH TLSA response received, size: %zu bytes\n", resp.size);
|
||||
|
||||
// Parse the DNS wire format response to extract TLSA records
|
||||
int result = extract_tlsa_records_from_dns_response(resp.data, resp.size, data_buffer, record_count);
|
||||
|
||||
// Clean up
|
||||
curl_slist_free_all(headers);
|
||||
curl_easy_cleanup(curl);
|
||||
free(dns_query);
|
||||
free(resp.data);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
10
src/doh.h
10
src/doh.h
@@ -13,4 +13,14 @@
|
||||
*/
|
||||
int resolve_doh(const char* hostname, char* ip_buffer, size_t buffer_size);
|
||||
|
||||
/**
|
||||
* Query for TLSA records using DNS-over-HTTPS (DoH)
|
||||
*
|
||||
* @param hostname The hostname to query
|
||||
* @param data_buffer Pointer to buffer that will be allocated to store the raw data
|
||||
* @param record_count Pointer to store the number of records found
|
||||
* @return 0 on success, non-zero on failure
|
||||
*/
|
||||
int query_tlsa_records_doh(const char* hostname, unsigned char*** data_buffer, int* record_count);
|
||||
|
||||
#endif // DOH_H
|
||||
|
||||
@@ -17,6 +17,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
printf("Starting FireProxy on port %d...\n", port);
|
||||
printf("Using DoH server: https://hnsdoh.com/dns-query\n");
|
||||
printf("DANE support: enabled\n");
|
||||
|
||||
// Start the proxy server
|
||||
if (start_proxy_server(port) != 0) {
|
||||
|
||||
270
src/proxy.c
270
src/proxy.c
@@ -1,5 +1,6 @@
|
||||
#include "proxy.h"
|
||||
#include "doh.h"
|
||||
#include "dane.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -11,6 +12,10 @@
|
||||
#include <netdb.h>
|
||||
#include <ctype.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#define MAX_REQUEST_SIZE 8192
|
||||
#define MAX_URL_LENGTH 2048
|
||||
@@ -91,8 +96,186 @@ int extract_port(const char* request) {
|
||||
return default_port;
|
||||
}
|
||||
|
||||
// Handle HTTPS CONNECT tunneling
|
||||
void handle_https_tunnel(int client_sock, int server_sock) {
|
||||
// Initialize SSL context for intercepting HTTPS
|
||||
ssl_context_t* init_ssl_context(const char* cert_path, const char* key_path) {
|
||||
ssl_context_t* ssl_ctx = malloc(sizeof(ssl_context_t));
|
||||
if (!ssl_ctx) {
|
||||
fprintf(stderr, "Failed to allocate memory for SSL context\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Initialize SSL context
|
||||
ssl_ctx->ctx = SSL_CTX_new(TLS_server_method());
|
||||
if (!ssl_ctx->ctx) {
|
||||
fprintf(stderr, "Failed to create SSL context\n");
|
||||
free(ssl_ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Configure SSL context
|
||||
SSL_CTX_set_options(ssl_ctx->ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION);
|
||||
|
||||
// Load certificate and private key
|
||||
if (SSL_CTX_use_certificate_file(ssl_ctx->ctx, cert_path, SSL_FILETYPE_PEM) <= 0) {
|
||||
fprintf(stderr, "Failed to load certificate: %s\n", cert_path);
|
||||
SSL_CTX_free(ssl_ctx->ctx);
|
||||
free(ssl_ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (SSL_CTX_use_PrivateKey_file(ssl_ctx->ctx, key_path, SSL_FILETYPE_PEM) <= 0) {
|
||||
fprintf(stderr, "Failed to load private key: %s\n", key_path);
|
||||
SSL_CTX_free(ssl_ctx->ctx);
|
||||
free(ssl_ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Verify private key
|
||||
if (!SSL_CTX_check_private_key(ssl_ctx->ctx)) {
|
||||
fprintf(stderr, "Private key does not match the certificate\n");
|
||||
SSL_CTX_free(ssl_ctx->ctx);
|
||||
free(ssl_ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ssl_ctx;
|
||||
}
|
||||
|
||||
// Handle HTTPS CONNECT tunneling with DANE verification
|
||||
void handle_https_tunnel(int client_sock, int server_sock, const char* hostname, const char* ip_addr) {
|
||||
// Unused parameter
|
||||
(void)ip_addr;
|
||||
|
||||
// Check if we have DANE records for this domain
|
||||
int has_dane = is_dane_available(hostname);
|
||||
|
||||
if (has_dane) {
|
||||
printf("DANE records found for %s, will verify certificate\n", hostname);
|
||||
|
||||
char cert_path[256];
|
||||
char key_path[256];
|
||||
|
||||
snprintf(cert_path, sizeof(cert_path), "certs/%s.crt", hostname);
|
||||
snprintf(key_path, sizeof(key_path), "certs/%s.key", hostname);
|
||||
|
||||
// Generate a trusted certificate for this domain
|
||||
if (!generate_trusted_cert(hostname, cert_path, key_path)) {
|
||||
fprintf(stderr, "Failed to generate trusted certificate for %s\n", hostname);
|
||||
// Fall back to regular tunneling
|
||||
handle_regular_https_tunnel(client_sock, server_sock);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize SSL context with our certificate
|
||||
ssl_context_t* client_ctx = init_ssl_context(cert_path, key_path);
|
||||
if (!client_ctx) {
|
||||
fprintf(stderr, "Failed to initialize SSL context\n");
|
||||
handle_regular_https_tunnel(client_sock, server_sock);
|
||||
return;
|
||||
}
|
||||
|
||||
// Connect to the server with SSL to verify DANE
|
||||
SSL_CTX* server_ctx = SSL_CTX_new(TLS_client_method());
|
||||
if (!server_ctx) {
|
||||
fprintf(stderr, "Failed to create server SSL context\n");
|
||||
SSL_CTX_free(client_ctx->ctx);
|
||||
free(client_ctx);
|
||||
handle_regular_https_tunnel(client_sock, server_sock);
|
||||
return;
|
||||
}
|
||||
|
||||
SSL* server_ssl = SSL_new(server_ctx);
|
||||
SSL_set_fd(server_ssl, server_sock);
|
||||
|
||||
// Set SNI hostname
|
||||
SSL_set_tlsext_host_name(server_ssl, hostname);
|
||||
|
||||
// Connect to the server with SSL
|
||||
if (SSL_connect(server_ssl) <= 0) {
|
||||
fprintf(stderr, "SSL connection to server failed\n");
|
||||
SSL_free(server_ssl);
|
||||
SSL_CTX_free(server_ctx);
|
||||
SSL_CTX_free(client_ctx->ctx);
|
||||
free(client_ctx);
|
||||
handle_regular_https_tunnel(client_sock, server_sock);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the server's certificate
|
||||
X509* server_cert = SSL_get_peer_certificate(server_ssl);
|
||||
if (!server_cert) {
|
||||
fprintf(stderr, "Failed to get server certificate\n");
|
||||
SSL_free(server_ssl);
|
||||
SSL_CTX_free(server_ctx);
|
||||
SSL_CTX_free(client_ctx->ctx);
|
||||
free(client_ctx);
|
||||
handle_regular_https_tunnel(client_sock, server_sock);
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the certificate against DANE
|
||||
int dane_verified = verify_cert_against_dane(hostname, server_cert);
|
||||
if (dane_verified <= 0) {
|
||||
fprintf(stderr, "DANE verification failed for %s\n", hostname);
|
||||
X509_free(server_cert);
|
||||
SSL_free(server_ssl);
|
||||
SSL_CTX_free(server_ctx);
|
||||
SSL_CTX_free(client_ctx->ctx);
|
||||
free(client_ctx);
|
||||
handle_regular_https_tunnel(client_sock, server_sock);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("DANE verification successful for %s\n", hostname);
|
||||
|
||||
// Send 200 Connection Established to the client
|
||||
const char* success_response = "HTTP/1.1 200 Connection Established\r\n\r\n";
|
||||
if (send(client_sock, success_response, strlen(success_response), 0) < 0) {
|
||||
perror("Failed to send connection established response");
|
||||
X509_free(server_cert);
|
||||
SSL_free(server_ssl);
|
||||
SSL_CTX_free(server_ctx);
|
||||
SSL_CTX_free(client_ctx->ctx);
|
||||
free(client_ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize SSL connection with the client
|
||||
client_ctx->ssl = SSL_new(client_ctx->ctx);
|
||||
SSL_set_fd(client_ctx->ssl, client_sock);
|
||||
|
||||
if (SSL_accept(client_ctx->ssl) <= 0) {
|
||||
fprintf(stderr, "SSL accept failed\n");
|
||||
SSL_free(client_ctx->ssl);
|
||||
X509_free(server_cert);
|
||||
SSL_free(server_ssl);
|
||||
SSL_CTX_free(server_ctx);
|
||||
SSL_CTX_free(client_ctx->ctx);
|
||||
free(client_ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("SSL connection established with client for %s\n", hostname);
|
||||
|
||||
// Now we have SSL connections to both client and server
|
||||
// We can forward data between them
|
||||
ssl_tunnel_data(client_ctx->ssl, server_ssl);
|
||||
|
||||
// Clean up
|
||||
SSL_free(client_ctx->ssl);
|
||||
X509_free(server_cert);
|
||||
SSL_free(server_ssl);
|
||||
SSL_CTX_free(server_ctx);
|
||||
SSL_CTX_free(client_ctx->ctx);
|
||||
free(client_ctx);
|
||||
} else {
|
||||
// No DANE records, use regular tunneling
|
||||
handle_regular_https_tunnel(client_sock, server_sock);
|
||||
}
|
||||
}
|
||||
|
||||
// Regular HTTPS tunneling without interception
|
||||
void handle_regular_https_tunnel(int client_sock, int server_sock) {
|
||||
fd_set read_fds;
|
||||
char buffer[MAX_REQUEST_SIZE];
|
||||
int max_fd = (client_sock > server_sock) ? client_sock : server_sock;
|
||||
@@ -129,6 +312,62 @@ void handle_https_tunnel(int client_sock, int server_sock) {
|
||||
}
|
||||
}
|
||||
|
||||
// Forward data between SSL connections
|
||||
void ssl_tunnel_data(SSL* client_ssl, SSL* server_ssl) {
|
||||
fd_set read_fds;
|
||||
char buffer[MAX_REQUEST_SIZE];
|
||||
int client_fd = SSL_get_fd(client_ssl);
|
||||
int server_fd = SSL_get_fd(server_ssl);
|
||||
int max_fd = (client_fd > server_fd) ? client_fd : server_fd;
|
||||
|
||||
while (1) {
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(client_fd, &read_fds);
|
||||
FD_SET(server_fd, &read_fds);
|
||||
|
||||
if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) < 0) {
|
||||
perror("Select failed");
|
||||
break;
|
||||
}
|
||||
|
||||
if (FD_ISSET(client_fd, &read_fds)) {
|
||||
int bytes_received = SSL_read(client_ssl, buffer, sizeof(buffer));
|
||||
if (bytes_received <= 0) {
|
||||
int err = SSL_get_error(client_ssl, bytes_received);
|
||||
if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
int bytes_sent = SSL_write(server_ssl, buffer, bytes_received);
|
||||
if (bytes_sent <= 0) {
|
||||
int err = SSL_get_error(server_ssl, bytes_sent);
|
||||
if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FD_ISSET(server_fd, &read_fds)) {
|
||||
int bytes_received = SSL_read(server_ssl, buffer, sizeof(buffer));
|
||||
if (bytes_received <= 0) {
|
||||
int err = SSL_get_error(server_ssl, bytes_received);
|
||||
if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
int bytes_sent = SSL_write(client_ssl, buffer, bytes_received);
|
||||
if (bytes_sent <= 0) {
|
||||
int err = SSL_get_error(client_ssl, bytes_sent);
|
||||
if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modify HTTP request for direct server communication
|
||||
char* rewrite_http_request(const char* original_request, size_t* new_length) {
|
||||
// Find the first line ending
|
||||
@@ -261,8 +500,8 @@ void* handle_client(void* arg) {
|
||||
}
|
||||
|
||||
if (is_connect) {
|
||||
// HTTPS: Handle CONNECT tunnel
|
||||
handle_https_tunnel(client_sock, server_sock);
|
||||
// HTTPS: Handle CONNECT tunnel with DANE support
|
||||
handle_https_tunnel(client_sock, server_sock, host, ip_addr);
|
||||
} else {
|
||||
// HTTP: Rewrite the request and forward it
|
||||
printf("HTTP request received, rewriting for server...\n");
|
||||
@@ -341,6 +580,12 @@ int start_proxy_server(int port) {
|
||||
struct sockaddr_in server_addr, client_addr;
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
|
||||
// Initialize proxy components
|
||||
if (proxy_init() != 0) {
|
||||
fprintf(stderr, "Failed to initialize proxy server\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create socket
|
||||
server_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (server_sock < 0) {
|
||||
@@ -419,10 +664,27 @@ int start_proxy_server(int port) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Initialize the proxy server
|
||||
int proxy_init() {
|
||||
// Initialize DANE support
|
||||
if (!dane_init()) {
|
||||
fprintf(stderr, "Failed to initialize DANE support\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Clean up proxy resources
|
||||
void proxy_cleanup() {
|
||||
dane_cleanup();
|
||||
}
|
||||
|
||||
// Signal handler for graceful termination
|
||||
void handle_signal(int sig) {
|
||||
if (sig == SIGINT) {
|
||||
printf("\nShutting down proxy server...\n");
|
||||
proxy_cleanup();
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
36
src/proxy.h
36
src/proxy.h
@@ -3,6 +3,15 @@
|
||||
|
||||
#include <signal.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
/**
|
||||
* SSL context for HTTPS interception
|
||||
*/
|
||||
typedef struct {
|
||||
SSL_CTX *ctx;
|
||||
SSL *ssl;
|
||||
} ssl_context_t;
|
||||
|
||||
/**
|
||||
* Starts the proxy server on the specified port
|
||||
@@ -17,4 +26,31 @@ int start_proxy_server(int port);
|
||||
*/
|
||||
void handle_signal(int sig);
|
||||
|
||||
/**
|
||||
* Initialize the proxy server components including DANE support
|
||||
*
|
||||
* @return 0 on success, non-zero on failure
|
||||
*/
|
||||
int proxy_init();
|
||||
|
||||
/**
|
||||
* Handle regular HTTPS tunneling without DANE verification
|
||||
*/
|
||||
void handle_regular_https_tunnel(int client_sock, int server_sock);
|
||||
|
||||
/**
|
||||
* Handle HTTPS tunneling with DANE verification
|
||||
*/
|
||||
void handle_https_tunnel(int client_sock, int server_sock, const char* hostname, const char* ip_addr);
|
||||
|
||||
/**
|
||||
* Forward data between SSL connections
|
||||
*/
|
||||
void ssl_tunnel_data(SSL* client_ssl, SSL* server_ssl);
|
||||
|
||||
/**
|
||||
* Cleanup proxy server resources
|
||||
*/
|
||||
void proxy_cleanup();
|
||||
|
||||
#endif // PROXY_H
|
||||
|
||||
Reference in New Issue
Block a user