Author: nikou Date: Mon Feb 27 14:36:00 2012 New Revision: 2902 Log: tool to extraxt certification information Added: trunk/community-operator/client/pisa-cert-info.c Added: trunk/community-operator/client/pisa-cert-info.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ trunk/community-operator/client/pisa-cert-info.c Mon Feb 27 14:36:00 2012 (r2902) @@ -0,0 +1,534 @@ +/** + * @file + * This program will determine whether certain properties of a certificate (which is + * contained in a file) are valid. Currently, only looking at the dates between which + * the certificate is deemed valid (and comparing them to the current system time) is + * implemented. + * + * @brief check a certificate according to a specified property + * @author Christoph Viethen <christoph.viethen@xxxxxxxxxxxxxx> + */ +#include <inttypes.h> +#include <openssl/asn1.h> +#include <openssl/pem.h> +#include <openssl/x509.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +//#include "cryptlib.h" +//#include <openssl/buffer.h> +//#include <openssl/bn.h> +#ifndef NO_RSA +//#include <openssl/rsa.h> +#endif +#ifndef NO_DSA +//#include <openssl/dsa.h> +#endif +//#include <openssl/objects.h> +//#include <openssl/x509v3.h> + +/** + * calculates a correction value that needs to be added to the second parameter + * of X509_cmp_time() in case we are using a buggy version of OpenSSL + * + * It is assumed that asn1_time points to an ASN1_TIME structure that has been + * verified to be correct (e.g. by ASN1_TIME_check()). + * + * @param asn1_time the time struct (first param to X509_cmp_time()) that we want to compare against later + * @return correction value, which may be pos., neg., or 0 + */ + +int X509_print_fp_cop(FILE *fp, X509 *x); +int X509_print_cop(BIO *bp, X509 *x); + +static void t1(BIO *bp) { BIO_write(bp,"<tr><td>",8); } +static void t2(BIO *bp) { BIO_write(bp,"</td><td>",9); } +static void t3(BIO *bp) { BIO_write(bp,"</td></tr>\n",11); } + +static time_t calculate_correction(ASN1_TIME *asn1_time) +{ + time_t offset = 0; + + char *asn1_time_data = 0; + + if ((asn1_time_data = calloc(1, asn1_time->length + 1))) { + strncpy(asn1_time_data, (char *) asn1_time->data, asn1_time->length); + + size_t asn1_time_length = strlen(asn1_time_data); + + /* does the time string contain an offset of +/-hhmm against UTC? + * - would have to be more than 5 digits long, or we don't even need to look */ + if (asn1_time_length > 5) { + if (asn1_time_data[asn1_time_length - 5] == '+' || + asn1_time_data[asn1_time_length - 5] == '-') { + int pos = asn1_time_length - 4; + + offset = (asn1_time_data[pos] - '0') * 10; + offset += (asn1_time_data[++pos] - '0'); + offset *= 60; + + offset += (asn1_time_data[++pos] - '0') * 10; + offset += (asn1_time_data[++pos] - '0'); + offset *= 60; + + if (asn1_time_data[asn1_time_length - 5] == '-') { + offset = -offset; + } + } + } + free(asn1_time_data); + } + + /* the bug in OpenSSL causes the offset to get substracted instead of added, or added + * instead of substracted; accordingly, the mistake can be corrected by performing the + * originally intended operation twice */ + return 2 * offset; +} + +/** + * determines whether OpenSSL has a buggy X509_cmp_time() and whether our workaround + * does actually work + * + * @return truth value "buggy or not buggy" +*/ +static bool openssl_is_buggy(void) +{ + bool is_buggy = false; + + /* "8201020700-0500" is the X.680 UTCTime representation of the point in time + * that POSIX calls "378820800 seconds since January 1st, 1970 (the epoch) */ + time_t secs_since_epoch = 378820800 - 1; + ASN1_TIME *const utc_time = ASN1_TIME_new(); + + if (utc_time && ASN1_UTCTIME_set_string(utc_time, "8201020700-0500")) { + /* comparing the UTCTime with the mentioned number of seconds minus 1 should + * give the result of "1", meaning that the first parameter is a later point + * in time than the second */ + const int cmp_result = X509_cmp_time(utc_time, &secs_since_epoch); + + /* on buggy OpenSSL versions, we don't get the expected result "1" */ + if (cmp_result != 1) { + /* find out whether our workaround works ... */ + secs_since_epoch += calculate_correction(utc_time); + + if (X509_cmp_time(utc_time, &secs_since_epoch) == 1) { + secs_since_epoch += 2; + if (X509_cmp_time(utc_time, &secs_since_epoch) == -1) { + /* only if we reach this point, the bug exists *and* the + * workaround is capable of getting around it */ + is_buggy = true; + } + } + } + ASN1_TIME_free(utc_time); + } + return is_buggy; +} + +/** + * will determine the number to be "okay", i.e. suitable for using it as a parameter to check_dates(), + * if it is a positive natural number (in decimal) with a length of max. 8 digits + * + * @param string string representation of the number + * @return truth value "number is okay or not" + */ +/**static bool number_ok(char *string) +{ + if (string != NULL) { + size_t len; + + // allow one leading '+' + if (string[0] == '+') { + string++; + } + +if ((len = strlen(string)) > 0 && (len <= 8)) { + for (size_t i = 0; i < len; i++) { + if (string[i] < '0' || string[i] > '9') { + return false; + } + } + return true; +} +} +return false; +} +*/ +/** +* determines whether the current system time is between the "notBefore" and "notAfter" +* boundaries of the certificate, with a certain offset before the "notAfter" point in time +* in case the num_seconds_string is a string with a numerical value +* +* @param cert the certificate +* @param num_seconds_string string, containing a positive number of seconds, assumed to have been checked by number_ok() +* @return truth value "current time could be successfully determined to be between boundaries, or not" +*/ + + +//######################################################################################## + + +int X509_print_fp_cop(FILE *fp, X509 *x) +{ +BIO *b; +int ret; + +if ((b=BIO_new(BIO_s_file())) == NULL) + { +// X509err(X509_F_X509_PRINT_FP,ERR_R_BUF_LIB); + return(0); + } +BIO_set_fp(b,fp,BIO_NOCLOSE); +ret=X509_print_cop(b, x); +BIO_free(b); +return(ret); +} +int X509_print_cop(BIO *bp, X509 *x) +{ +long l; +int ret=0,i,j,n; +// char *m=NULL; +char *s; +X509_CINF *ci; + ASN1_INTEGER *bs; +EVP_PKEY *pkey=NULL; + const char *neg; +X509_EXTENSION *ex; +ASN1_STRING *str=NULL; + +ci=x->cert_info; +//if (BIO_write(bp,"Certificate:<br>\n<table>\n",25) <= 0) goto err; +//if (BIO_write(bp," Data:\n",10) <= 0) goto err; +BIO_printf(bp,"<h3><strong>@TR<<Certification Data>></strong></h3>\n"); +BIO_printf(bp,"<table style=\"width: 90%; margin-left: 2.5em; text-align: left; font-size: 0.8em;\" border=\"0\" cellpadding=\"2\" cellspacing=\"1\" summary=\"@TR<<Certification>>\">\n"); + + +BIO_printf(bp,"<tr><td><table style=\"margin-left: 0.2em; text-align: left;\" border=\"0\" cellpadding=\"1\" cellspacing= \"8\" summary=\"@TR<<Certification>>\">\n"); +l=X509_get_version(x); +if (BIO_printf(bp,"<tr><td>Version</td><td>%lu (0x%lx)</td></tr>\n",l+1,l) <= 0) goto err; + +t1(bp); +if (BIO_write(bp,"Serial Number",13) <= 0) goto err; +t2(bp); + +bs=X509_get_serialNumber(x); +if (bs->length <= 4) + { + l=ASN1_INTEGER_get(bs); + if (l < 0) + { + l= -l; + neg="-"; + } + else + neg=""; + if (BIO_printf(bp," %s%lu (%s0x%lx)",neg,l,neg,l) <= 0) + goto err; + } +else + { + neg=(bs->type == V_ASN1_NEG_INTEGER)?" (Negative)":""; + if (BIO_printf(bp,"\n%12s%s","",neg) <= 0) goto err; + + for (i=0; i<bs->length; i++) + { + if (BIO_printf(bp,"%02x%c",bs->data[i], + ((i+1 == bs->length)?'\n':':')) <= 0) + goto err; + } + } +t3(bp); + +i=OBJ_obj2nid(ci->signature->algorithm); +if (BIO_printf(bp,"<tr><td>Signature Algorithm</td><td> %s</td></tr>\n", + (i == NID_undef)?"UNKNOWN":OBJ_nid2ln(i)) <= 0) + goto err; + +t1(bp); +if (BIO_write(bp,"Issuer",6) <= 0) goto err; +t2(bp); +if (!X509_NAME_print(bp,X509_get_issuer_name(x),16)) goto err; +t3(bp); + +t1(bp); +//if (BIO_write(bp,"\n Validity\n",18) <= 0) goto err; +if (BIO_write(bp,"Valid Not Before",16) <= 0) goto err; +t2(bp); +if (!ASN1_TIME_print(bp,X509_get_notBefore(x))) goto err; +t3(bp); + +t1(bp); +if (BIO_write(bp,"Valid Not After",15) <= 0) goto err; +t2(bp); +if (!ASN1_TIME_print(bp,X509_get_notAfter(x))) goto err; +t3(bp); + +t1(bp); +if (BIO_write(bp,"Subject",7) <= 0) goto err; +t2(bp); +if (!X509_NAME_print(bp,X509_get_subject_name(x),16)) goto err; +t3(bp); + +//if (BIO_write(bp,"\n Subject Public Key Info:\n",34) <= 0) +// goto err; +i=OBJ_obj2nid(ci->key->algor->algorithm); +if (BIO_printf(bp,"<tr><td>Public Key Algorithm</td><td>%s<br>\n", +(i == NID_undef)?"UNKNOWN":OBJ_nid2ln(i)) <= 0) goto err; + +pkey=X509_get_pubkey(x); +if (pkey == NULL) +{ +BIO_printf(bp,"%12sUnable to load Public Key\n",""); +// ERR_print_errors(bp); +} +else +#ifndef NO_RSA +if (pkey->type == EVP_PKEY_RSA) +{ +BIO_printf(bp,"%12sRSA Public Key: (%d bit)\n","", +BN_num_bits(pkey->pkey.rsa->n)); + RSA_print(bp,pkey->pkey.rsa,16); + } +else +#endif +#ifndef NO_DSA +if (pkey->type == EVP_PKEY_DSA) + { + BIO_printf(bp,"%12sDSA Public Key:\n",""); + DSA_print(bp,pkey->pkey.dsa,16); + } +else +#endif + BIO_printf(bp,"%12sUnknown Public Key:\n",""); +t3(bp); +BIO_printf(bp,"</table>\n"); +EVP_PKEY_free(pkey); + +n=X509_get_ext_count(x); +if (n > 0) + { + + BIO_printf(bp,"<h3><strong>@TR<<X509v3 extensions>></strong></h3>\n"); + BIO_printf(bp,"<table style=\"width: 90%; margin-left: 2.5em; text-align: left; font-size: 1em;\" border=\"0\" cellpadding=\"2\" cellspacing=\"1\" summary=\"@TR<<Certification>>\">" ); + BIO_printf(bp,"<tr><td><table style=\"margin-left: 0.2em; text-align: left;\" border=\"0\" cellpadding=\"1\" cellspacing=\"8\" summary=\"@TR<<Certification>>\">\n "); + for (i=0; i<n; i++) + { + ASN1_OBJECT *obj; + ex=X509_get_ext(x,i); + t1(bp); + //if (BIO_printf(bp,"%12s","") <= 0) goto err; + obj=X509_EXTENSION_get_object(ex); + i2a_ASN1_OBJECT(bp,obj); + j=X509_EXTENSION_get_critical(ex); + t2(bp); + if (BIO_printf(bp,"%1s",j?"critical":"") <= 0) goto err; + // if(!X509V3_EXT_print(bp, ex, 0,0)) + // { + // //BIO_printf(bp, "%16s", ""); + // M_ASN1_OCTET_STRING_print(bp,ex->value); + // } + //if (BIO_write(bp,"\n",1) <= 0) goto err; + t3(bp); + } + } + +i=OBJ_obj2nid(x->sig_alg->algorithm); +if (BIO_printf(bp,"<tr><td>Signature Algorithm</td><td>%s<br>\n", + (i == NID_undef)?"UNKNOWN":OBJ_nid2ln(i)) <= 0) goto err; + +n=x->signature->length; + s=(char *)x->signature->data; + for (i=0; i<n; i++) + { + if ((i%18) == 0) + if (BIO_write(bp,"\n ",9) <= 0) goto err; + if (BIO_printf(bp,"%02x%s",(unsigned char)s[i], + ((i+1) == n)?"":":") <= 0) goto err; + } + if (BIO_write(bp,"\n",1) != 1) goto err; + if (!X509_CERT_AUX_print(bp, x->aux, 0)) goto err; + ret=1; +err: + if (str != NULL) ASN1_STRING_free(str); +// if (m != NULL) Free(m); +t3(bp); +BIO_write(bp,"\n</table>",9); + return(ret); + } + +//################################################################################################################ + + +static bool check_dates(X509 *cert, char *num_seconds_string) +{ + ASN1_TIME *notBefore; + ASN1_TIME *notAfter; + long version; + // EVP_PKEY *PUBKEY; +// EVP_PKEY *REQPUBKEY; + int Output; + time_t now1, now2; + + time_t notBefore_correction = 0; + time_t notAfter_correction = 0; + + int32_t num_seconds = 0; + + bool result = false; + + /* first, convert the time stamps to GeneralizedTime format */ + notBefore = ASN1_TIME_to_generalizedtime(X509_get_notBefore(cert), NULL); + notAfter = ASN1_TIME_to_generalizedtime(X509_get_notAfter(cert), NULL); + version = X509_get_version(cert); +// signaturtyp = X509_get_signature_type(?); +// PUBKEY = X509_get_pubkey(cert); +// REQPUBKEY = X509_REQ_extract_key(cert); + Output = X509_print_fp_cop(stdout, cert); + +// fprintf(stdout, "notBefore %s\n",notBefore->data); + +// fprintf(stdout, "notAfter %s\n",notAfter->data); + fprintf(stdout, "%s\n", ""); +// fprintf(stdout, "PUBKEY %d\n", PUBKEY->type); +// fprintf(stdout, "REQPUBKEY %d\n", REQPUBKEY->type); + if ((num_seconds_string == NULL || (sscanf(num_seconds_string, "%"SCNd32, &num_seconds) == 1)) && + (notBefore != NULL) && (notAfter != NULL)) { + if (openssl_is_buggy()) { + notBefore_correction = calculate_correction(notBefore); + notAfter_correction = calculate_correction(notAfter); + } + + /* find out the number of seconds since the epoch now, and use this constant + * value for both following comparisons */ + const time_t now0 = time(NULL); + + /* check whether the current system time is inbetween, inclusively, the notBefore and + * the notAfter time stamps of the certificate; offset the latter with num_seconds */ + now1 = now0 + notBefore_correction; + now2 = now0 + notAfter_correction + num_seconds; + + /* we might be running right after system startup, with no available time server, + * so the system might be believing we live shortly after Jan. 1st, 1970, so it might + * happen that "now1" or "now2" ended up smaller than 0 */ + if ((now1 > 0) && (now2 > 0) && (X509_cmp_time(notBefore, &now1) < 0) && (X509_cmp_time(notAfter, &now2) > 0)) { + result = true; + } + } + + /* ASN1_TIME_to_generalizedtime() calls above included implicit ASN1_GENERALIZEDTIME_new(), + * so free the memory again */ + if (notBefore != NULL) { + ASN1_TIME_free(notBefore); + } + if (notAfter != NULL) { + ASN1_TIME_free(notAfter); + } + + return result; +} + +/** + * outputs usage information + * + * @param output the file where the output should go - must be specified + */ +static void usage(FILE *output) +{ + + fprintf(output, "Nikous' Programm \n\n"); + fprintf(output, "pisa-cert-check - check properties of an X.509 certificate\n\n"); + + fprintf(output, "Usage:\n"); + fprintf(output, "pisa-cert-check <cert-filename> <property> {<opt. param>}\n\n"); + + fprintf(output, "with property being one of:\n\n"); + + fprintf(output, " dates check whether current system time has at least the value of the\n"); + fprintf(output, " certificate's \"notBefore\" and at most that of the \"notAfter\"\n"); + fprintf(output, " time stamp\n\n"); + + fprintf(output, " with an optional number of seconds added as parameter: handle\n"); + fprintf(output, " \"notBefore\" as above, but check for current system time to have\n"); + fprintf(output, " at most the value of the \"notAfter\" time stamp minus the indicated\n"); + fprintf(output, " number of seconds\n\n"); + + fprintf(output, "Example:\n"); + fprintf(output, "pisa-cert-check /etc/hip/cert dates 600\n"); + fprintf(output, "(makes sure certificate is currently valid and will not be expiring earlier\n"); + fprintf(output, " than 10 minutes from now)\n\n"); +} + +/** + * main() function; will process the command-line parameters, doing some simple checks + * for their validity, and then call the appropriate processing function depending on the + * value of "property" command-line param + * + * @param argc argument counter + * @param argv argument values + * @return truth value "certificate property could be verified to be in order, or not" + */ +int main(int argc, char **argv) +{ + char *filename = NULL; +// char *property = NULL; +// char *extra_param = NULL; + + char passphrase[1] = { 0 }; + + FILE *cert_file = NULL; + X509 *cert = NULL; + + bool result = false; + +// fprintf(stdout,"arhumente %d",argc); + + /* give usage help if required */ + if (argc == 1 || + (argc == 2 && strlen(argv[1]) == 2 && !strcmp(argv[1], "-h")) || + (argc == 2 && strlen(argv[1]) == 6 && !strcmp(argv[1], "--help"))) { + usage(stdout); + exit(EXIT_SUCCESS); + } + + /* process command-line parameters */ + + if (argc == 2 ) { + filename = argv[1]; + } else { + usage(stderr); + exit(EXIT_FAILURE); + } + + /* open the file the name of which was passed as parameter */ + if (!(cert_file = fopen(filename, "r"))) { + perror(filename); + exit(EXIT_FAILURE); + } + + if (!(cert = PEM_read_X509_AUX(cert_file, NULL, NULL, passphrase))) { + rewind(cert_file); + if (!(cert = d2i_X509_fp(cert_file, NULL))) { + fprintf(stderr, "%s: reading the certificate file failed\n", filename); + fclose(cert_file); + exit(EXIT_FAILURE); + } + } + + + + result = check_dates(cert,NULL); + + + /* clean up */ + X509_free(cert); + fclose(cert_file); + + /* return result of certificate check */ + if (result == true) { + exit(EXIT_SUCCESS); + } else { + exit(EXIT_FAILURE); + } +} -- This is the pisa developer mailing list. Please also subscribe to the main pisa list at: //www.freelists.org/list/pisa