diff --git a/certexample.c b/certexample.c new file mode 100644 index 0000000000000000000000000000000000000000..6a9df3efacd48577abb96b844d3a72559b66e076 --- /dev/null +++ b/certexample.c @@ -0,0 +1,463 @@ +/** + Name: Shubham Jayswal + Login Id: sjayswal@student.unimelb.edu.au + Example certifcate code + gcc -o certexample certexample.c -lssl -lcrypto + */ + +#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> + +void read_csv(char *filename); +int get_keylen(EVP_PKEY * pubkey); +int validate_date(ASN1_TIME *to); +int validate_cn(char *hostname, X509 *cert); +int validate_ca(X509 *cert); +int validate_san(char *hostname, X509 *cert); +int validate_key_usage(X509 *cert); +int is_subdomain(char *hostname); +int sub_domain_match(char *hostname, char *wildcard); + +/** + * Returns 1 if the certificate is not for a CA + */ +int validate_ca(X509 *cert) { + + 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()); + if (!X509V3_EXT_print(bio, ex, 0, 0)) { + fprintf(stderr, "Error in reading extensions"); + } + 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_free_all(bio); + return (strcmp("CA:TRUE", buf) == 0); + +} + +/** + * Validates extended key usages. + * + * return 0 if valid + * -1 if a code signing certificate has been used + * -2 if a client certificate has been used. + * -3 if the extendend usage info isn't clear + * 1 if there are multiple allowed usages which includes on a webserver + */ +int validate_key_usage(X509 *cert) { + + 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()); + if (!X509V3_EXT_print(bio, ex, 0, 0)) { + fprintf(stderr, "Error in reading extensions"); + } + 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_free_all(bio); + + if (strcmp(buf, "Code Signing") == 0) { + return -11; + } + if (strcmp(buf, "TLS Web Client Authentication") == 0) { + return -2; + } + + if (strstr(buf, "Web Server Authentication") != NULL) { + /* Web Server Authentication is one of the supported usages, but are there any others? */ + if (strstr(buf, ",") != NULL) { + return 1; + } + return 0; + } + return -3; +} + +/** + * Return 0 when there is a perfect match between one of the SANs and the domain + * 1 when the SAN is a wildcard and there is a wildcard match + * -1 When there is not SAN information + * -2 When san information i present but we cannot match + */ +int validate_san(char *hostname, X509 *cert) { + + GENERAL_NAMES *alt_names = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, + 0); + char *buf; + + int len; + int i; + if (alt_names == NULL) { + return -1; + } + + len = sk_GENERAL_NAME_num(alt_names); + + for (i = 0; i < len; ++i) { + GENERAL_NAME *name = sk_GENERAL_NAME_value(alt_names, i); + if (name->type == GEN_DNS) { + buf = (char *) ASN1_STRING_data(name->d.dNSName); + + if (buf[0] == '*' && is_subdomain(hostname) + && sub_domain_match(hostname, buf)) { + printf("SAN WILDCARD %s %s\n", hostname, buf); + OPENSSL_free(buf); + return 1; + } else { + OPENSSL_free(buf); + return -2; + } + + } + } + + return -2; +} + +/** + * Validate the given certificates date + * + * return 1 if the date is in the future, 0 if the date is current + * and -1 if the date is in the past. + * + * + */ +int validate_date(ASN1_TIME *to) { + int day, sec; + ASN1_TIME_diff(&day, &sec, NULL, to); + + if (day > 0 || sec > 0) { + return 1; + } + if (day < 0 || sec < 0) { + return -1; + } + + return 0; + +} + +/** + * Validates the common name + * + * Return 0 if there is an exact match. + * 1 if this is a wild card certificate and can be matched + * -1 cannot be matched + * -2 is a wild card but still cannot be matched because no sub domai. + */ + +int validate_cn(char *hostname, X509 *cert) { + + X509_NAME_ENTRY *common_name_entry = NULL; + ASN1_STRING *common_name_asn1 = NULL; + char *common_name_str = NULL; + + int location = X509_NAME_get_index_by_NID( + X509_get_subject_name((X509 *) cert), NID_commonName, -1); + if (location >= 0) { + common_name_entry = X509_NAME_get_entry( + X509_get_subject_name((X509 *) cert), location); + if (common_name_entry != NULL) { + common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry); + if (common_name_asn1 != NULL) { + common_name_str = (char *) ASN1_STRING_data(common_name_asn1); + + if (ASN1_STRING_length(common_name_asn1) + == strlen(common_name_str)) { + /* after a long drawn out process we have finaly got the common name as a string. + * but we are not out of the woods yet. + */ + if (strcasecmp(hostname, common_name_str) == 0) { + /* all is well, we have an exact match */ + return 0; + } else { + if (common_name_str[0] == '*') { + /* wildcard, we can find out if there is a subdomain by counting .. */ + + if (is_subdomain(hostname) + && sub_domain_match(hostname, + common_name_str)) { + + return 1; + } else { + return -2; + } + } + } + } + } + } + } + + return -1; +} + +/** + * Is there a wildcard match? + * + * 1 if true + */ +int sub_domain_match(char *hostname, char *wildcard) { + char *h = strstr(hostname, "."); + char *w = strstr(wildcard, "."); + + return strcmp(h + 1, w + 1) == 0; +} + +/** + * Does this hostname have a subdomain component. + */ +int is_subdomain(char *hostname) { + int i, count; + for (i = 0, count = 0; hostname[i] != '\0'; i++) { + if (hostname[i] == '.') { + count++; + } + } + return count > 1; +} +/** + * Gets the keylength in bits. + */ +int get_keylen(EVP_PKEY * pubkey) { + RSA * rsa; + + rsa = EVP_PKEY_get1_RSA(pubkey); + int key_length = RSA_size(rsa); + + free(rsa); + return key_length * 8; +} + +void read_csv(char *filename) { + + BIO *certificate_bio = NULL; + X509 *cert = NULL; + int key_len; + + int domain_status; + + char line[127]; + int i = 0, j = 0, cmp, san_validation; + char site[64]; + char file_name[64]; + + FILE *in = fopen(filename, "r"); + FILE *out; + + if (in != NULL) { + out = fopen("output.csv", "w"); + if (out != NULL) { + 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()); + + while (fscanf(in, "%s", line) > 0) { + /* first part of the code. We read the file_name to a pair, certificate file_name name and hostname */ + for (i = 0; line[i] != '\0' && line[i] != ','; i++) { + file_name[i] = line[i]; + } + + file_name[i++] = '\0'; + + for (j = 0; line[i] != '\0' && line[i] != ','; i++) { + site[j++] = line[i]; + } + + site[j] = '\0'; + + if (!(BIO_read_filename(certificate_bio, file_name))) { + 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); + } + + /* now for the domain verification. This is the first step that we are peforming. */ + domain_status = validate_cn(site, cert); + + if (domain_status == -1) { + fprintf(out, + "%s,%s,%d,Invalid - domain does not match Common Name\n", + file_name, site, 0); + continue; + } + if (domain_status == -2) { + fprintf(out, + "%s,%s,%d,Invalid - wildcard Common Name requires subdomain \n", + file_name, site, 0); + continue; + } + + if (validate_ca(cert)) { + /* failed the first test. This is for a CA */ + fprintf(out, "%s,%s,%d,Invalid - marked as a CA\n", + file_name, site, 0); + + continue; // continue is ugly but a huge chain of if else is uglier. + + } + + key_len = get_keylen(X509_get_pubkey(cert)); + if (key_len < 2048) { + /* failed the second test, the keylen is too short */ + fprintf(out, + "%s,%s,%d,Invalid because key size is too small (%d bits instead of >=2048)\n", + file_name, site, 0, key_len); + continue; + } + + /* now we check the dates */ + cmp = validate_date(X509_get_notBefore(cert)); + if (cmp == 1) { + fprintf(out, + "%s,%s,%d,Invalid valid from date - date is in the future\n", + file_name, site, 0); + continue; + } + + cmp = validate_date(X509_get_notAfter(cert)); + if (cmp == -1) { + + fprintf(out, + "%s,%s,%d,Invalid - certificate has expired - valid to date in the past\n", + file_name, site, 0); + + continue; + } + + /* now for key usage */ + cmp = validate_key_usage(cert); + + if (cmp == -11) { + fprintf(out, + "%s,%s,%d,Invalid - extended key usage is code signing only\n", + file_name, site, 0); + continue; + } + if (cmp == -2) { + fprintf(out, + "%s,%s,%d,Invalid - extended key usage is only for client authentication\n", + file_name, site, 0); + continue; + + } + + san_validation = validate_san(site, cert); + + /** + * Don't ask how i wrote this huge bunch of if else conditions. I don't know myself!! + */ + if (domain_status == 0) { + if (san_validation == 0) { + fprintf(out, + "%s,%s,1,Valid - all correct - domain matches Common Name or SAN wildcard\n", + file_name, site); + } else { + if (cmp == 1) { + fprintf(out, + "%s,%s,1,Valid - all correct - tests multiple extended key usage\n", + file_name, site); + } else { + if (san_validation == -1) { + fprintf(out, + "%s,%s,1,Valid correct key size; valid dates; not a CA; server authentication; domain matches Common Name\n", + file_name, site); + } else if (san_validation == 0) { + + fprintf(out, + "%s,%s,1,Valid - all correct - domain matches Common Name and SAN\n", + file_name, site); + } else if (san_validation == 1) { + printf("%s %d %d\n", site, domain_status, + san_validation); + fprintf(out, + "%s,%s,1,Valid - all correct - domain matches Common Name or SAN wildcard\n", + file_name, site); + } else { + fprintf(out, + "%s,%s,1,Valid - all correct - domain matches Common Name and SAN\n", + file_name, site); + } + + } + } + } else { + if (cmp == 0) { + if (san_validation == 1) { + fprintf(out, + "%s,%s,1,Valid - all correct - domain matches Common Name or SAN wildcard\n", + file_name, site); + } else { + if (san_validation == -1) { + fprintf(out, + "%s,%s,1,Valid - all correct - domain matches through Common Name wildcard No SAN\n", + file_name, site); + } + } + } else { + fprintf(out, + "%s,%s,1,Valid - all correct - tests multiple extended key usage\n", + file_name, site); + } + + } + + } + } else { + printf("Couldn't create output file_name\n"); + } + } else { + printf("File not found\n"); + } + + X509_free(cert); + BIO_free_all(certificate_bio); + +} + +int main(int argc, char *argv[]) { + if (argc != 2) { + printf("Incorrect number of arguments\n"); + return 1; + } + read_csv(argv[1]); + return 0; +}