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/ obj/
fireproxy fireproxy
certs/
ca/

View File

@@ -1,6 +1,6 @@
CC = gcc CC = gcc
CFLAGS = -Wall -Wextra -pthread CFLAGS = -Wall -Wextra -pthread
LDFLAGS = -lcurl -ljansson LDFLAGS = -lcurl -lssl -lcrypto
SRC_DIR = src SRC_DIR = src
OBJ_DIR = obj 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 3. You should see messages indicating DoH lookups to hnsdoh.com
4. The proxy should log the resolved IP addresses 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 ## Troubleshooting
### Common Issues ### 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; *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 // 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) { 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)) { 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; 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) { int resolve_doh(const char* hostname, char* ip_buffer, size_t buffer_size) {
CURL* curl; CURL* curl;
CURLcode res; CURLcode res;
@@ -224,3 +382,69 @@ int resolve_doh(const char* hostname, char* ip_buffer, size_t buffer_size) {
return result; 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); 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 #endif // DOH_H

View File

@@ -17,6 +17,7 @@ int main(int argc, char *argv[]) {
printf("Starting FireProxy on port %d...\n", port); printf("Starting FireProxy on port %d...\n", port);
printf("Using DoH server: https://hnsdoh.com/dns-query\n"); printf("Using DoH server: https://hnsdoh.com/dns-query\n");
printf("DANE support: enabled\n");
// Start the proxy server // Start the proxy server
if (start_proxy_server(port) != 0) { if (start_proxy_server(port) != 0) {

View File

@@ -1,5 +1,6 @@
#include "proxy.h" #include "proxy.h"
#include "doh.h" #include "doh.h"
#include "dane.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -11,6 +12,10 @@
#include <netdb.h> #include <netdb.h>
#include <ctype.h> #include <ctype.h>
#include <signal.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_REQUEST_SIZE 8192
#define MAX_URL_LENGTH 2048 #define MAX_URL_LENGTH 2048
@@ -91,8 +96,186 @@ int extract_port(const char* request) {
return default_port; return default_port;
} }
// Handle HTTPS CONNECT tunneling // Initialize SSL context for intercepting HTTPS
void handle_https_tunnel(int client_sock, int server_sock) { 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; fd_set read_fds;
char buffer[MAX_REQUEST_SIZE]; char buffer[MAX_REQUEST_SIZE];
int max_fd = (client_sock > server_sock) ? client_sock : server_sock; 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 // Modify HTTP request for direct server communication
char* rewrite_http_request(const char* original_request, size_t* new_length) { char* rewrite_http_request(const char* original_request, size_t* new_length) {
// Find the first line ending // Find the first line ending
@@ -261,8 +500,8 @@ void* handle_client(void* arg) {
} }
if (is_connect) { if (is_connect) {
// HTTPS: Handle CONNECT tunnel // HTTPS: Handle CONNECT tunnel with DANE support
handle_https_tunnel(client_sock, server_sock); handle_https_tunnel(client_sock, server_sock, host, ip_addr);
} else { } else {
// HTTP: Rewrite the request and forward it // HTTP: Rewrite the request and forward it
printf("HTTP request received, rewriting for server...\n"); 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; struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(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 // Create socket
server_sock = socket(AF_INET, SOCK_STREAM, 0); server_sock = socket(AF_INET, SOCK_STREAM, 0);
if (server_sock < 0) { if (server_sock < 0) {
@@ -419,10 +664,27 @@ int start_proxy_server(int port) {
return 0; 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 // Signal handler for graceful termination
void handle_signal(int sig) { void handle_signal(int sig) {
if (sig == SIGINT) { if (sig == SIGINT) {
printf("\nShutting down proxy server...\n"); printf("\nShutting down proxy server...\n");
proxy_cleanup();
exit(0); exit(0);
} }
} }

View File

@@ -3,6 +3,15 @@
#include <signal.h> #include <signal.h>
#include <netinet/tcp.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 * Starts the proxy server on the specified port
@@ -17,4 +26,31 @@ int start_proxy_server(int port);
*/ */
void handle_signal(int sig); 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 #endif // PROXY_H