feat: Get started on DANE

This commit is contained in:
2025-04-23 17:59:43 +10:00
parent 92f4f19d32
commit ad60c9f2b4
10 changed files with 1400 additions and 5 deletions

4
.gitignore vendored
View File

@@ -1,2 +1,6 @@
obj/
fireproxy
certs/
ca/

View File

@@ -1,6 +1,6 @@
CC = gcc
CFLAGS = -Wall -Wextra -pthread
LDFLAGS = -lcurl -ljansson
LDFLAGS = -lcurl -lssl -lcrypto
SRC_DIR = src
OBJ_DIR = obj

View File

@@ -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
View 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
View 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
View File

@@ -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;
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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