Initial https proxy work

The proxy can now sit between a client and a https web server. It does
this by looking for a CONNECT request that conventional proxies use to
open a tunnel between a client and an https server. Instead of opening
an opaque tunnel, yaip immediately sends bacck a "connection
established" response. This tells the client (browser normally) to
proceed and initiate an HTTPS connection.

I use the host that was send in the connect request to set up a fake SSL
server. If we have seen the domain before, we re-use the certificate,
otherwise we generate a new one and sign it using YAIP's built in
certificate authority.

I still need to do work on forwarding the request upstream. This is my
next job. Currently, yaip responds with a valid response of "it worked".

```
$ curl https://example.com --cacert ~/.config/yaip/cert.pem
It worked
```

Notice, we don't get any certificate errors because we are telling curl
to trust the authority that yaip uses
master
Jonathan Hodgson 3 years ago
parent 1beca38af6
commit dd71d26245
  1. 10
      src/main.c
  2. 104
      src/proxy.c
  3. 4
      src/proxy.h
  4. 50
      src/readline.c
  5. 9
      src/readline.h
  6. 34
      src/request.c
  7. 6
      src/request.h
  8. 4
      src/response.c
  9. 2
      src/response.h
  10. 41
      src/ssl.c
  11. 85
      src/ssl.h
  12. 48
      src/util.c
  13. 3
      src/util.h
  14. 21
      tests/request.test.c
  15. 32
      tests/ssl.test.c

@ -68,7 +68,10 @@ int startListener(unsigned int port){
int main(int argc, char**argv){
Config *config = configDefaults();
int listener;
CertList *certs = NULL;
CertificateAutority *ca = malloc( sizeof( CertificateAutority ) );
ca->cert = NULL;
ca->pkey = NULL;
ca->certs = NULL;
for ( unsigned int i = 1; i < argc; i++ ){
@ -112,6 +115,7 @@ int main(int argc, char**argv){
return 1;
}
}
ca->pkey = read_private_key( config->keyfile );
if (!path_exists( config->certfile ) ){
printf("Creating cert\n");
@ -119,6 +123,7 @@ int main(int argc, char**argv){
return 1;
}
}
ca->cert = read_cert( config->certfile );
listener = startListener(config->port);
@ -151,12 +156,11 @@ int main(int argc, char**argv){
// necesarily the hosts header
webserverRequest(request, client);
} else {
proxyRequest(request, client, certs);
proxyRequest(request, client, ca);
}
close(client);
freeRequest( request );
}
return 0;

@ -16,7 +16,7 @@ Response *upstreamGetResponse(Request *request){
address.sin_family = AF_INET;
address.sin_port = htons( 80 );
address.sin_port = htons( request->port );
// We want the request to go out to whatever the host was resolved to
memcpy( &address.sin_addr, host->h_addr_list[0], host->h_length );
@ -35,47 +35,77 @@ Response *upstreamGetResponse(Request *request){
rsp = newResponseFromSocket( client_fd );
return rsp;
}
void proxyRequest(Request *request, int client, CertList *certs){
void sendConnectionEstablished(int client){
// If it is a connect request, we are dealing with https
// I am basically doing the same thing that mitmproxy does here
// We start by responding with 200 Connection Established which
// in a normal proxy would mean that we have established a
// connection with the remote host. However, we haven't because we
// are going to pretend to be the host to the client and pretend to
// be the client to the host
Response *response = newResponse();
connectionEstablished(response);
char *responseStr = responseToString(response);
send(client , responseStr, strlen(responseStr) , 0 );
freeResponse( response );
}
void proxyRequest(Request *request, int client, CertificateAutority *ca){
if ( strcmp( request->method, "CONNECT" ) == 0 ){
// If it is a connect request, we are dealing with https
// I am basically doing the same thing that mitmproxy does here
// We start by responding with 200 Connection Established which
// in a normal proxy would mean that we have established a
// connection with the remote host. However, we haven't because we
// are going to pretend to be the host to the client and pretend to
// be the client to the host
Response *response = newResponse();
connectionEstablished(response);
char *responseStr = responseToString(response);
send(client , responseStr, strlen(responseStr) , 0 );
//SSL_CTX *ctx;
//SSL *ssl;
//char buf[1024] = {0};
//int bytes;
//SSL_library_init();
//ctx = InitServerCTX(config);
//ssl = SSL_new(ctx);
//SSL_set_fd( ssl, client );
//if ( SSL_accept(ssl) == -1 ){
// ERR_print_errors_fp(stderr);
//} else {
// bytes = SSL_read(ssl, buf, sizeof(buf));
// buf[bytes] = '\0';
// printf("%s", buf);
//}
sendConnectionEstablished(client);
// All we might need from the connect resquest is the host
char *host = request->host;
freeRequest( request );
// If we already have a host cert for the domain we're dealing with use
// it
CertList *certItem = findCertListItem( ca->certs, host );
if ( certItem == NULL ){
// If we don't, generate a new one
X509 *siteCert = generate_site_cert( ca->pkey, ca->cert, host );
certItem = newCertListItem( host, ca->pkey, siteCert );
if ( ca->certs == NULL ) ca->certs = certItem;
else getLastCertListItem(ca->certs)->next = certItem;
}
SSL_CTX *ctx;
SSL *ssl;
char buf[1024] = {0};
int bytes;
Response *response;
SSL_library_init();
ctx = setup_ctx(certItem);
ssl = SSL_new(ctx);
SSL_set_fd( ssl, client );
if ( SSL_accept(ssl) == -1 ){
ERR_print_errors_fp(stderr);
} else {
bytes = SSL_read(ssl, buf, sizeof(buf));
buf[bytes] = '\0';
request = newRequestFromString( buf );
//If this request doesn't contain a host, use the one from the
//connect request
if ( strlen( request->host ) == 0 ){
free(request->host);
request->host = strdup( host );
}
response = upstreamGetResponse(request);
char *responseStr = responseToString( response );
SSL_write( ssl, responseStr, strlen(responseStr) );
}
SSL_free(ssl);
SSL_CTX_free(ctx);
} else {
Response *response = upstreamGetResponse(request);

@ -17,7 +17,9 @@
#include "ssl.h"
Response *upstreamGetResponse(Request *request);
void sendConnectionEstablished(int client);
void proxyRequest(Request *request, int client, CertificateAutority *ca);
void proxyRequest(Request *request, int client, CertList *certs);
#endif /* ifndef PROXY_H */

@ -1,50 +0,0 @@
// https://man7.org/tlpi/code/online/dist/sockets/read_line.c.html
#include "readline.h"
/* Read characters from 'fd' until a newline is encountered. If a newline
character is not encountered in the first (n - 1) bytes, then the excess
characters are discarded. The returned string placed in 'buf' is
null-terminated and includes the newline character if it was read in the
first (n - 1) bytes. The function return value is the number of bytes
placed in buffer (which includes the newline character if encountered,
but excludes the terminating null byte). */
ssize_t fdReadLine(int fd, void *buffer, size_t n) {
ssize_t numRead; /* # of bytes fetched by last read() */
size_t totRead; /* Total bytes read so far */
char *buf;
char ch;
if (n <= 0 || buffer == NULL) {
errno = EINVAL;
return -1;
}
buf = buffer; /* No pointer arithmetic on "void *" */
totRead = 0;
for (;;) {
numRead = read(fd, &ch, 1);
if (numRead == -1) {
if (errno == EINTR) /* Interrupted --> restart read() */
continue;
else
return -1; /* Some other error */
} else if (numRead == 0) { /* EOF */
if (totRead == 0) /* No bytes read; return 0 */
return 0;
else /* Some bytes read; add '\0' */
break;
} else { /* 'numRead' must be 1 if we get here */
if (totRead < n - 1) { /* Discard > (n - 1) bytes */
totRead++;
*buf++ = ch;
}
if (ch == '\n')
break;
}
}
*buf = '\0';
return totRead;
}

@ -1,9 +0,0 @@
#ifndef READLINE_H
#define READLINE_H
#include <unistd.h>
#include <errno.h>
ssize_t fdReadLine(int fd, void *buffer, size_t n);
#endif /* ifndef READLINE_H */

@ -83,6 +83,7 @@ void requestFirstLine( Request *req, char line[] ){
}
if ( strlen(req->protocol) == 0 ){
free(req->protocol);
if ( req->port == 443 )
req->protocol = strdup("https");
else
@ -122,6 +123,39 @@ Request* newRequestFromSocket(int socket){
return req;
}
Request* newRequestFromString(char *string){
// We don't want to modify the original string
char *str = strdup(string);
Request *req = newRequest();
int valread;
//Find the new line
char *occurance = strchr( str, '\n' );
occurance[0] = '\0';
requestFirstLine(req, str);
str = occurance + 1;
occurance = strchr( str, '\n' );
occurance[0] = '\0';
while ( strlen(str) > 1 ){
requestAddHeader( req, str );
str = occurance + 1;
occurance = strchr( str, '\n' );
occurance[0] = '\0';
}
//TODO: make requests work with a body
//contentLength = getHeader( req->headers, "content-length" );
//if ( contentLength != NULL ){
// printf( "Content length is %i\n", atoi(contentLength->value) );
//}
return req;
}
char* requestToString( Request *req){
unsigned int fullLength = strlen(req->method) + 1 + sizeof( req->path ) +
//11 = [space]http/1.1\r\n

@ -7,10 +7,9 @@
#include <string.h>
#include <netinet/in.h>
#include <math.h>
#include "readline.h"
#include "requestresponse.h"
#include "./util.h"
#include "util.h"
#include "requestresponse.h"
@ -33,6 +32,7 @@ typedef struct {
Request* newRequest();
void requestFirstLine( Request *req, char line[] );
Request* newRequestFromSocket(int socket);
Request* newRequestFromString(char *string);
/*
* requestToString
* @prarm req the request to convert

@ -50,6 +50,10 @@ void responseSetBody(Response *rsp, char *string, bool updateContentLength){
if ( updateContentLength ){
Header *contentLengthHeader = getHeader(rsp->headers, "content-length");
if ( contentLengthHeader == NULL ){
responseAddHeader( rsp, "content-length: 0" );
contentLengthHeader = getHeader(rsp->headers, "content-length");
}
char *value = malloc(sizeof(char) * 20);
sprintf(value, "%lu", strlen(string) );
contentLengthHeader->value = strdup(value);

@ -7,7 +7,7 @@
#include <string.h>
#include <stdbool.h>
#include "readline.h"
#include "util.h"
#include "requestresponse.h"

@ -1,13 +1,14 @@
#include "ssl.h"
SSL_CTX* InitServerCTX(Config *config) {
SSL_CTX* setup_ctx(CertList *certItem) {
const SSL_METHOD *method;
SSL_CTX *ctx;
OpenSSL_add_all_algorithms(); /* load & register all cryptos, etc. */
SSL_load_error_strings(); /* load all error messages */
method = TLS_server_method(); /* create new server-method instance */
ctx = SSL_CTX_new(method); /* create new context from method */
if ( ctx == NULL ) {
ERR_print_errors_fp(stderr);
@ -15,18 +16,18 @@ SSL_CTX* InitServerCTX(Config *config) {
}
//set the local certificate from CertFile
if ( SSL_CTX_use_certificate_file(ctx, config->certfile, SSL_FILETYPE_PEM) <= 0 ){
if ( SSL_CTX_use_certificate(ctx, certItem->cert) <= 0 ){
ERR_print_errors_fp(stderr);
abort();
}
//set the private key from KeyFile (may be the same as CertFile)
if ( SSL_CTX_use_PrivateKey_file(ctx, config->keyfile, SSL_FILETYPE_PEM) <= 0 ){
if ( SSL_CTX_use_PrivateKey(ctx, certItem->key) <= 0 ){
ERR_print_errors_fp(stderr);
abort();
}
//verify private key
if ( !SSL_CTX_check_private_key(ctx) ) {
if ( !SSL_CTX_check_private_key(ctx) ) {
fprintf(stderr, "Private key does not match the public certificate\n");
abort();
}
@ -35,6 +36,7 @@ SSL_CTX* InitServerCTX(Config *config) {
}
// Generates a 2048-bit RSA key.
// Largely stolen from here: https://gist.github.com/nathan-osman/5041136
EVP_PKEY* generate_ca_key() {
@ -60,7 +62,6 @@ EVP_PKEY* generate_ca_key() {
return pkey;
}
// Generates a self-signed x509 certificate.
// Largely stolen from here: https://gist.github.com/nathan-osman/5041136
X509* generate_ca_cert(EVP_PKEY * pkey) {
@ -177,7 +178,6 @@ bool create_and_save_key(char keyfile[]) {
return true;
}
bool create_and_save_cert(char certfile[], EVP_PKEY *pkey) {
// Open the PEM file for writing the certificate to disk.
FILE * x509_file = fopen(certfile, "wb");
@ -204,7 +204,6 @@ bool create_and_save_cert(char certfile[], EVP_PKEY *pkey) {
}
EVP_PKEY* read_private_key(char keyfile[]){
FILE *fp;
EVP_PKEY *pkey;
@ -218,10 +217,30 @@ EVP_PKEY* read_private_key(char keyfile[]){
if ( pkey == NULL ){
perror("Error cant read certificate private key file.\n");
}
fclose(fp);
return pkey;
}
X509* read_cert(char certfile[]){
FILE *fp;
X509 *cert;
if (! (fp = fopen(certfile, "r"))){
perror("Error cant open certificate private key file.\n");
return NULL;
}
cert = PEM_read_X509( fp, NULL, NULL, NULL );
if ( cert == NULL ){
perror("Error cant read certificate private key file.\n");
}
fclose(fp);
return cert;
}
CertList *newCertListItem(char *host, EVP_PKEY *key, X509 *cert){
CertList *item = malloc(sizeof( CertList ));
item->host = host;
@ -245,3 +264,9 @@ unsigned int countCertListItems(CertList *item){
}
return count;
}
CertList *findCertListItem( CertList *item, char *host ){
while ( item != NULL && strcmp( host, item->host ) != 0 ) item = item->next;
return item;
}

@ -18,18 +18,97 @@ struct CertList {
CertList *next;
};
SSL_CTX* InitServerCTX(Config *config);
typedef struct {
X509 *cert;
EVP_PKEY *pkey;
CertList *certs;
} CertificateAutority;
SSL_CTX* setup_ctx(CertList *certItem);
/*
* generate_ca_key
* Genreates a EVP_PKEY for the certificate authority
*/
EVP_PKEY* generate_ca_key();
/*
* generate_ca_cert
* Genreates a x509 cert for the certificate authority
* @param EVP_PKEY pkey - the pkey for the ca
*/
X509* generate_ca_cert(EVP_PKEY * pkey);
/*
* generate_site_cert
* Genreates a signed x509 cert for the host authority
* @param EVP_PKEY pkey - the pkey for the ca
* @param X509 caCert - the cert for the ca
* @param char* host - the common name for the certificate
*/
X509* generate_site_cert( EVP_PKEY *pkey, X509 *caCert, char *host );
/*
* create_and_save_key
* Genreates a EVP_PKEY for the CA and saves it
* @param char[] keyfile - the name of the file
*/
bool create_and_save_key(char keyfile[]);
bool create_and_save_cert(char keyfile[], EVP_PKEY *pkey);
/*
* create_and_save_cert
* Genreates a cert for the CA and saves it
* @param char[] certfile - the name of the file
* @param EVP_PKEY pkey - the pkey for the ca
*/
bool create_and_save_cert(char certfile[], EVP_PKEY *pkey);
/*
* read_private_key
* Reades a priveate key from the keyfile
* @param char[] keyfile - the name of the file
*/
EVP_PKEY* read_private_key(char keyfile[]);
/*
* read_cert
* Reades a cert from the certfile
* @param char[] certfile - the name of the file
*/
X509* read_cert(char certfile[]);
/*
* newCertListItem
* Creates an element of the CertList linked list
* @param char* host - the common name for the certificate
* @param EVP_PKEY key - the pkey
* @param X509 cert - the cert
*/
CertList *newCertListItem(char *host, EVP_PKEY *key, X509 *cert);
/*
* getLastCertListItem
* Gets the last element in the linked list
* @param CertList item - An item in the linked list
*/
CertList *getLastCertListItem(CertList *item);
/*
* countCertListItems
* counts the elements in the linked list
* @param CertList item - the first item in the linked list
*/
unsigned int countCertListItems(CertList *item);
CertList *findCertListItem( CertList *first, char *hostname );
/*
* countCertListItems
* Returns the certList item with the specified host
* @param CertList item - the first item in the linked list
* @param char* host - the host we are looking for
* returns null if not there
*/
CertList *findCertListItem( CertList *item, char *host );
#endif /* ifndef SSL_ */

@ -24,3 +24,51 @@ int countLines(char fileName[]){
fclose(fp);
return linesCount;
}
/* Read characters from 'fd' until a newline is encountered. If a newline
character is not encountered in the first (n - 1) bytes, then the excess
characters are discarded. The returned string placed in 'buf' is
null-terminated and includes the newline character if it was read in the
first (n - 1) bytes. The function return value is the number of bytes
placed in buffer (which includes the newline character if encountered,
but excludes the terminating null byte). */
ssize_t fdReadLine(int fd, void *buffer, size_t n) {
ssize_t numRead; /* # of bytes fetched by last read() */
size_t totRead; /* Total bytes read so far */
char *buf;
char ch;
if (n <= 0 || buffer == NULL) {
errno = EINVAL;
return -1;
}
buf = buffer; /* No pointer arithmetic on "void *" */
totRead = 0;
for (;;) {
numRead = read(fd, &ch, 1);
if (numRead == -1) {
if (errno == EINTR) /* Interrupted --> restart read() */
continue;
else
return -1; /* Some other error */
} else if (numRead == 0) { /* EOF */
if (totRead == 0) /* No bytes read; return 0 */
return 0;
else /* Some bytes read; add '\0' */
break;
} else { /* 'numRead' must be 1 if we get here */
if (totRead < n - 1) { /* Discard > (n - 1) bytes */
totRead++;
*buf++ = ch;
}
if (ch == '\n')
break;
}
}
*buf = '\0';
return totRead;
}

@ -3,8 +3,11 @@
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
int strpos(char *haystack, char *needle);
int countLines(char fileName[]);
ssize_t fdReadLine(int fd, void *buffer, size_t n);
#endif

@ -4,7 +4,7 @@
#include "munit/munit.h"
#include "../src/readline.h"
#include "../src/util.h"
#include "../src/request.h"
#include "../src/requestresponse.h"
@ -160,6 +160,18 @@ MunitResult testRequestToString(const MunitParameter params[],
return MUNIT_OK;
}
MunitResult testRequestFromString(const MunitParameter params[],
void* user_data_or_fixture){
char testString[] = "GET / HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n\r\n";
Request *req = newRequestFromString(testString);
munit_assert_string_equal( req->method, "GET" );
munit_assert_string_equal( req->path, "/" );
munit_assert_string_equal( requestToString( req ), testString );
return MUNIT_OK;
}
MunitParameterEnum test_first_line_params[2] = {NULL, NULL};
static MunitTest request_tests[] = {
@ -226,6 +238,13 @@ static MunitTest request_tests[] = {
NULL, /* tear_down */
MUNIT_TEST_OPTION_NONE, /* options */
NULL /* parameters */
},{
"/fromstring", /* name */
testRequestFromString, /* test */
NULL, /* setup */
NULL, /* tear_down */
MUNIT_TEST_OPTION_NONE, /* options */
NULL /* parameters */
},
/* Mark the end of the array with an entry where the test
* function is NULL */

@ -145,6 +145,37 @@ MunitResult testNewHostCertificate(const MunitParameter params[],
return MUNIT_OK;
}
MunitResult testFindCertListItem(const MunitParameter params[],
void* user_data_or_fixture){
CertList *head = newCertListItem( "example.com", NULL, NULL );
CertList *last = head;
CertList *curr;
for ( unsigned int i = 0; i < 10; i++ ){
char host[15] = {'\0'};
sprintf(host, "example%d.com", i+1);
last->next = newCertListItem( strdup(host), NULL, NULL );
last = last->next;
}
munit_assert_int( countCertListItems( head ), ==, 11 );
curr = findCertListItem( head, "example.com" );
munit_assert_not_null( curr );
munit_assert_string_equal( curr->host, "example.com" );
curr = findCertListItem( head, "example1.com" );
munit_assert_not_null( curr );
munit_assert_string_equal( curr->host, "example1.com" );
curr = findCertListItem( head, "example5.com" );
munit_assert_not_null( curr );
munit_assert_string_equal( curr->host, "example5.com" );
curr = findCertListItem( head, "doesnt-exist.com" );
munit_assert_null( curr );
return MUNIT_OK;
}
static char* count_parameters[] = {
"0", "1", "2", "10", "50", NULL
@ -180,6 +211,7 @@ static MunitTest ssl_tests[] = {
{ "/ca/cert/save", testNewCertificateSave, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ "/CertList/count", testCerlistCount, NULL, NULL, MUNIT_TEST_OPTION_NONE, count_params },
{ "/CertList/last", testCerlistLast, NULL, NULL, MUNIT_TEST_OPTION_NONE, count_params },
{ "/CertList/find", testFindCertListItem, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ "/hostcert/new", testNewHostCertificate, NULL, NULL, MUNIT_TEST_OPTION_NONE, x509_issuer_subject_params },
/* Mark the end of the array with an entry where the test
* function is NULL */

Loading…
Cancel
Save