--- httpd-trunk/modules/ssl/ssl_private.h (revision 793376) +++ httpd-trunk/modules/ssl/ssl_private.h (working copy) @@ -199,7 +199,8 @@ #define SSL_OPT_FAKEBASICAUTH (1<<4) #define SSL_OPT_STRICTREQUIRE (1<<5) #define SSL_OPT_OPTRENEGOTIATE (1<<6) -#define SSL_OPT_ALL (SSL_OPT_STDENVVARS|SSL_OPT_EXPORTCERTDATA|SSL_OPT_FAKEBASICAUTH|SSL_OPT_STRICTREQUIRE|SSL_OPT_OPTRENEGOTIATE) +#define SSL_OPT_SUBJECTDIRATTR (1<<7) +#define SSL_OPT_ALL (SSL_OPT_STDENVVARS|SSL_OPT_EXPORTCERTDATA|SSL_OPT_FAKEBASICAUTH|SSL_OPT_STRICTREQUIRE|SSL_OPT_OPTRENEGOTIATE|SSL_OPT_SUBJECTDIRATTR) typedef int ssl_opt_t; /** @@ -504,6 +505,7 @@ apr_size_t nRenegBufferSize; } SSLDirConfigRec; + /** * function prototypes */ @@ -690,7 +692,13 @@ void ssl_var_register(apr_pool_t *p); char *ssl_var_lookup(apr_pool_t *, server_rec *, conn_rec *, request_rec *, char *); apr_array_header_t *ssl_ext_list(apr_pool_t *p, conn_rec *c, int peer, const char *extension); +apr_array_header_t *ssl_ext_subject_dir_attr_lookup(apr_pool_t *p, X509 *xs, int nid); +apr_array_header_t *ssl_ext_get_subject_dir_attr_values_by_NID(apr_pool_t *p, void *sda_object_ptr, int pdaNID); +void *ssl_ext_get_subject_dir_attrs_object_ptr(X509 *xs); +void ssl_ext_free_subject_dir_attrs_object_ptr(void *sda_object_ptr); +char *get_age_from_generalizedtime_str(apr_pool_t *p, char *str); + void ssl_var_log_config_register(apr_pool_t *p); /* Extract SSL_*_DN_* variables into table 't' from SSL object 'ssl', --- httpd-trunk/modules/ssl/ssl_engine_config.c (revision 793376) +++ httpd-trunk/modules/ssl/ssl_engine_config.c (working copy) @@ -1100,6 +1100,9 @@ else if (strcEQ(w, "OptRenegotiate")) { opt = SSL_OPT_OPTRENEGOTIATE; } + else if (strcEQ(w, "SubjectDirAttrVars")) { + opt = SSL_OPT_SUBJECTDIRATTR; + } else { return apr_pstrcat(cmd->pool, "SSLOptions: Illegal option '", w, "'", --- httpd-trunk/modules/ssl/ssl_engine_vars.c (revision 793376) +++ httpd-trunk/modules/ssl/ssl_engine_vars.c (working copy) @@ -32,6 +32,26 @@ #include "apr_time.h" +#ifdef HAVE_OPENSSL + +#include +#include + +/** + * Subject Directory Attributes extension + */ +typedef STACK_OF(X509_ATTRIBUTE) SUBJECT_DIRECTORY_ATTRIBUTES; + +ASN1_ITEM_TEMPLATE(SUBJECT_DIRECTORY_ATTRIBUTES) = + ASN1_EX_TEMPLATE_TYPE(ASN1_TFLG_SEQUENCE_OF, 0, Attributes, X509_ATTRIBUTE) +ASN1_ITEM_TEMPLATE_END(SUBJECT_DIRECTORY_ATTRIBUTES) + +DECLARE_ASN1_FUNCTIONS(SUBJECT_DIRECTORY_ATTRIBUTES) + +IMPLEMENT_ASN1_FUNCTIONS(SUBJECT_DIRECTORY_ATTRIBUTES) + +#endif + /* _________________________________________________________________ ** ** Variable Lookup @@ -51,6 +71,7 @@ static void ssl_var_lookup_ssl_cipher_bits(SSL *ssl, int *usekeysize, int *algkeysize); static char *ssl_var_lookup_ssl_version(apr_pool_t *p, char *var); static char *ssl_var_lookup_ssl_compress_meth(SSL *ssl); +static char *ssl_var_lookup_ssl_cert_ext_subject_dir_attrs(apr_pool_t *p, X509 *xs, char *var); static int ssl_is_https(conn_rec *c) { @@ -399,6 +420,10 @@ (nid == NID_undef) ? "UNKNOWN" : OBJ_nid2ln(nid)); resdup = FALSE; } + else if (strcEQn(var, "EXT_SDA_", 8)) { + result = ssl_var_lookup_ssl_cert_ext_subject_dir_attrs(p, xs, var+8); + resdup = FALSE; + } else if (strcEQ(var, "CERT")) { result = ssl_var_lookup_ssl_cert_PEM(p, xs); } @@ -408,6 +433,79 @@ return result; } +static char *ssl_var_lookup_ssl_cert_ext_subject_dir_attrs(apr_pool_t *p, X509 *xs, char *var) +{ + char *result = NULL; + + if (strcEQ(var, "GENDER")) { + apr_array_header_t *values = ssl_ext_subject_dir_attr_lookup(p, xs, NID_id_pda_gender); + if(values != NULL) { + char **elts = (char **) values->elts; + result = elts[0]; + } + } + else if (strlen(var) > 21 && strcEQn(var, "COUNTRYOFCITIZENSHIP_", 21)) { + int index = atoi(var+21); + apr_array_header_t *values = ssl_ext_subject_dir_attr_lookup(p, xs, NID_id_pda_countryOfCitizenship); + if((values != NULL) && (index < values->nelts)) { + char **elts = (char **) values->elts; + result = elts[index]; + } + } + else if (strlen(var) > 19 && strcEQn(var, "COUNTRYOFRESIDENCE_", 19)) { + int index = atoi(var+19); + apr_array_header_t *values = ssl_ext_subject_dir_attr_lookup(p, xs, NID_id_pda_countryOfResidence); + if((values != NULL) && (index < values->nelts)) { + char **elts = (char **) values->elts; + result = elts[index]; + } + } + else if (strcEQ(var, "PLACEOFBIRTH")) { + apr_array_header_t *values = ssl_ext_subject_dir_attr_lookup(p, xs, NID_id_pda_placeOfBirth); + if(values != NULL) { + char **elts = (char **) values->elts; + result = elts[0]; + } + } + else if (strcEQ(var, "DATEOFBIRTH")) { + apr_array_header_t *values = ssl_ext_subject_dir_attr_lookup(p, xs, NID_id_pda_dateOfBirth); + if(values != NULL) { + char **elts = (char **) values->elts; + result = elts[0]; + } + } + else if (strcEQ(var, "AGE")) { + apr_array_header_t *values = ssl_ext_subject_dir_attr_lookup(p, xs, NID_id_pda_dateOfBirth); + if(values != NULL) { + char **elts = (char **) values->elts; + result = get_age_from_generalizedtime_str(p, elts[0]); + } + } + return result; +} + + +char *get_age_from_generalizedtime_str(apr_pool_t *p, char *str) +{ + char *result = NULL; + char format[] = "%Y%m%d%H%M%S"; + char tmp[20]; + struct tm dateOfBirth; + struct tm today; + time_t now = time(0); + gmtime_r(&now, &today); + strptime(str, format, &dateOfBirth); + int age = today.tm_year - dateOfBirth.tm_year - 1; + if( (today.tm_mon > dateOfBirth.tm_mon) || ((today.tm_mon == dateOfBirth.tm_mon) && (today.tm_mday > dateOfBirth.tm_mday)) ) { + age++; + } + + sprintf(tmp, "%d", age); + result = apr_pstrdup(p, tmp); + return result; +} + + /* In this table, .extract is non-zero if RDNs using the NID should be * extracted to for the SSL_{CLIENT,SERVER}_{I,S}_DN_* environment * variables. */ @@ -849,6 +947,122 @@ return array; } + +void *ssl_ext_get_subject_dir_attrs_object_ptr(X509 *xs) +{ + void *retval = NULL; + +#ifdef HAVE_OPENSSL + X509_EXTENSION *ext = NULL; + ASN1_OCTET_STRING *ext_data_ocstr = NULL; + const unsigned char *ext_data; + int sdaIndex; + + if (xs == NULL) { + return NULL; + } + + /* Search "Subject Directory Attributes" extension */ + sdaIndex = X509_get_ext_by_NID(xs, NID_subject_directory_attributes, -1); + if(sdaIndex < 0) { + return NULL; + } + + /* Get Subject Directory Attributes OCTET_STRING */ + ext = X509_get_ext(xs, sdaIndex); + ext_data_ocstr = X509_EXTENSION_get_data(ext); + if(ext_data_ocstr == NULL) { + return NULL; + } + + /* Convert extension data (OCTET_STRING) to internal representation object */ + ext_data = ext_data_ocstr->data; + retval = (void *)d2i_SUBJECT_DIRECTORY_ATTRIBUTES(NULL, &ext_data, (long)ext_data_ocstr->length); +#endif + + return retval; +} + + +void ssl_ext_free_subject_dir_attrs_object_ptr(void *sda_object_ptr) +{ +#ifdef HAVE_OPENSSL + SUBJECT_DIRECTORY_ATTRIBUTES *sda = (SUBJECT_DIRECTORY_ATTRIBUTES *)sda_object_ptr; + if(sda != NULL) SUBJECT_DIRECTORY_ATTRIBUTES_free(sda); +#endif +} + + +apr_array_header_t *ssl_ext_get_subject_dir_attr_values_by_NID(apr_pool_t *p, void *sda_object_ptr, int pdaNID) +{ + apr_array_header_t *array = NULL; + +#ifdef HAVE_OPENSSL + SUBJECT_DIRECTORY_ATTRIBUTES *sda = (SUBJECT_DIRECTORY_ATTRIBUTES *)sda_object_ptr; + int attr_index, value_index, num_values; + + if(sda == NULL) { + return NULL; + } + + /* Search pdaNID in "Subject Directory Attributes", and create an array with a copy of all attribute values (as strings) */ + for (attr_index = 0; attr_index < sk_X509_ATTRIBUTE_num(sda); attr_index++) { + X509_ATTRIBUTE *attribute = sk_X509_ATTRIBUTE_value(sda, attr_index); + if(pdaNID == OBJ_obj2nid(X509_ATTRIBUTE_get0_object(attribute))) { + + num_values = X509_ATTRIBUTE_count(attribute); + if(num_values > 0) { + if(array == NULL) { + array = apr_array_make(p, num_values, sizeof(char *)); + } + + for (value_index = 0; value_index < num_values; value_index++) { + ASN1_TYPE *type = X509_ATTRIBUTE_get0_type(attribute, value_index); + if(type->type == V_ASN1_GENERALIZEDTIME) { // i.e: dateOfBirth + ASN1_GENERALIZEDTIME *val = (ASN1_GENERALIZEDTIME *)X509_ATTRIBUTE_get0_data(attribute, value_index, type->type, NULL); + char **ptr = apr_array_push(array); + *ptr = apr_pstrdup(p, val->data); + } else { // ASN1_STRING + ASN1_STRING *val = (ASN1_STRING *)X509_ATTRIBUTE_get0_data(attribute, value_index, type->type, NULL); + char **ptr = apr_array_push(array); + *ptr = apr_pstrdup(p, val->data); + } + } + } + + } + } + + ERR_clear_error(); +#endif + + return array; +} + +apr_array_header_t *ssl_ext_subject_dir_attr_lookup(apr_pool_t *p, X509 *xs, int pdaNID) +{ + apr_array_header_t *array = NULL; + void *sda_object_ptr = NULL; + + /* Check certificate */ + if(xs == NULL) { + return NULL; + } + + /* Check "Subject Directory Attributes" */ + sda_object_ptr = ssl_ext_get_subject_dir_attrs_object_ptr(xs); + if(sda_object_ptr == NULL) { + return NULL; + } + + array = ssl_ext_get_subject_dir_attr_values_by_NID(p, sda_object_ptr, pdaNID); + + ssl_ext_free_subject_dir_attrs_object_ptr(sda_object_ptr); + + return array; +} + + static char *ssl_var_lookup_ssl_compress_meth(SSL *ssl) { char *result = "NULL"; --- httpd-trunk/modules/ssl/ssl_engine_kernel.c (revision 793376) +++ httpd-trunk/modules/ssl/ssl_engine_kernel.c (working copy) @@ -1133,6 +1133,64 @@ } } + /* + * On-demand subject directory attributes + */ + if (dc->nOptions & SSL_OPT_SUBJECTDIRATTR) { + apr_array_header_t *array; + X509 *peercert = SSL_get_peer_certificate(ssl); + void *sda_object_ptr = ssl_ext_get_subject_dir_attrs_object_ptr(peercert); + + if(sda_object_ptr != NULL) { + + array = ssl_ext_get_subject_dir_attr_values_by_NID(r->pool, sda_object_ptr, NID_id_pda_dateOfBirth); + if( (array != NULL) && (array->nelts > 0) ) { + char **elts = (char **) array->elts; + val = elts[0]; + apr_table_setn(env, "SSL_CLIENT_EXT_SDA_DATEOFBIRTH", val); + apr_table_setn(env, "SSL_CLIENT_EXT_SDA_AGE", get_age_from_generalizedtime_str(r->pool, val)); + } + + array = ssl_ext_get_subject_dir_attr_values_by_NID(r->pool, sda_object_ptr, NID_id_pda_gender); + if( (array != NULL) && (array->nelts > 0) ) { + char **elts = (char **) array->elts; + val = elts[0]; + apr_table_setn(env, "SSL_CLIENT_EXT_SDA_GENDER", val); + } + + array = ssl_ext_get_subject_dir_attr_values_by_NID(r->pool, sda_object_ptr, NID_id_pda_placeOfBirth); + if( (array != NULL) && (array->nelts > 0) ) { + char **elts = (char **) array->elts; + val = elts[0]; + apr_table_setn(env, "SSL_CLIENT_EXT_SDA_PLACEOFBIRTH", val); + } + + array = ssl_ext_get_subject_dir_attr_values_by_NID(r->pool, sda_object_ptr, NID_id_pda_countryOfResidence); + if( (array != NULL) && (array->nelts > 0) ) { + for(i = 0; i < array->nelts; i++) { + char **elts = (char **) array->elts; + val = elts[i]; + var = apr_psprintf(r->pool, "SSL_CLIENT_EXT_SDA_COUNTRYOFRESIDENCE_%d", i); + apr_table_setn(env, var, val); + } + } + + array = ssl_ext_get_subject_dir_attr_values_by_NID(r->pool, sda_object_ptr, NID_id_pda_countryOfCitizenship); + if( (array != NULL) && (array->nelts > 0) ) { + for(i = 0; i < array->nelts; i++) { + char **elts = (char **) array->elts; + val = elts[i]; + var = apr_psprintf(r->pool, "SSL_CLIENT_EXT_SDA_COUNTRYOFCITIZENSHIP_%d", i); + apr_table_setn(env, var, val); + } + } + + ssl_ext_free_subject_dir_attrs_object_ptr(sda_object_ptr); + X509_free(peercert); + } + + } + return DECLINED; }