Starts work on https as well as some moving about

I now start the listener in the main.c file rather than proxy given that
I didn't feel proxy was the right place if a normal (non-proxied)
request came in. webserver.{c,h} and proxy.{c,h} had some changes
relating to this.

The config changed slightly - we now create a folder in ~/.config/
called yaip. This is where certificates and so on will be stored along
with the user configuration

I created a helper function to get files inside this directory (it
changes based on xdg_config_home) and updated relevant tests.

In ssl.{c,h} I have started work. If they don't exist, the tool now
creates and stores a key and certificate for the CA that this tool will
need to pretend to be. I still need to write tests for this.
master
Jonathan Hodgson 2 years ago
parent 6eaad263be
commit bb62ed3b1f
  1. 1
      .gitignore
  2. 2
      Makefile
  3. 6
      docs/certificate-authority.md
  4. 37
      src/config.c
  5. 5
      src/config.h
  6. 114
      src/main.c
  7. 79
      src/proxy.c
  8. 4
      src/proxy.h
  9. 180
      src/ssl.c
  10. 19
      src/ssl.h
  11. 15
      src/webserver.c
  12. 1
      src/webserver.h
  13. 12
      tests/config.test.c
  14. 1
      tests/request.test.c

1
.gitignore vendored

@ -3,3 +3,4 @@
yaip
mynotes.md
*.sqlite
/certs

@ -6,7 +6,7 @@ TESTFILES = $(wildcard tests/*.c)
TESTOUT = $(TESTFILES:.c=)
OUT = yaip
CFLAGS = -Wall -g
LDLIBS = -lsqlite3 -lm
LDLIBS = -lsqlite3 -lm -lssl -lcrypto
CC = gcc

@ -0,0 +1,6 @@
# Certificate Authority
At some point, it would be nice if yaip does this automatically, but for now,
you need to create a certificate authority for yaip to sign requests with.
;

@ -34,35 +34,50 @@ char* resolveTilde(const char *path) {
*/
Config* configDefaults(){
Config *conf = malloc( sizeof( Config ) );
conf->database = "proxy.sqlite";
conf->database = strdup("proxy.sqlite");
conf->port = 8080;
conf->localConfig = "proxy.conf";
conf->userConfig = getDefaultUserConfigLoc();
conf->localConfig = strdup("proxy.conf");
conf->userConfig = getUserConfigFile("proxy.conf");
conf->certfile = getUserConfigFile("cert.pem");
conf->keyfile = getUserConfigFile("key.pem");
return conf;
}
void maybeMakeDir(const char *path){
if ( path_exists( path ) ) return;
mkdir( path, 0700 );
}
char* getConfigDir(){
char *xdg_config_home;
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
xdg_config_home = resolveTilde("~/.config");
char configDir[strlen(xdg_config_home) + 6];
memset(configDir, '\0', strlen(xdg_config_home) + 6);
return xdg_config_home;
sprintf( configDir, "%s/yaip", xdg_config_home );
return strdup(configDir);
}
char* getDefaultUserConfigLoc(){
char* getUserConfigFile(char *filename){
//This doesn't end in a slash
char *configDir = getConfigDir();
char configFile[strlen(configDir) + 11];
memset(configFile, '\0', strlen(configDir) + 11);
//Make sure our filename doesn't start with a slash
while ( filename[0] == '/' ) filename++;
// Allocate space for both parts, a slash and a \0
char retFile[strlen(configDir) + strlen(filename) + 2];
strcpy( configFile, configDir );
memset(retFile, '\0', strlen(configDir) + strlen(filename) + 2);
strcat( configFile, "/proxy.conf" );
sprintf(retFile, "%s/%s", configDir, filename);
return strdup(configFile);
return strdup(retFile);
}
void setConfig(Config *config, char option[], char value[]){
if ( strcmp( option, "database" ) == 0 ){
config->database = value;

@ -13,13 +13,16 @@ typedef struct {
char *localConfig; // Project Specific
char *userConfig; // User Specific
unsigned int port;
char *certfile;
char *keyfile;
} Config;
bool path_exists(const char *path);
char* resolveTilde(const char *path);
Config* configDefaults();
char* getConfigDir();
char* getDefaultUserConfigLoc();
void maybeMakeDir(const char *path);
char* getUserConfigFile();
void setConfig(Config *config, char option[], char value[]);
void printConfig(Config *config);

@ -7,6 +7,7 @@
#include "config.h"
#include "request.h"
#include "response.h"
#include "ssl.h"
#define PACKAGE_NAME "WICTP"
#define DEFAULT_DATABASE "data.sqlite"
@ -30,6 +31,40 @@ void printHelp(){
printf( "\t --print-config Prints current config\n" );
}
int startListener(unsigned int port){
//we need to act as an http server
int server_fd;
struct sockaddr_in address;
memset( &address, 0, sizeof(address) );
int addrlen = sizeof(address);
Response *response;
char *responseStr;
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
return -1;
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons( port );
// Forcefully attaching socket to the port 8080
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) != 0) {
perror("bind failed");
return -1;
}
if (listen(server_fd, 3) != 0) {
perror("listen");
return -1;
}
return server_fd;
}
int main(int argc, char**argv){
Config *config = configDefaults();
int listener;
@ -67,7 +102,24 @@ int main(int argc, char**argv){
db_create(config->database);
}
listener = proxy_startListener(config->port);
maybeMakeDir( getConfigDir() );
if (!path_exists( config->keyfile ) ){
printf("Creating keyfile\n");
if ( !create_and_save_key(config->keyfile) ){
return 1;
}
}
if (!path_exists( config->certfile ) ){
printf("Creating cert\n");
if ( !create_and_save_cert(config->certfile, read_private_key( config->keyfile) ) ){
return 1;
}
}
listener = startListener(config->port);
if ( listener < 0 ){
return 1;
@ -78,8 +130,6 @@ int main(int argc, char**argv){
socklen_t addrlen = sizeof(addr);
int client = 0;
Request *request = NULL;
Response *response = NULL;
char *responseStr;
printf("Listening on port %i\n", config->port);
if ((client = accept(listener, (struct sockaddr *)&addr,
@ -93,62 +143,18 @@ int main(int argc, char**argv){
//thread
request = newRequestFromSocket(client);
// If the host is not defined, then it is not a proxy request
// Note that the host here is where the request should be sent, not
// necesarily the hosts header
if ( strcmp( request->host, "" ) == 0 ){
response = webserverGetResponse(request);
// If the host is not defined, then it is not a proxy request
// Note that the host here is where the request should be sent, not
// necesarily the hosts header
webserverRequest(request, client);
} else {
response = upstreamGetResponse(request);
proxyRequest(request, client);
}
responseStr = responseToString( response );
// I'm also not convinced that strlen is the best function to use here
// When we get to dealing with binary requests / responses, they may
// well have null characters in them
send(client , responseStr, strlen(responseStr) , 0 );
printf( "\n1\n" );
close(client);
printf( "\n2\n" );
freeRequest( request );
printf( "\n3\n" );
freeResponse( response );
printf( "\n4\n" );
free(responseStr);
printf( "\n5\n" );
//If this is an https request - this is the first part
//if ( strcmp( request->method, "CONNECT" ) == 0 ){
// // 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 = newResponse();
// connectionEstablished(response);
// responseStr = responseToString(response);
// send(new_socket , responseStr, strlen(responseStr) , 0 );
// char line[1024] = {'\0'};
// //a length of 2 will indicate an empty line which will split the headers
// //from the body (if there is a body)
// int valread = read( new_socket, line, 1024);
// while (valread > 0){
// printf("%s", line);
// //I believe at this point all the headers are done.
// valread = read( new_socket , line, 1024);
// }
//}
}

@ -38,37 +38,50 @@ Response *upstreamGetResponse(Request *request){
}
int proxy_startListener(unsigned int port){
//we need to act as an http server
int server_fd;
struct sockaddr_in address;
memset( &address, 0, sizeof(address) );
int addrlen = sizeof(address);
Response *response;
char *responseStr;
// Creating socket file descriptor
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
return -1;
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons( port );
// Forcefully attaching socket to the port 8080
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) != 0) {
perror("bind failed");
return -1;
}
if (listen(server_fd, 3) != 0) {
perror("listen");
return -1;
}
return server_fd;
void proxyRequest(Request *request, int client){
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);
//}
} else {
Response *response = upstreamGetResponse(request);
char *responseStr = responseToString( response );
send(client , responseStr, strlen(responseStr) , 0 );
free( responseStr );
freeResponse( response );
}
}

@ -12,9 +12,11 @@
#include <stdbool.h>
#include "request.h"
#include "response.h"
#include "webserver.h"
int proxy_startListener( unsigned int port );
Response *upstreamGetResponse(Request *request);
void proxyRequest(Request *request, int client);
#endif /* ifndef PROXY_H */

@ -0,0 +1,180 @@
#include "ssl.h"
SSL_CTX* InitServerCTX(Config *config) {
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);
abort();
}
//set the local certificate from CertFile
if ( SSL_CTX_use_certificate_file(ctx, config->certfile, SSL_FILETYPE_PEM) <= 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 ){
ERR_print_errors_fp(stderr);
abort();
}
//verify private key
if ( !SSL_CTX_check_private_key(ctx) )
{
fprintf(stderr, "Private key does not match the public certificate\n");
abort();
}
return ctx;
}
// Generates a 2048-bit RSA key.
// Largely stolen from here: https://gist.github.com/nathan-osman/5041136
EVP_PKEY* generate_ca_key() {
// Allocate memory for the EVP_PKEY structure.
EVP_PKEY *pkey = EVP_PKEY_new();
if(!pkey) {
perror("Unable to create EVP_PKEY structure.\n");
return NULL;
}
// Generate the RSA key and assign it to pkey.
RSA *rsa = RSA_new();
BIGNUM *bn = BN_new();
BN_set_word(bn, RSA_F4);
RSA_generate_key_ex(rsa, 2048, bn, NULL );
if(!EVP_PKEY_assign_RSA(pkey, rsa)) {
perror("Unable to generate 2048-bit RSA key.\n");
EVP_PKEY_free(pkey);
return NULL;
}
// The key has been generated, return it.
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) {
// Allocate memory for the X509 structure.
X509 *x509 = X509_new();
if(!x509) {
perror( "Unable to create X509 structure.\n");
return NULL;
}
// Set the serial number.
ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
// This certificate is valid from now until exactly one year from now.
X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);
// Set the public key for our certificate.
X509_set_pubkey(x509, pkey);
// We want to copy the subject name to the issuer name.
X509_NAME *name = X509_get_subject_name(x509);
// Set the country code and common name.
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"UK", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"Yet Another Intercepting Proxy", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"jn.hn", -1, -1, 0);
// Now set the issuer name.
X509_set_issuer_name(x509, name);
// TODO: mitmproxy adds some extensions. I don' tknow if I need those
// https://github.com/mitmproxy/mitmproxy/blob/3cb89069b9f70c198cc89337bd5a9b1bbf3a69d0/mitmproxy/certs.py#L190-L204
// Actually sign the certificate with our key.
if(!X509_sign(x509, pkey, EVP_sha1()))
{
perror( "Error signing certificate.\n");
X509_free(x509);
return NULL;
}
return x509;
}
bool create_and_save_key(char keyfile[]) {
// Generate the key
EVP_PKEY *pkey = generate_ca_key();
if ( pkey == NULL ){
fprintf(stderr, "Unable to open generate key\n");
return false;
}
// Open the PEM file for writing the key to disk.
FILE * pkey_file = fopen(keyfile, "wb");
if(!pkey_file) {
fprintf(stderr, "Unable to open \"%s\" for writing.\n", keyfile);
return false;
}
// Write the key to disk.
bool ret = PEM_write_PrivateKey(pkey_file, pkey, NULL, NULL, 0, NULL, NULL);
fclose(pkey_file);
if(!ret) {
perror("Unable to write private key to disk.\n");
return false;
}
return true;
}
EVP_PKEY* read_private_key(char keyfile[]){
FILE *fp;
EVP_PKEY *pkey;
if (! (fp = fopen(keyfile, "r"))){
perror("Error cant open certificate private key file.\n");
return NULL;
}
pkey = PEM_read_PrivateKey( fp, NULL, NULL, NULL );
if ( pkey == NULL ){
perror("Error cant read certificate private key file.\n");
}
return pkey;
}
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");
if(!x509_file) {
fprintf(stderr, "Unable to open \"%s\" for writing.\n", certfile);
return false;
}
X509 *x509 = generate_ca_cert(pkey);
if ( x509 == NULL ){
fprintf(stderr, "Unable to open generate cert\n");
return false;
}
// Write the certificate to disk.
int ret = PEM_write_X509(x509_file, x509);
fclose(x509_file);
if(!ret) {
perror("Unable to write certificate to disk.\n");
return false;
}
return true;
}

@ -0,0 +1,19 @@
#ifndef SSL_H
#define SSL_H
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include "config.h"
SSL_CTX* InitServerCTX(Config *config);
EVP_PKEY* generate_ca_key();
X509* generate_ca_cert(EVP_PKEY * pkey);
bool create_and_save_key(char keyfile[]);
bool create_and_save_cert(char keyfile[], EVP_PKEY *pkey);
EVP_PKEY* read_private_key(char keyfile[]);
#endif /* ifndef SSL_ */

@ -6,3 +6,18 @@ Response* webserverGetResponse( Request *req ){
responseSetBody(rsp, "Test", 1);
return rsp;
}
void webserverRequest( Request *req, int client ){
Response *rsp = webserverGetResponse( req );
char *responseStr = responseToString( rsp );
// I'm also not convinced that strlen is the best function to use here
// When we get to dealing with binary requests / responses, they may
// well have null characters in them
send(client , responseStr, strlen(responseStr) , 0 );
free( responseStr );
freeResponse( rsp );
}

@ -8,6 +8,7 @@
#include "response.h"
Response* webserverGetResponse( Request *req );
void webserverRequest( Request *req, int client);
#endif /* ifndef WEBSERVER_H

@ -37,7 +37,7 @@ MunitResult checkConfigDirWithXdg(const MunitParameter params[],
void* user_data_or_fixture){
char directory[] = "/testing/xdg/directory";
setenv( "XDG_CONFIG_HOME",directory, 1 );
munit_assert_string_equal( directory, getConfigDir() );
munit_assert_string_equal( "/testing/xdg/directory/yaip", getConfigDir() );
return MUNIT_OK;
}
@ -46,7 +46,7 @@ MunitResult checkConfigDirWithoutXdg(const MunitParameter params[],
unsetenv( "XDG_CONFIG_HOME" );
char dir[500] = {'\0'};
strcpy( dir, getenv("HOME") );
strcat( dir, "/.config" );
strcat( dir, "/.config/yaip" );
munit_assert_string_equal( dir, getConfigDir() );
return MUNIT_OK;
}
@ -55,14 +55,18 @@ MunitResult checkDefaults(const MunitParameter params[],
void* user_data_or_fixture){
char directory[] = "/testing/xdg/directory";
char file[] = "/testing/xdg/directory/proxy.conf";
char conffile[] = "/testing/xdg/directory/yaip/proxy.conf";
char certfile[] = "/testing/xdg/directory/yaip/cert.pem";
char keyfile[] = "/testing/xdg/directory/yaip/key.pem";
setenv( "XDG_CONFIG_HOME",directory, 1 );
Config *conf = configDefaults();
munit_assert_string_equal(conf->database, "proxy.sqlite");
munit_assert_int(conf->port, ==, 8080);
munit_assert_string_equal(conf->localConfig, "proxy.conf");
munit_assert_string_equal(conf->userConfig, file);
munit_assert_string_equal(conf->userConfig, conffile);
munit_assert_string_equal(conf->certfile, certfile);
munit_assert_string_equal(conf->keyfile, keyfile);
return MUNIT_OK;
}

@ -114,6 +114,7 @@ MunitResult testFirstLineVersions(const MunitParameter params[],
void* user_data_or_fixture){
Request *req;
printf("\nI get here\n");
requestTestFirstLine *line = getLineObj(params);
if ( line->fullLine == NULL ) return MUNIT_ERROR;
req = newRequest();

Loading…
Cancel
Save