From 1beca38af610733a7e3c5869608eaa5fae0e3814 Mon Sep 17 00:00:00 2001 From: Jonathan Hodgson Date: Sun, 23 Jan 2022 15:38:24 +0000 Subject: [PATCH] Early SSL Certificates creation work done Yaip is now able to generate simple SSL certificates for a given host. I don't currently add any extensions to the certificates (such as alternative names). When I get to testing it with a browser I'll see what browsers require. However, I think I'll probably generate certificates for each host, including sub domains, to make life easier searching. I've also added a util function to count lines in a file that was used for testing some of the ssl functions. --- src/main.c | 3 +- src/proxy.c | 2 +- src/proxy.h | 3 +- src/ssl.c | 121 +++++++++++++++++++++------ src/ssl.h | 16 ++++ src/util.c | 19 +++++ src/util.h | 1 + tests/ssl.test.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++ tests/util.test.c | 34 ++++++++ 9 files changed, 375 insertions(+), 30 deletions(-) create mode 100644 tests/ssl.test.c diff --git a/src/main.c b/src/main.c index 16c68c7..43d12f2 100644 --- a/src/main.c +++ b/src/main.c @@ -68,6 +68,7 @@ int startListener(unsigned int port){ int main(int argc, char**argv){ Config *config = configDefaults(); int listener; + CertList *certs = NULL; for ( unsigned int i = 1; i < argc; i++ ){ @@ -150,7 +151,7 @@ int main(int argc, char**argv){ // necesarily the hosts header webserverRequest(request, client); } else { - proxyRequest(request, client); + proxyRequest(request, client, certs); } close(client); diff --git a/src/proxy.c b/src/proxy.c index 0bb5ff7..1c67bcc 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -38,7 +38,7 @@ Response *upstreamGetResponse(Request *request){ } -void proxyRequest(Request *request, int client){ +void proxyRequest(Request *request, int client, CertList *certs){ if ( strcmp( request->method, "CONNECT" ) == 0 ){ // If it is a connect request, we are dealing with https diff --git a/src/proxy.h b/src/proxy.h index 2eb5e88..adbf353 100644 --- a/src/proxy.h +++ b/src/proxy.h @@ -14,9 +14,10 @@ #include "request.h" #include "response.h" #include "webserver.h" +#include "ssl.h" Response *upstreamGetResponse(Request *request); -void proxyRequest(Request *request, int client); +void proxyRequest(Request *request, int client, CertList *certs); #endif /* ifndef PROXY_H */ diff --git a/src/ssl.c b/src/ssl.c index b0f71ad..da82bff 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -1,6 +1,7 @@ #include "ssl.h" + SSL_CTX* InitServerCTX(Config *config) { const SSL_METHOD *method; SSL_CTX *ctx; @@ -25,11 +26,10 @@ SSL_CTX* InitServerCTX(Config *config) { abort(); } //verify private key - if ( !SSL_CTX_check_private_key(ctx) ) - { - fprintf(stderr, "Private key does not match the public certificate\n"); - abort(); - } + if ( !SSL_CTX_check_private_key(ctx) ) { + fprintf(stderr, "Private key does not match the public certificate\n"); + abort(); + } return ctx; } @@ -71,7 +71,7 @@ X509* generate_ca_cert(EVP_PKEY * pkey) { 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. @@ -87,7 +87,10 @@ X509* generate_ca_cert(EVP_PKEY * pkey) { // 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); + //I want this to be a fake domain because CAs can't sign certificates with + //the same domain. I thought about using jn.hn but I want to test using + //that. + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"yaip.yaip", -1, -1, 0); // Now set the issuer name. X509_set_issuer_name(x509, name); @@ -98,8 +101,7 @@ X509* generate_ca_cert(EVP_PKEY * pkey) { // Actually sign the certificate with our key. - if(!X509_sign(x509, pkey, EVP_sha1())) - { + if(!X509_sign(x509, pkey, EVP_sha1())) { perror( "Error signing certificate.\n"); X509_free(x509); return NULL; @@ -110,6 +112,46 @@ X509* generate_ca_cert(EVP_PKEY * pkey) { return x509; } +X509* generate_site_cert( EVP_PKEY *pkey, X509 *caCert, char *host ){ + X509 *hostCert = X509_new(); + if(!hostCert) { + perror( "Unable to create X509 structure.\n"); + return NULL; + } + + + ASN1_INTEGER_set(X509_get_serialNumber(hostCert), 1); + + // This certificate is valid from now until exactly one year from now. + X509_gmtime_adj(X509_get_notBefore(hostCert), 0); + X509_gmtime_adj(X509_get_notAfter(hostCert), 31536000L); + + // Set the public key for our certificate. + X509_set_pubkey(hostCert, pkey); + + // We want to copy the subject name of the ca to the issuer of the new + // certificate + X509_NAME *ca_sn = X509_get_subject_name(caCert); + X509_NAME *host_in = X509_NAME_dup( ca_sn ); + X509_set_issuer_name(hostCert, host_in); + + + X509_NAME *host_sn = X509_get_subject_name(hostCert); + + // Set the country code and common name. + X509_NAME_add_entry_by_txt(host_sn, "C", MBSTRING_ASC, (unsigned char *)"UK", -1, -1, 0); + X509_NAME_add_entry_by_txt(host_sn, "O", MBSTRING_ASC, (unsigned char *)"Yet Another Intercepting Proxy", -1, -1, 0); + X509_NAME_add_entry_by_txt(host_sn, "CN", MBSTRING_ASC, (unsigned char *)strdup(host), -1, -1, 0); + + if(!X509_sign(hostCert, pkey, EVP_sha1())) { + perror( "Error signing certificate.\n"); + X509_free(hostCert); + return NULL; + } + + return hostCert; +} + bool create_and_save_key(char keyfile[]) { // Generate the key EVP_PKEY *pkey = generate_ca_key(); @@ -135,24 +177,6 @@ bool create_and_save_key(char keyfile[]) { 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. @@ -178,3 +202,46 @@ bool create_and_save_cert(char certfile[], EVP_PKEY *pkey) { } 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; +} + +CertList *newCertListItem(char *host, EVP_PKEY *key, X509 *cert){ + CertList *item = malloc(sizeof( CertList )); + item->host = host; + item->key = key; + item->cert = cert; + item->next = NULL; + return item; +} + +CertList *getLastCertListItem(CertList *item){ + if ( item == NULL ) return NULL; + while ( item->next != NULL ) item=item->next; + return item; +} + +unsigned int countCertListItems(CertList *item){ + unsigned int count = 0; + while ( item != NULL ){ + count++; + item = item->next; + } + return count; +} diff --git a/src/ssl.h b/src/ssl.h index aca8e0f..8f5d02c 100644 --- a/src/ssl.h +++ b/src/ssl.h @@ -1,19 +1,35 @@ #ifndef SSL_H #define SSL_H +#include #include #include #include #include +#include #include "config.h" +typedef struct CertList CertList; +struct CertList { + char *host; + EVP_PKEY *key; + X509 *cert; + CertList *next; +}; + SSL_CTX* InitServerCTX(Config *config); EVP_PKEY* generate_ca_key(); X509* generate_ca_cert(EVP_PKEY * pkey); +X509* generate_site_cert( EVP_PKEY *pkey, X509 *caCert, char *host ); bool create_and_save_key(char keyfile[]); bool create_and_save_cert(char keyfile[], EVP_PKEY *pkey); EVP_PKEY* read_private_key(char keyfile[]); +CertList *newCertListItem(char *host, EVP_PKEY *key, X509 *cert); +CertList *getLastCertListItem(CertList *item); +unsigned int countCertListItems(CertList *item); +CertList *findCertListItem( CertList *first, char *hostname ); + #endif /* ifndef SSL_ */ diff --git a/src/util.c b/src/util.c index e637c0d..dd15354 100644 --- a/src/util.c +++ b/src/util.c @@ -5,3 +5,22 @@ int strpos(char *haystack, char *needle) { if ( p != NULL ) return p - haystack; return -1; } + +int countLines(char fileName[]){ + FILE *fp; + char ch; + int linesCount = 0; + fp=fopen(fileName,"r"); + if(fp==NULL) { + printf("File \"%s\" does not exist!!!\n",fileName); + return -1; + } + //read character by character and check for new line + while((ch=fgetc(fp))!=EOF) { + if(ch=='\n') + linesCount++; + } + //close the file + fclose(fp); + return linesCount; +} diff --git a/src/util.h b/src/util.h index 923df29..44dc0e0 100644 --- a/src/util.h +++ b/src/util.h @@ -5,5 +5,6 @@ #include int strpos(char *haystack, char *needle); +int countLines(char fileName[]); #endif diff --git a/tests/ssl.test.c b/tests/ssl.test.c new file mode 100644 index 0000000..7073294 --- /dev/null +++ b/tests/ssl.test.c @@ -0,0 +1,206 @@ +#ifndef SSL_TEST +#define SSL_TEST + +#include "munit/munit.h" +#include "../src/ssl.h" +#include "../src/util.h" + + +MunitResult testNewRsaKeySave(const MunitParameter params[], + void* user_data_or_fixture){ + char keyFile[] = "/tmp/yaip-test-keyfile"; + create_and_save_key( keyFile ); + //TODO: make better tests. + // For now checking there is at least 3 lines seems ok + munit_assert_int( 3, <, countLines(keyFile) ); + remove(keyFile); + + return MUNIT_OK; +} + +MunitResult testNewCACertificate(const MunitParameter params[], + void* user_data_or_fixture){ + EVP_PKEY *key = generate_ca_key(); + const char *nameEntry =munit_parameters_get(params, "Name Entry" ); + + X509 *x509 = generate_ca_cert(key); + munit_assert_not_null( x509 ); + + X509_NAME *name = X509_get_subject_name(x509); + munit_assert_not_null( name ); + + char buff[200] = {'\0'}; + + if ( strcmp( nameEntry, "C" ) == 0 ){ + X509_NAME_get_text_by_NID( name, NID_countryName, buff, 200 ); + munit_assert_string_equal( buff, "UK"); + } else if ( strcmp( nameEntry, "O" ) == 0 ){ + X509_NAME_get_text_by_NID( name, NID_organizationName, buff, 200 ); + munit_assert_string_equal( buff, "Yet Another Intercepting Proxy"); + } else if ( strcmp( nameEntry, "CN" ) == 0 ){ + X509_NAME_get_text_by_NID( name, NID_commonName, buff, 200 ); + munit_assert_string_equal( buff, "yaip.yaip"); + } + + return MUNIT_OK; +} + +MunitResult testNewCertificateSave(const MunitParameter params[], + void* user_data_or_fixture){ + char certFile[] = "/tmp/yaip-test-certfile"; + EVP_PKEY *key = generate_ca_key(); + create_and_save_cert( certFile, key ); + //TODO: make better tests. + // For now checking there is at least 3 lines seems ok + munit_assert_int( 3, <, countLines(certFile) ); + remove( certFile ); + + return MUNIT_OK; +} + +MunitResult testCerlistCount(const MunitParameter params[], + void* user_data_or_fixture){ + CertList *head = NULL; + CertList *last = head; + + unsigned int count = atoi(munit_parameters_get(params, "Count" )); + for ( unsigned int i = 0; i < count; i++ ){ + CertList *newItem = malloc(sizeof( CertList )); + newItem->next = NULL; + if ( head == NULL ){ + head = newItem; + last = newItem; + } else { + last->next = newItem; + last = last->next; + } + } + munit_assert_int( countCertListItems(head), ==, count ); + + return MUNIT_OK; +} + +MunitResult testCerlistLast(const MunitParameter params[], + void* user_data_or_fixture){ + + const char *countstr = munit_parameters_get(params, "Count" ); + //const char *countstr = "2"; + const unsigned int count = atoi(countstr); + + CertList *head = NULL; + CertList *last = head; + + for ( unsigned int i = 0; i < count; i++ ){ + CertList *newItem = malloc(sizeof( CertList )); + char str[5] = {'\0'}; + sprintf(str, "%d", i+1); + newItem->next = NULL; + newItem->host = strdup(str); + if ( head == NULL ){ + head = newItem; + last = newItem; + } else { + last->next = newItem; + last = last->next; + } + } + + CertList *lastCert = getLastCertListItem(head); + if ( count == 0 ){ + munit_assert_null( lastCert ); + } else { + munit_assert_int( countCertListItems(head), ==, count ); + munit_assert_string_equal( countstr, lastCert->host ); + } + return MUNIT_OK; +} + +MunitResult testNewHostCertificate(const MunitParameter params[], + void* user_data_or_fixture){ + + EVP_PKEY *key = generate_ca_key(); + X509 *x509 = generate_ca_cert(key); + X509 *siteCert = generate_site_cert( key, x509, "example.com" ); + const char *nameType = munit_parameters_get(params, "Name" ); + X509_NAME *name; + + char buff[200] = {'\0'}; + + if ( strcmp( nameType, "subject" ) ){ + name = X509_get_subject_name(siteCert); + X509_NAME_get_text_by_NID( name, NID_commonName, buff, 200 ); + munit_assert_string_equal( buff, "example.com" ); + } else if ( strcmp( nameType, "issuer" ) ){ + name = X509_get_issuer_name(siteCert); + X509_NAME_get_text_by_NID( name, NID_commonName, buff, 200 ); + munit_assert_string_equal( buff, "yaip.yaip" ); + } else { + return MUNIT_ERROR; + } + //FILE * pkey_file = fopen("examplecert.pem", "wb"); + //// Write the cert to disk. + //PEM_write_X509( pkey_file, siteCert ); + //fclose(pkey_file); + + return MUNIT_OK; +} + + +static char* count_parameters[] = { + "0", "1", "2", "10", "50", NULL +}; + +static char* x509_name_parameters[] = { + "C", "O", "CN", NULL +}; + +static char* x509_issuer_subject_parameters[] = { + "issuer", "subject", NULL +}; + +static MunitParameterEnum count_params[] = { + { "Count", count_parameters }, + { NULL, NULL }, +}; + +static MunitParameterEnum x509_name_params[] = { + { "Name Entry", x509_name_parameters }, + { NULL, NULL }, +}; + +static MunitParameterEnum x509_issuer_subject_params[] = { + { "Name", x509_issuer_subject_parameters }, + { NULL, NULL }, +}; + +static MunitTest ssl_tests[] = { + // name test setup tear_down options parameters + { "/ca/rsa_key/save", testNewRsaKeySave, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }, + { "/ca/cert/new", testNewCACertificate, NULL, NULL, MUNIT_TEST_OPTION_NONE, x509_name_params }, + { "/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 }, + { "/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 */ + { NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL } +}; + +MunitSuite ssl_test_suite = { + "/ssl", /* name */ + ssl_tests, /* tests */ + NULL, /* suites */ + 1, /* iterations */ + MUNIT_SUITE_OPTION_NONE /* options */ +}; + +#ifndef MAINTEST +#define MAINTEST + +int main (int argc, char* argv[]) { + return munit_suite_main(&ssl_test_suite, NULL, argc, argv); +} + +#endif /* ifndef MAINTEST */ + +#endif diff --git a/tests/util.test.c b/tests/util.test.c index 9f70760..dad4dd9 100644 --- a/tests/util.test.c +++ b/tests/util.test.c @@ -17,6 +17,33 @@ MunitResult testStrPos(const MunitParameter params[], return MUNIT_OK; } +MunitResult testCountLines(const MunitParameter params[], + void* user_data_or_fixture){ + char filename[] = "/tmp/yaip-test-file"; + FILE *fp = fopen(filename, "w"); + if ( fp == NULL ){ + printf("Cannot open file %s\n", filename); + return MUNIT_ERROR; + } + fprintf( fp, "test\n" ); + fclose(fp); + + munit_assert_int( 1, ==, countLines(filename) ); + + fp = fopen(filename, "w"); + if ( fp == NULL ){ + printf("Cannot open file %s\n", filename); + return MUNIT_ERROR; + } + fprintf( fp, "test2\ntest3\n" ); + fclose(fp); + + munit_assert_int( 2, ==, countLines(filename) ); + + remove(filename); + + return MUNIT_OK; +} static MunitTest util_tests[] = { { @@ -26,6 +53,13 @@ static MunitTest util_tests[] = { NULL, /* tear_down */ MUNIT_TEST_OPTION_NONE, /* options */ NULL /* parameters */ + }, { + "/util/countLines", /* name */ + testCountLines, /* 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 */