diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..e126f79bebca0fe5f0098639caf5007e428197c4 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +all: + gcc -o certcheck assignment_2.c -lssl -lcrypto \ No newline at end of file diff --git a/assignment_2.c b/assignment_2.c new file mode 100644 index 0000000000000000000000000000000000000000..041055a47e45b481276320a6ad6503d76678fed0 --- /dev/null +++ b/assignment_2.c @@ -0,0 +1,272 @@ +/* +Name: Jihai Fan +Student ID: 832919 +Login name: j.fan11@student.unimelb.edu.au +Description: validate of x509 cert against DNS name +*/ + +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/bio.h> +#include <openssl/pem.h> +#include <openssl/err.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +// get column from csv +char* getfield(char* line, int num){ + char* tok; + char *newline = strtok(line, "\n"); + for (tok = strtok(newline, ","); tok && *tok; tok = strtok(NULL, ",")){ + if (!--num) + return tok; + } + return NULL; +} + +// change the format from asn1_time to time_t +static time_t ASN1_GetTimeT(const ASN1_TIME* time){ + struct tm t; + const char* str = (const char*) time->data; + size_t i = 0; + + memset(&t, 0, sizeof(t)); + + if (time->type == V_ASN1_UTCTIME) {/* two digit year YYMMDDHHMMSS*/ + t.tm_year = (str[i++] - '0') * 10; + t.tm_year += (str[i++] - '0'); + if (t.tm_year < 70) + t.tm_year += 100; + } else if (time->type == V_ASN1_GENERALIZEDTIME) {/* four digit year YYYYMMDDHHMMSS*/ + t.tm_year = (str[i++] - '0') * 1000; + t.tm_year+= (str[i++] - '0') * 100; + t.tm_year+= (str[i++] - '0') * 10; + t.tm_year+= (str[i++] - '0'); + t.tm_year -= 1900; + } + t.tm_mon = (str[i++] - '0') * 10; + t.tm_mon += (str[i++] - '0') - 1; // -1 since January is 0 not 1. + t.tm_mday = (str[i++] - '0') * 10; + t.tm_mday+= (str[i++] - '0'); + t.tm_hour = (str[i++] - '0') * 10; + t.tm_hour+= (str[i++] - '0'); + t.tm_min = (str[i++] - '0') * 10; + t.tm_min += (str[i++] - '0'); + t.tm_sec = (str[i++] - '0') * 10; + t.tm_sec += (str[i++] - '0'); + + return mktime(&t); +} + +// compare ASN1_time with current time +int cmp_current_time(const ASN1_TIME *ctm){ + time_t current = time(NULL); + time_t ct = ASN1_GetTimeT(ctm); + double result = difftime(current, ct); + if (result < 0){ + return 1; + }else{ + return -1; + } +} + +// validate the time of certificate +int cmp_time(X509 *cert){ + int i; + i = cmp_current_time(X509_get_notBefore(cert)); + int j; + j = cmp_current_time(X509_get_notAfter(cert)); + if (i != -1 || j != 1){ // one should before current time, and another should be after + return 1; + } + return 0; +} + +// validate the rsa key length +int cmp_rsa_length(X509 *cert){ + EVP_PKEY *public_key = X509_get_pubkey(cert); + RSA *rsa_key = EVP_PKEY_get1_RSA(public_key); + int key_length = BN_num_bits(rsa_key->n); + if (key_length < 2048){ + RSA_free(rsa_key); + return 1; + } + RSA_free(rsa_key); + return 0; +} + +// validate common name +int cmp_cn(X509 *cert, char *url){ + X509_NAME *cert_issuer = X509_get_subject_name(cert); + char common_name[256] = "Issuer CN NOT FOUND"; + X509_NAME_get_text_by_NID(cert_issuer, NID_commonName, common_name, 256); + + /** + * compare from the back of dns_name + * *.test.com + * www.test.com + * |---------| + */ + int url_len = strlen(url); + int cn_len = strlen(common_name); + if (cn_len > url_len){ + return 1; + } + int wildcard = 1; + int result = strcmp(&common_name[wildcard], &url[url_len - cn_len + wildcard]); + if (result != 0){ + return 1; + } + free(cert_issuer); + return result; +} + +// validate CA +int cmp_ca(X509 *cert){ + BASIC_CONSTRAINTS *bc = X509_get_ext_d2i(cert, NID_basic_constraints, NULL, NULL); + if (bc->ca != 0){ + free(bc); + return 1; + } + free(bc); + return 0; +} + +// validate Enhenced Key Usage +int cmp_key_usage(X509 *cert){ + EXTENDED_KEY_USAGE *usage = X509_get_ext_d2i(cert, NID_ext_key_usage, NULL, NULL); + if(usage){ + for(int i = 0; i < sk_BIO_num(usage); i++){ + int result = OBJ_obj2nid(sk_ASN1_OBJECT_value(usage,i)); // get the nid of the EKU in the stack + if (result == NID_server_auth){ // NID_server_auth is the ID for TLS web server + free(usage); + return 0; + } + } + } + free(usage); + return 1; +} + +// validate subject alternative name +int cmp_san(X509 *cert, char *url){ + int san_names_nb = -1; + int result = 0; + STACK_OF(GENERAL_NAME) *san_names = NULL; + + // try to get SAN + san_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (san_names == NULL) { + return 1; + } + + // length of the stack + san_names_nb = sk_GENERAL_NAME_num(san_names); + + // check each name within the extension + for (int i=0; i<san_names_nb; i++) { + const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i); + + // make sure it is a dns name + if (current_name->type == GEN_DNS) { + char *dns_name = (char *) ASN1_STRING_data(current_name->d.dNSName); + // printf("san is: %s", dns_name); + int url_len = strlen(url); + int dns_len = strlen(dns_name); + if (dns_len > url_len){ + return 1; + } + int wildcard = 1; + if(strcmp(&dns_name[wildcard], &url[url_len - dns_len + wildcard]) == 0){ + return 0; + } + } + } + sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); + + return 1; +} + +int validate_url(X509 *cert, char *url){ + int time_result = cmp_time(cert); + int cn_result = cmp_cn(cert, &url); + int rsa_result = cmp_rsa_length(cert); + int ca_result = cmp_ca(cert); + int eku_result = cmp_key_usage(cert); + int san_result = cmp_san(cert, &url); + // printf("%i\n",time_result); + // printf("%i\n",cn_result); + // printf("%i\n",rsa_result); + // printf("%i\n",ca_result); + // printf("%i\n",eku_result); + // printf("%i\n",san_result); + int result; + // only one fo SAN and CN should be + if (cn_result == 0){ + result = time_result + rsa_result + ca_result + eku_result; + } else { + result = time_result + rsa_result + ca_result + eku_result + san_result; + } + if (result != 0){ + return 0; + } + return 1; +} + +int read_and_validate(const char *test_cert_example, const char *url){ + // const char test_cert_example[] = "cert-file2.pem"; + BIO *certificate_bio = NULL; + X509 *cert = NULL; + X509_NAME *cert_issuer = NULL; + X509_CINF *cert_inf = NULL; + STACK_OF(X509_EXTENSION) * ext_list; + + // initialise openSSL + OpenSSL_add_all_algorithms(); + ERR_load_BIO_strings(); + ERR_load_crypto_strings(); + + // create BIO object to read certificate + certificate_bio = BIO_new(BIO_s_file()); + + // read certificate into BIO + if (!(BIO_read_filename(certificate_bio, test_cert_example))) + { + fprintf(stderr, "Error in reading cert BIO filename"); + exit(EXIT_FAILURE); + } + if (!(cert = PEM_read_bio_X509(certificate_bio, NULL, 0, NULL))) + { + fprintf(stderr, "Error in loading certificate"); + exit(EXIT_FAILURE); + } + + // get the result + int result = validate_url(cert, url); + + X509_free(cert); + BIO_free_all(certificate_bio); + return result; +} + +int main(int argv, char** argc) +{ + + FILE* stream = fopen(argc[1], "r"); + FILE* output = fopen("output.csv", "w+"); + char line[1024]; + while (fgets(line, 1024, stream)) + { + char* tmp = strdup(line); + char* url = getfield(tmp, 2); + char* cert = getfield(tmp, 1); + int result = read_and_validate(cert, url); + printf("%s\n",url); + fprintf(output, "%s,%s,%i\n", cert, url, result); + // NOTE strtok clobbers tmp + free(tmp); + } + + exit(0); +} \ No newline at end of file