Select Git revision
http-server.c
certvalidator.c 14.79 KiB
/**
Example certifcate code
gcc -o certexample certexample.c -lssl -lcrypto
*/
#include <openssl/x509.h>
#include <openssl/asn1.h>
#include <openssl/crypto.h>
#include <openssl/asn1t.h>
#include <openssl/x509v3.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <openssl/asn1_mac.h>
#define DEBUG 0
#define LINE_BUFFER 100
const ASN1_TIME *X509_get0_notBefore(const X509 *x);
char** str_split(const char* a_str, const char a_delim);
char* concat(char *s1, char *s2);
char* get_basic_constraints(X509 *cert);
char* get_key_usage(X509 *cert);
char* compare_not_before(X509 *cert);
char* compare_not_after(X509 *cert);
char* get_SAN(X509 *cert);
char *get_domain_name(X509 *cert);
int matches_subject_alternative_name(const char *hostname, X509 *server_cert);
int get_public_key_length(X509 *cert);
int validate_basic_constraints(char* basic_constraints);
int validate_key_usage(char* key_usage);
int validate_key_length(int length);
int validate_CN(const char* hostname, char*cn);
int validate_wildcard_string(const char *hostname, char*hostname_with_wildcard);
int validate_CN_and_SAN(const char *url, X509 *cert);
int validate_not_before(X509 *cert);
int validate_not_after(X509 *cert);
int validate_certificate(const char *url, X509 *cert);
int find_first_instanceof(const char *str, char delim);
char *str_slice_to_end(const char *str, int begin);
int main(int argc, char **argv){
char line[LINE_BUFFER];
//open the file, create the file to write to
FILE *csv_input = fopen(argv[1], "r");
FILE *csv_output = fopen("output_test.csv" ,"w");
//for each line in the csv file, process each certificate
OpenSSL_add_all_algorithms();
ERR_load_BIO_strings();
ERR_load_crypto_strings();
int n = 0;
while (fgets(line, LINE_BUFFER, csv_input) != NULL){
if(DEBUG){
printf("CSV LINE # %d\n", n);
}
//init all the things we use to describe a certificate
BIO *certificate_bio = NULL;
X509 *cert = NULL;
X509_NAME *cert_issuer = NULL;
X509_CINF *cert_inf = NULL;
STACK_OF(X509_EXTENSION) * ext_list;
certificate_bio = BIO_new(BIO_s_file());
//here we are able to access each line
//get rid of newline
line[strlen(line)-1] = '\0';
//split the csv line up into its elements
char **csv_row_elements = str_split(line, ',');
if(DEBUG){
printf("\tFILE: %s\n",csv_row_elements[0]);
printf("\tURL: %s\n",csv_row_elements[1]);
}
char *certificate_file = csv_row_elements[0];
const char *url = csv_row_elements[1];
//for some reason splitting keeps mututating the original string
char *unchanged_url = csv_row_elements[1];
//open up the certificate file specifed by the line in the input csv
FILE *fp = fopen(certificate_file, "r");
if (!(BIO_read_filename(certificate_bio, certificate_file))){
fprintf(stderr, "Error in reading cert BIO filename");
exit(EXIT_FAILURE);
}
//load certiifcate
if (!(cert = PEM_read_bio_X509(certificate_bio, NULL, 0, NULL))){
fprintf(stderr, "Error in loading certificate");
exit(EXIT_FAILURE);
}
if(DEBUG){
printf ("\tBASIC CONSTRAINT: %s\n",get_basic_constraints(cert));
printf ("\tBASIC CONSTRAINT VALIDATION: %d\n",validate_basic_constraints(get_basic_constraints(cert)));
printf ("\tKEY USAGE: %s\n",get_key_usage(cert));
printf ("\tKEY USAGE VALIDATION: %d\n",validate_key_usage(get_key_usage(cert)));
printf ("\tKEY LENGTH BITS: %d\n",get_public_key_length(cert));
printf ("\tKEY LENGTH VALIDATION: %d\n",validate_key_length(get_public_key_length(cert)));
printf ("\tNot Before compared to Current: %s\n",compare_not_before(cert));
printf ("\tNot After compared to Current: %s\n",compare_not_after(cert));
printf("\tNOT BEFORE VALIDATION %d\n", validate_not_before(cert));
printf("\tNOT AFTER VALIDATION %d\n", validate_not_after(cert));
printf ("\tCommon Name: %s\n",get_domain_name(cert));
printf("\tCOMMON NAME AND SAN VALIDATION %d\n", validate_CN_and_SAN(url, cert));
printf ("%d", 0 || 1);
printf("FINAL VALIDATION %d\n", validate_certificate(url, cert));
printf("\t%s\n", unchanged_url);
}
fprintf(csv_output,"%s,", csv_row_elements[0]);
fprintf(csv_output,"%s,", unchanged_url);
//print validation result
fprintf(csv_output,"%d\n", validate_certificate(url, cert));
//this is for debugging and printing out line numbers
n++;
}
exit(0);
}
char* get_basic_constraints(X509 *cert){
/* -returns a string that represents the certificate's basic constraint, either CA is false or true
-taken from the sample code and modified
*/
X509_EXTENSION *ex = X509_get_ext(cert, X509_get_ext_by_NID(cert, NID_basic_constraints, -1));
ASN1_OBJECT *obj = X509_EXTENSION_get_object(ex);
char buff[1024];
OBJ_obj2txt(buff, 1024, obj, 0);
BUF_MEM *bptr = NULL;
char *buf = NULL;
BIO *bio = BIO_new(BIO_s_mem());
X509V3_EXT_print(bio, ex, 0, 0);
BIO_flush(bio);
BIO_get_mem_ptr(bio, &bptr);
//bptr->data is not NULL terminated - add null character
buf = (char *)malloc((bptr->length + 1) * sizeof(char));
memcpy(buf, bptr->data, bptr->length);
buf[bptr->length] = '\0';
BIO_flush(bio);
//Can print or parse value
return buf;
}
char* get_key_usage(X509 *cert){
/* -returns a string that represents the certificate's key usage
-taken from the sample code and modified
*/
X509_EXTENSION *ex = X509_get_ext(cert, X509_get_ext_by_NID(cert, NID_ext_key_usage, -1));
ASN1_OBJECT *obj = X509_EXTENSION_get_object(ex);
char buff[1024];
OBJ_obj2txt(buff, 1024, obj, 0);
BUF_MEM *bptr = NULL;
char *buf = NULL;
BIO *bio = BIO_new(BIO_s_mem());
X509V3_EXT_print(bio, ex, 0, 0);
BIO_flush(bio);
BIO_get_mem_ptr(bio, &bptr);
//bptr->data is not NULL terminated - add null character
buf = (char *)malloc((bptr->length + 1) * sizeof(char));
memcpy(buf, bptr->data, bptr->length);
buf[bptr->length] = '\0';
//Can print or parse value
return buf;
}
int get_public_key_length(X509 *cert){
/*Gets the length of the key and returns its size in bits*/
EVP_PKEY *public_key = X509_get_pubkey(cert);
RSA *rsa_key = EVP_PKEY_get1_RSA(public_key);
int key_length_bytes = RSA_size(rsa_key);
return (key_length_bytes*8);
}
char* get_domain_name(X509 *cert){
//gets the common name for this certifate and returns it as a string
X509_NAME *x509_name = X509_get_subject_name(cert);
char *cert_cn = X509_NAME_oneline(x509_name, 0, 0);
cert_cn =(cert_cn+1);
char **chain = str_split(cert_cn, '/');
char *domain = chain[5];
char **domain_clean = str_split(domain, '=');
return domain_clean[1];
}
char* compare_not_before(X509 *cert){
// gets a certificate's not before date and compares it to the current date
int day, sec;
const ASN1_TIME *not_before = X509_get_notBefore(cert);
if (!ASN1_TIME_diff(&day, &sec, NULL, not_before)){
/* Invalid time format */
return "invalid";
}
if (day > 0 || sec > 0){
return "Later";
}
else if (day < 0 || sec < 0){
return "Sooner";
}
else{
return "Same";
}
return "invalid";
}
char* compare_not_after(X509 *cert){
// gets a certificate's not after date and compares it to the current date
int day, sec;
const ASN1_TIME *not_after = X509_get_notAfter(cert);
if (!ASN1_TIME_diff(&day, &sec, NULL, not_after)){
/* Invalid time format */
printf("what the fuck do now\n");
}
if (day > 0 || sec > 0){
return "Later";
}
else if (day < 0 || sec < 0){
return "Sooner";
}
else{
return "Same";
}
return "invalid?";
}
char* concat(char *s1, char *s2){
//concats two strings
char *result = malloc(strlen(s1)+strlen(s2)+1);
strcpy(result, s1);
strcat(result, s2);
return result;
}
char** str_split(const char* a_str, const char a_delim){
//splits up string by a delimiter
char** result = 0;
size_t count = 0;
char* tmp = (char *)a_str;
char* copy = (char *)a_str;
char* last_comma = 0;
char delim[2];
delim[0] = a_delim;
delim[1] = 0;
/* Count how many elements will be extracted. */
while (*tmp){
if (a_delim == *tmp){
count++;
last_comma = tmp;
}
tmp++;
}
/* Add space for trailing token. */
count += last_comma < (a_str + strlen(a_str) - 1);
/* Add space for terminating null string so caller
knows where the list of returned strings ends. */
count++;
result = malloc(sizeof(char*) * count);
if (result){
size_t idx = 0;
char* token = strtok(copy, delim);
while (token){
assert(idx < count);
*(result + idx++) = strdup(token);
token = strtok(0, delim);
}
assert(idx == count - 1);
*(result + idx) = 0;
}
return result;
}
int matches_subject_alternative_name(const char *hostname, X509 *server_cert) {
//goes through the alternative domain names for a certifate and validates each one
int i;
int san_names_nb = -1;
STACK_OF(GENERAL_NAME) *san_names = NULL;
// Try to extract the names within the SAN extension from the certificate
san_names = X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL);
if (san_names == NULL) {
return 0;
}
san_names_nb = sk_GENERAL_NAME_num(san_names);
// Check each name within the extension
for (i=0; i<san_names_nb; i++) {
const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);
if (current_name->type == GEN_DNS) {
// Current name is a DNS name, let's check it
char *dns_name = (char *) ASN1_STRING_data(current_name->d.dNSName);
printf ("\t%s\n", dns_name);
// Make sure there isn't an embedded NUL character in the DNS name
if (ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) {
return 0;
break;
}
else { // Compare expected hostname with the DNS name
if (validate_wildcard_string(hostname, dns_name) == 1) {
return 1;
break;
}
}
}
}
sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
return 0;
}
int validate_key_usage(char* key_usage){
//gets the key usage as a string from the helper function above, and then validates if it is equal to "TLS Web Server Authentication""
//key usage may be a bunch of strings, need to get first one if this is the case
if(strlen("TLS Web Server Authentication")!=strlen(key_usage)){
char **key_usage_clean = str_split(key_usage, ',');
if(strcmp(key_usage_clean[0], "TLS Web Server Authentication") == 0){
return 1;
}
return 0;
}
else{
if(strcmp(key_usage, "TLS Web Server Authentication") == 0){
return 1;
}
return 0;
}
}
int validate_basic_constraints(char* basic_constraints){
//gets the basic constraint from a helper function above and then validates if it is "CA: FALSE"
if(strcmp(basic_constraints, "CA:FALSE")==0){
// printf("\tBASIC CONSTRAINT PASS\n");
return 1;
}
return 0;
}
int validate_wildcard_string(const char *hostname, char*hostname_with_wildcard){
//compares a domain with a wildcard with a given url, the wildcard is stripped of its '*' and '.'
//the string is then compared with the url (also having all things left from the initial '.' removed)
char *hostname_with_wildcard_sliced = str_slice_to_end(hostname_with_wildcard, (find_first_instanceof(hostname_with_wildcard, '.')));
char *hostname_sliced = str_slice_to_end(hostname, (find_first_instanceof(hostname, '.')));
if(strcasecmp(hostname_with_wildcard_sliced, hostname_sliced)==0){
if (DEBUG){
printf("\t\tWILDCARD FUNCTION\t\t%s == %s\n", hostname_with_wildcard_sliced, hostname_sliced);
}
return 1;
}
return 0;
}
int validate_key_length(int length){
//validates whether or not the certifate's key is 2048 bits
if (length==2048){
return 1;
}
return 0;
}
int validate_CN(const char* hostname, char*cn){
//matches the common name with the given url, if a wildcard is present, it gives the strings to the wildcard validator
if(cn[0]=='*'){
return (validate_wildcard_string(hostname, cn));
}
else{
if(strcasecmp(cn, hostname)==0){
return 1;
}
else{
return 0;
}
}
return 0;
}
int validate_CN_and_SAN(const char *url, X509 *cert){
//gets the result from both the CN and SAN validator and returns true if either are valid
if(validate_CN(url, get_domain_name(cert)) || matches_subject_alternative_name(url, cert)){
return 1;
}
else{return 0;}
}
int validate_not_before(X509 *cert){
//validates the not before date to be consistent with the current time
if(strcmp(compare_not_before(cert), "Sooner")==0){
return 1;
}
return 0;
}
int validate_not_after(X509 *cert){
//validates the not after date to be consistent with the current time
if(strcmp(compare_not_after(cert), "Later")==0){
return 1;
}
return 0;
}
int validate_certificate(const char *url, X509 *cert){
//the final validation decision, takes all of the validation results from each aspect of the certificate
//and makes sure there are no invalid components
int a = validate_basic_constraints(get_basic_constraints(cert));
int b = validate_key_usage(get_key_usage(cert));
int c = validate_key_length(get_public_key_length(cert));
int d = validate_not_before(cert);
int e = validate_not_after(cert);
int f = validate_CN_and_SAN(url, cert);
//if any of these is invalid, the certificate is invalid
if(a & b & c & d & e & f){
return 1;
}
else{
return 0;
}
}
int find_first_instanceof(const char *str, char delim){
//gets the first instance of a char in a string
int i;
for (i=0;i<=strlen(str);i++){
if(str[i]==delim){
return i;
}
}
return -1;
}
char *str_slice_to_end(const char *str, int begin){
//gets rid of things left of the index(inclusive) in a string
//eg str_slice_to_end("www.example.com",3) becomes example.com
char *tmp = (char *)str;
tmp = (tmp+=begin+1);
return tmp;
}