From 029a080e20446744ffebdc5f6fe091c93736f868 Mon Sep 17 00:00:00 2001 From: Marcel Poul Date: Thu, 23 Aug 2012 13:08:22 +0000 Subject: [PATCH] grst_x509.c with adoption of caNl --- org.gridsite.core/src/grst_canl_x509.c | 2661 ++++++++++++++++++++++++++++++++ 1 file changed, 2661 insertions(+) create mode 100644 org.gridsite.core/src/grst_canl_x509.c diff --git a/org.gridsite.core/src/grst_canl_x509.c b/org.gridsite.core/src/grst_canl_x509.c new file mode 100644 index 0000000..41f0597 --- /dev/null +++ b/org.gridsite.core/src/grst_canl_x509.c @@ -0,0 +1,2661 @@ +/* + Copyright (c) 2002-10, Andrew McNab, University of Manchester + All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + o Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + o Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + --------------------------------------------------------------- + For more information about GridSite: http://www.gridsite.org/ + --------------------------------------------------------------- +*/ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef GRST_NO_OPENSSL +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include + +#include "gridsite.h" + +#define GRST_KEYSIZE 512 +#define GRST_PROXYCACHE "/../proxycache/" +#define GRST_MAX_CHAIN_LEN 9 +#define GRST_BACKDATE_SECONDS 300 + +/// Compare X509 Distinguished Name strings +int GRSTx509NameCmp(char *a, char *b) +/// +/// This function attempts to do with string representations what +/// would ideally be done with OIDs/values. In particular, we equate +/// "/Email=" == "/emailAddress=" to deal with this important change +/// between OpenSSL 0.9.6 and 0.9.7. +/// Other than that, it is currently the same as ordinary strcasecmp(3) +/// (for consistency with EDG/LCG/EGEE gridmapdir case insensitivity.) +{ + int ret; + char *aa, *bb, *p; + + if ((a == NULL) || (b == NULL)) return 1; /* NULL never matches */ + + aa = strdup(a); + while ((p = strstr(aa, "/emailAddress=")) != NULL) + { + memmove(&p[6], &p[13], strlen(&p[13]) + 1); + p[1] = 'E'; + } + + bb = strdup(b); + while ((p = strstr(bb, "/emailAddress=")) != NULL) + { + memmove(&p[6], &p[13], strlen(&p[13]) + 1); + p[1] = 'E'; + } + + ret = strcasecmp(aa, bb); + + free(aa); + free(bb); + + return ret; +} + + +/// Check critical extensions +int GRSTx509KnownCriticalExts(X509 *cert) +/// +/// Returning GRST_RET_OK if all of extensions are known to us or +/// OpenSSL; GRST_REF_FAILED otherwise. +/// +/// Since this function relies on functionality (X509_supported_extension) +/// introduced in 0.9.7, then we do nothing and report an error +/// (GRST_RET_FAILED) if one of the associated defines +/// (X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION) is absent. +{ + int i; + char s[80]; + X509_EXTENSION *ex; + +#ifdef X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION + for (i = 0; i < X509_get_ext_count(cert); ++i) + { + ex = X509_get_ext(cert, i); + + if (X509_EXTENSION_get_critical(ex) && + !X509_supported_extension(ex)) + { + OBJ_obj2txt(s, sizeof(s), X509_EXTENSION_get_object(ex), 1); + + if ((strcmp(s, GRST_PROXYCERTINFO_OID) != 0) && + (strcmp(s, GRST_PROXYCERTINFO_OLD_OID) != 0)) + return GRST_RET_FAILED; + } + } + + return GRST_RET_OK; +#else + return GRST_RET_FAILED; +#endif +} + +/// Check if certificate can be used as a CA to sign standard X509 certs +int GRSTx509IsCA(X509 *cert) +/// +/// Return GRST_RET_OK if true; GRST_RET_FAILED if not. +{ + int idret, purpose_id; + + purpose_id = X509_PURPOSE_get_by_sname("sslclient"); + + /* final argument to X509_check_purpose() is whether to check for CAness */ + + if (X509_check_purpose(cert, purpose_id + X509_PURPOSE_MIN, 1)) + return GRST_RET_OK; + else return GRST_RET_FAILED; +} + +int GRSTx509ChainFree(GRSTx509Chain *chain) +{ + GRSTx509Cert *grst_cert, *next_grst_cert; + + if (chain == NULL) return GRST_RET_OK; + + next_grst_cert = chain->firstcert; + + while (next_grst_cert != NULL) + { + grst_cert = next_grst_cert; + + if (grst_cert->issuer != NULL) free(grst_cert->issuer); + if (grst_cert->dn != NULL) free(grst_cert->dn); + if (grst_cert->value != NULL) free(grst_cert->value); + if (grst_cert->ocsp != NULL) free(grst_cert->ocsp); + + next_grst_cert = grst_cert->next; + free(grst_cert); + } + + free(chain); + + return GRST_RET_OK; +} + +/// Check a specific signature against a specific (VOMS) cert +static int GRSTx509VerifySig(time_t *time1_time, time_t *time2_time, + unsigned char *txt, int txt_len, + unsigned char *sig, int sig_len, + X509 *cert, EVP_MD *md_type) +/// +/// Returns GRST_RET_OK if signature is ok, other values if not. +{ + int ret; + EVP_PKEY *prvkey; + EVP_MD_CTX ctx; + time_t voms_service_time1, voms_service_time2; + + prvkey = X509_extract_key(cert); + if (prvkey == NULL) return GRST_RET_FAILED; + + OpenSSL_add_all_digests(); +#if OPENSSL_VERSION_NUMBER >= 0x0090701fL + EVP_MD_CTX_init(&ctx); + EVP_VerifyInit_ex(&ctx, md_type, NULL); +#else + EVP_VerifyInit(&ctx, md_type); +#endif + + EVP_VerifyUpdate(&ctx, txt, txt_len); + + ret = EVP_VerifyFinal(&ctx, sig, sig_len, prvkey); + +#if OPENSSL_VERSION_NUMBER >= 0x0090701fL + EVP_MD_CTX_cleanup(&ctx); +#endif + EVP_PKEY_free(prvkey); + + if (ret != 1) return GRST_RET_FAILED; + + voms_service_time1 = + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(cert)),0); + if (voms_service_time1 > *time1_time) + *time1_time = voms_service_time1; + + voms_service_time2 = + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(cert)),0); + if (voms_service_time2 < *time1_time) + *time2_time = voms_service_time2; + + return GRST_RET_OK ; /* verified */ +} + +/// Check the signature of the VOMS attributes +static int GRSTx509VerifyVomsSig(time_t *time1_time, time_t *time2_time, + unsigned char *asn1string, + struct GRSTasn1TagList taglist[], + int lasttag, + char *vomsdir, int acnumber) +/// +/// Returns GRST_RET_OK if signature is ok, other values if not. +{ +#define GRST_ASN1_COORDS_VOMS_DN "-1-1-%d-1-3-1-1-1-%%d-1-%%d" +#define GRST_ASN1_COORDS_VOMS_INFO "-1-1-%d-1" +#define GRST_ASN1_COORDS_VOMS_HASH "-1-1-%d-2-1" +#define GRST_ASN1_COORDS_VOMS_SIG "-1-1-%d-3" + int ret, ihash, isig, iinfo; + char *certpath, *certpath2, acvomsdn[200], dn_coords[200], + info_coords[200], sig_coords[200], hash_coords[200], + *p; + unsigned char *q; + DIR *vomsDIR, *vomsDIR2; + struct dirent *vomsdirent, *vomsdirent2; + X509 *cert; + EVP_PKEY *prvkey; + FILE *fp; + EVP_MD_CTX ctx; + EVP_MD *md_type = NULL; + struct stat statbuf; + time_t voms_service_time1 = GRST_MAX_TIME_T, voms_service_time2 = 0, + tmp_time1, tmp_time2; + ASN1_OBJECT *hash_obj = NULL; + + if ((vomsdir == NULL) || (vomsdir[0] == '\0')) return GRST_RET_FAILED; + + snprintf(dn_coords, sizeof(dn_coords), + GRST_ASN1_COORDS_VOMS_DN, acnumber); + + if (GRSTasn1GetX509Name(acvomsdn, sizeof(acvomsdn), dn_coords, + asn1string, taglist, lasttag) != GRST_RET_OK) return GRST_RET_FAILED; + + snprintf(info_coords, sizeof(info_coords), + GRST_ASN1_COORDS_VOMS_INFO, acnumber); + iinfo = GRSTasn1SearchTaglist(taglist, lasttag, info_coords); + + snprintf(hash_coords, sizeof(hash_coords), + GRST_ASN1_COORDS_VOMS_HASH, acnumber); + ihash = GRSTasn1SearchTaglist(taglist, lasttag, hash_coords); + + snprintf(sig_coords, sizeof(sig_coords), + GRST_ASN1_COORDS_VOMS_SIG, acnumber); + isig = GRSTasn1SearchTaglist(taglist, lasttag, sig_coords); + + if ((iinfo < 0) || (ihash < 0) || (isig < 0)) return GRST_RET_FAILED; + + /* determine hash algorithm's type */ + + p = &asn1string[taglist[ihash].start]; + + d2i_ASN1_OBJECT(&hash_obj, &p, + taglist[ihash].length+taglist[ihash].headerlength); + + md_type = EVP_get_digestbyname(OBJ_nid2sn(OBJ_obj2nid(hash_obj))); + + if (md_type == NULL) return GRST_RET_FAILED; + + + vomsDIR = opendir(vomsdir); + if (vomsDIR == NULL) return GRST_RET_FAILED; + + while ((vomsdirent = readdir(vomsDIR)) != NULL) + { + if (vomsdirent->d_name[0] == '.') continue; + + asprintf(&certpath, "%s/%s", vomsdir, vomsdirent->d_name); + stat(certpath, &statbuf); + + if (S_ISDIR(statbuf.st_mode)) + { + vomsDIR2 = opendir(certpath); + GRSTerrorLog(GRST_LOG_DEBUG, + "Descend VOMS subdirectory %s", certpath); + free(certpath); + + if (vomsDIR2 == NULL) continue; + + while ((vomsdirent2 = readdir(vomsDIR2)) != NULL) + { + if (vomsdirent2->d_name[0] == '.') continue; + + asprintf(&certpath2, "%s/%s/%s", + vomsdir, vomsdirent->d_name, vomsdirent2->d_name); + + fp = fopen(certpath2, "r"); + GRSTerrorLog(GRST_LOG_DEBUG, + "Examine VOMS cert %s", certpath2); + free(certpath2); + if (fp == NULL) continue; + + cert = PEM_read_X509(fp, NULL, NULL, NULL); + fclose(fp); + if (cert == NULL) continue; + + tmp_time1 = 0; + tmp_time2 = GRST_MAX_TIME_T; + + if (GRSTx509VerifySig(&tmp_time1, &tmp_time2, + &asn1string[taglist[iinfo].start], + taglist[iinfo].length+taglist[iinfo].headerlength, + &asn1string[taglist[isig].start+ + taglist[isig].headerlength+1], + taglist[isig].length - 1, + cert, md_type) == GRST_RET_OK) + { + GRSTerrorLog(GRST_LOG_DEBUG, "Matched VOMS cert file %s", vomsdirent2->d_name); + + /* Store more permissive time ranges for now */ + + if (tmp_time1 < voms_service_time1) + voms_service_time1 = tmp_time1; + + if (tmp_time2 > voms_service_time2) + voms_service_time2 = tmp_time2; + } + + X509_free(cert); + } + + closedir(vomsDIR2); + } + else + { + fp = fopen(certpath, "r"); + GRSTerrorLog(GRST_LOG_DEBUG, "Examine VOMS cert %s", certpath); + free(certpath); + if (fp == NULL) continue; + + cert = PEM_read_X509(fp, NULL, NULL, NULL); + fclose(fp); + if (cert == NULL) continue; + + tmp_time1 = 0; + tmp_time2 = GRST_MAX_TIME_T; + + if (GRSTx509VerifySig(&tmp_time1, &tmp_time2, + &asn1string[taglist[iinfo].start], + taglist[iinfo].length+taglist[iinfo].headerlength, + &asn1string[taglist[isig].start+ + taglist[isig].headerlength+1], + taglist[isig].length - 1, + cert, md_type) == GRST_RET_OK) + { + GRSTerrorLog(GRST_LOG_DEBUG, "Matched VOMS cert file %s", vomsdirent->d_name); + + /* Store more permissive time ranges for now */ + + if (tmp_time1 < voms_service_time1) + voms_service_time1 = tmp_time1; + + if (tmp_time2 > voms_service_time2) + voms_service_time2 = tmp_time2; + } + + X509_free(cert); + } + } + + closedir(vomsDIR); + + if ((voms_service_time1 == GRST_MAX_TIME_T) || (voms_service_time2 == 0)) + return GRST_RET_FAILED; + + /* now we tighten up the VOMS AC time range using the most permissive + pair of VOMS certificate ranges (in case of multiple, possibly + overlapping, matching certificates in the VOMS certs store) */ + + if (voms_service_time1 > *time1_time) *time1_time = voms_service_time1; + + if (voms_service_time2 < *time2_time) *time2_time = voms_service_time2; + + return GRST_RET_OK; +} + +/// Check the signature of the VOMS attributes using the LSC file cert +static int GRSTx509VerifyVomsSigCert(time_t *time1_time, time_t *time2_time, + unsigned char *asn1string, + struct GRSTasn1TagList taglist[], + int lasttag, + char *vomsdir, int acnumber, + int ivomscert, + char *capath, + char *acvomsdn, + char *voname) +/// +/// Returns GRST_RET_OK if signature is ok, other values if not. +{ +#define GRST_ASN1_COORDS_VOMS_DN "-1-1-%d-1-3-1-1-1-%%d-1-%%d" +#define GRST_ASN1_COORDS_VOMS_INFO "-1-1-%d-1" +#define GRST_ASN1_COORDS_VOMS_SIG "-1-1-%d-3" + int ret, isig, iinfo, chain_errors = GRST_RET_OK, + cadn_len, vomsdn_len, lsc_found = 0, ihash; + char *lscpath, dn_coords[200], + info_coords[200], sig_coords[200], *p, *cacertpath, + *vodir, *vomscert_cadn, *vomscert_vomsdn, + *lsc_cadn, *lsc_vomsdn; + unsigned char *q; + unsigned long issuerhash = 0; + DIR *voDIR; + struct dirent *vodirent; + X509 *cacert = NULL, *vomscert = NULL; + EVP_PKEY *prvkey; + FILE *fp; + EVP_MD_CTX ctx; + struct stat statbuf; + time_t tmp_time; + ASN1_OBJECT *hash_obj = NULL; + char coords[200]; + EVP_MD *md_type = NULL; + time_t voms_service_time1, voms_service_time2; + + if ((vomsdir == NULL) || (vomsdir[0] == '\0')) return GRST_RET_FAILED; + + q = &asn1string[taglist[ivomscert].start + 12]; + + vomscert = d2i_X509(NULL, (const unsigned char **) &q, + taglist[ivomscert].length - 8); + + if (vomscert == NULL) + { + GRSTerrorLog(GRST_LOG_DEBUG, "Failed to read included VOMS cert in GRSTx509VerifyVomsSigCert()"); + return GRST_RET_FAILED; + } + + GRSTerrorLog(GRST_LOG_DEBUG, "Found included VOMS cert in GRSTx509VerifyVomsSigCert()"); + + snprintf(coords, sizeof(coords), GRST_ASN1_COORDS_VOMS_INFO, acnumber); + iinfo = GRSTasn1SearchTaglist(taglist, lasttag, coords); + + snprintf(coords, sizeof(coords), GRST_ASN1_COORDS_VOMS_HASH, acnumber); + ihash = GRSTasn1SearchTaglist(taglist, lasttag, coords); + + snprintf(coords, sizeof(coords), GRST_ASN1_COORDS_VOMS_SIG, acnumber); + isig = GRSTasn1SearchTaglist(taglist, lasttag, coords); + + if ((iinfo < 0) || (ihash < 0) || (isig < 0)) return GRST_RET_FAILED; + + q = &asn1string[taglist[ihash].start]; + d2i_ASN1_OBJECT(&hash_obj, &q, + taglist[ihash].length+taglist[ihash].headerlength); + + md_type = EVP_get_digestbyname(OBJ_nid2sn(OBJ_obj2nid(hash_obj))); + if (md_type == NULL) return GRST_RET_FAILED; + + /* check issuer CA certificate */ + + issuerhash = X509_NAME_hash(X509_get_issuer_name(vomscert)); + asprintf(&cacertpath, "%s/%.8x.0", capath, issuerhash); + + /* check voms cert DN matches DN from AC */ + + vomscert_vomsdn = X509_NAME_oneline(X509_get_subject_name(vomscert),NULL,0); + + if (GRSTx509NameCmp(vomscert_vomsdn, acvomsdn) != 0) + { + free(vomscert_vomsdn); + + GRSTerrorLog(GRST_LOG_DEBUG, "Included VOMS cert DN does not match AC issuer DN!"); + return GRST_RET_FAILED; + } + + free(vomscert_vomsdn); + + GRSTerrorLog(GRST_LOG_DEBUG, "Look for CA root file %s", cacertpath); + + fp = fopen(cacertpath, "r"); + free(cacertpath); + + if (fp == NULL) return GRST_RET_FAILED; + else + { + cacert = PEM_read_X509(fp, NULL, NULL, NULL); + fclose(fp); + if (cacert != NULL) + { + GRSTerrorLog(GRST_LOG_DEBUG, " Loaded CA root cert from file"); + } + else + { + GRSTerrorLog(GRST_LOG_DEBUG, " Failed to load CA root cert file"); + return GRST_RET_FAILED; + } + } + + /* check times CA cert times, and reject if necessary */ + + tmp_time = GRSTasn1TimeToTimeT( + ASN1_STRING_data(X509_get_notBefore(cacert)), 0); + if (tmp_time > *time1_time) chain_errors |= GRST_CERT_BAD_TIME; + + tmp_time = GRSTasn1TimeToTimeT( + ASN1_STRING_data(X509_get_notAfter(cacert)), 0); + if (tmp_time < *time2_time) chain_errors |= GRST_CERT_BAD_TIME; + + /* check times VOMS cert times, and tighten if necessary */ + + tmp_time = GRSTasn1TimeToTimeT( + ASN1_STRING_data(X509_get_notBefore(vomscert)), 0); + if (tmp_time > *time1_time) chain_errors |= GRST_CERT_BAD_TIME; + + tmp_time = GRSTasn1TimeToTimeT( + ASN1_STRING_data(X509_get_notAfter(vomscert)), 0); + if (tmp_time < *time2_time) chain_errors |= GRST_CERT_BAD_TIME; + + ret = X509_check_issued(cacert, vomscert); + GRSTerrorLog(GRST_LOG_DEBUG, "X509_check_issued returns %d", ret); + + vomscert_cadn = X509_NAME_oneline(X509_get_issuer_name(vomscert),NULL,0); + + + if (ret != X509_V_OK) return (chain_errors | GRST_CERT_BAD_SIG); + + asprintf(&vodir, "%s/%s", vomsdir, voname); + + voDIR = opendir(vodir); + if (voDIR == NULL) return GRST_RET_FAILED; + + cadn_len = strlen(vomscert_cadn); + vomsdn_len = strlen(acvomsdn); + + lsc_cadn = malloc(cadn_len + 2); + lsc_vomsdn = malloc(vomsdn_len + 2); + + while (((vodirent = readdir(voDIR)) != NULL) && !lsc_found) + { + if (vodirent->d_name[0] == '.') continue; + + if (strlen(vodirent->d_name) < 5) continue; + + if (strcmp(&(vodirent->d_name[strlen(vodirent->d_name)-4]), ".lsc") != 0) continue; + + asprintf(&lscpath, "%s/%s", vodir, vodirent->d_name); + stat(lscpath, &statbuf); + + GRSTerrorLog(GRST_LOG_DEBUG, "Check LSC file %s for %s,%s", + lscpath, acvomsdn, vomscert_cadn); + + if ((fp = fopen(lscpath, "r")) != NULL) + { + lsc_cadn[0] = '\0'; + lsc_vomsdn[0] = '\0'; + + if ((fgets(lsc_vomsdn, vomsdn_len + 2, fp) != NULL) + && (fgets(lsc_cadn, cadn_len + 2, fp) != NULL)) + { + if ((p = index(lsc_cadn, '\n')) != NULL) *p = '\0'; + + if ((p = index(lsc_vomsdn, '\n')) != NULL) *p = '\0'; + + if ((GRSTx509NameCmp(lsc_cadn, vomscert_cadn) == 0) && + (GRSTx509NameCmp(lsc_vomsdn, acvomsdn) == 0)) + { + GRSTerrorLog(GRST_LOG_DEBUG, "Matched LSC file %s", lscpath); + lsc_found = 1; + } + } + + fclose(fp); + } + + free(lscpath); + } + + closedir(voDIR); + free(vodir); + free(vomscert_cadn); + free(lsc_cadn); + free(lsc_vomsdn); + + if (!lsc_found) { + chain_errors |= GRST_CERT_BAD_SIG; + goto end; + } + + ret = GRSTx509VerifySig(&voms_service_time1, &voms_service_time2, + &asn1string[taglist[iinfo].start], + taglist[iinfo].length+taglist[iinfo].headerlength, + &asn1string[taglist[isig].start+ + taglist[isig].headerlength+1], + taglist[isig].length - 1, + vomscert, md_type); + if (ret != GRST_RET_OK) { + chain_errors |= GRST_CERT_BAD_SIG; + goto end; + } + + if (voms_service_time1 > *time1_time) *time1_time = voms_service_time1; + if (voms_service_time2 < *time2_time) *time2_time = voms_service_time2; + +end: + if (cacert) + X509_free(cacert); + if (vomscert) + X509_free(vomscert); + + return (chain_errors ? GRST_RET_FAILED : GRST_RET_OK); +} + +/// Get the VOMS attributes in the given extension +static int GRSTx509ChainVomsAdd(GRSTx509Cert **grst_cert, + time_t time1_time, time_t time2_time, + int delegation, + X509_EXTENSION *ex, + GRSTx509Cert *user_cert, char *vomsdir, char *capath) +/// +/// Add any VOMS credentials found into the chain. Always returns GRST_RET_OK +/// - even for invalid credentials, which are flagged in errors field +{ +#define MAXTAG 500 +#define GRST_ASN1_COORDS_FQAN "-1-1-%d-1-7-1-2-1-2-%d" +#define GRST_ASN1_COORDS_ISSUER_DN "-1-1-%d-1-2-1-1-1-1-%%d-1-%%d" +#define GRST_ASN1_COORDS_ISSUER_SERIAL "-1-1-%d-1-2-1-2" +#define GRST_ASN1_COORDS_VOMS_DN "-1-1-%d-1-3-1-1-1-%%d-1-%%d" +#define GRST_ASN1_COORDS_TIME1 "-1-1-%d-1-6-1" +#define GRST_ASN1_COORDS_TIME2 "-1-1-%d-1-6-2" +#define GRST_ASN1_COORDS_VOMSCERT "-1-1-%d-1-8-%%d-%%d" + + ASN1_OCTET_STRING *asn1data; + char *asn1string, acissuerdn[200], acvomsdn[200], + dn_coords[200], fqan_coords[200], time1_coords[200], + time2_coords[200], vomscert_coords[200], *voname = NULL, + serial_coords[200]; + long asn1length; + int lasttag=-1, itag, i, j, acnumber = 1, chain_errors = 0, + ivomscert, tmp_chain_errors, ret; + char *acissuerserial = NULL; + struct GRSTasn1TagList taglist[MAXTAG+1]; + time_t actime1 = 0, actime2 = 0, time_now, + tmp_time1, tmp_time2; + ASN1_INTEGER acissuerserialASN1; + + asn1data = X509_EXTENSION_get_data(ex); + asn1string = ASN1_STRING_data(asn1data); + asn1length = ASN1_STRING_length(asn1data); + + GRSTasn1ParseDump(NULL, asn1string, asn1length, taglist, MAXTAG, &lasttag); + + for (acnumber = 1; ; ++acnumber) /* go through ACs one by one */ + { + chain_errors = 0; + + snprintf(dn_coords, sizeof(dn_coords), GRST_ASN1_COORDS_ISSUER_DN, acnumber); + if (GRSTasn1GetX509Name(acissuerdn, sizeof(acissuerdn), dn_coords, + asn1string, taglist, lasttag) != GRST_RET_OK) + break; + + snprintf(dn_coords, sizeof(dn_coords), GRST_ASN1_COORDS_VOMS_DN, acnumber); + if (GRSTasn1GetX509Name(acvomsdn, sizeof(acvomsdn), dn_coords, + asn1string, taglist, lasttag) != GRST_RET_OK) + break; + + if ((GRSTx509NameCmp(user_cert->dn, acissuerdn) != 0) && /* old */ + (GRSTx509NameCmp(user_cert->issuer, acissuerdn) != 0)) /* new */ + chain_errors |= GRST_CERT_BAD_CHAIN; + + /* check serial numbers */ + + snprintf(serial_coords, sizeof(serial_coords), + GRST_ASN1_COORDS_ISSUER_SERIAL, acnumber); + + itag = GRSTasn1SearchTaglist(taglist, lasttag, serial_coords); + + if (itag > -1) + { + acissuerserialASN1.length = taglist[itag].length; + acissuerserialASN1.type = V_ASN1_INTEGER; + acissuerserialASN1.data = &asn1string[taglist[itag].start+taglist[itag].headerlength]; + + acissuerserial = i2s_ASN1_INTEGER(NULL, &acissuerserialASN1); +/* + p = &asn1string[taglist[itag].start+taglist[itag].headerlength]; + + if (taglist[itag].length == 2) + acissuerserial = p[1] + p[0] * 0x100; + else if (taglist[itag].length == 3) + acissuerserial = p[2] + p[1] * 0x100 + p[0] * 0x10000; + else if (taglist[itag].length == 4) + acissuerserial = p[3] + p[2] * 0x100 + p[1] * 0x10000 + + p[0] * 0x1000000; +*/ + } + + if (strcmp(acissuerserial, user_cert->serial) != 0) + chain_errors |= GRST_CERT_BAD_CHAIN; + + /* get times */ + + snprintf(time1_coords, sizeof(time1_coords), GRST_ASN1_COORDS_TIME1, acnumber); + itag = GRSTasn1SearchTaglist(taglist, lasttag, time1_coords); + + if (itag > -1) actime1 = GRSTasn1TimeToTimeT( + &asn1string[taglist[itag].start+ + taglist[itag].headerlength], + taglist[itag].length); + else actime1 = 0; + + snprintf(time2_coords, sizeof(time2_coords), GRST_ASN1_COORDS_TIME2, acnumber); + itag = GRSTasn1SearchTaglist(taglist, lasttag, time2_coords); + + if (itag > -1) actime2 = GRSTasn1TimeToTimeT( + &asn1string[taglist[itag].start+ + taglist[itag].headerlength], + taglist[itag].length); + else actime2 = 0; + + if (actime1 > time1_time) time1_time = actime1; + if (actime2 < time2_time) time2_time = actime2; + + time(&time_now); + if ((time1_time > time_now + 300) || (time2_time < time_now)) + chain_errors |= GRST_CERT_BAD_TIME; + + /* get first FQAN and use to get VO name */ + + snprintf(fqan_coords, sizeof(fqan_coords), GRST_ASN1_COORDS_FQAN, acnumber, 1); + itag = GRSTasn1SearchTaglist(taglist, lasttag, fqan_coords); + + if ((itag > -1) && + (asn1string[taglist[itag].start+taglist[itag].headerlength] == '/')) + { + for (j=1; + (asn1string[taglist[itag].start+taglist[itag].headerlength+j] != '/') && + (j < taglist[itag].length); + ++j) ; + + asprintf(&voname, "%.*s", j-1, + &(asn1string[taglist[itag].start+taglist[itag].headerlength+1])); + } + + snprintf(vomscert_coords, sizeof(vomscert_coords), + GRST_ASN1_COORDS_VOMSCERT, acnumber); + ret = GRSTasn1FindField(GRST_VOMS_PK_CERT_LIST_OID, vomscert_coords, asn1string, + taglist, lasttag, &ivomscert); + + /* try using internal VOMS issuer cert */ + tmp_chain_errors = GRST_CERT_BAD_SIG; + tmp_time1 = time1_time; + tmp_time2 = time2_time; + if ((ivomscert > -1) && + (voname != NULL) && + (GRSTx509VerifyVomsSigCert(&tmp_time1, &tmp_time2, + asn1string, taglist, lasttag, vomsdir, acnumber, + ivomscert, capath, acvomsdn, + voname) == GRST_RET_OK)) + { + tmp_chain_errors = 0; + time1_time = tmp_time1; + time2_time = tmp_time2; + } + + if (voname != NULL) + { + free(voname); + voname = NULL; + } + + if ((tmp_chain_errors != 0) && + (GRSTx509VerifyVomsSig(&time1_time, &time2_time, + asn1string, taglist, lasttag, vomsdir, acnumber) + != GRST_RET_OK)) + chain_errors |= GRST_CERT_BAD_SIG; + + for (i=1; ; ++i) /* now go through FQANs */ + { + snprintf(fqan_coords, sizeof(fqan_coords), GRST_ASN1_COORDS_FQAN, acnumber, i); + itag = GRSTasn1SearchTaglist(taglist, lasttag, fqan_coords); + + if (itag > -1) + { + (*grst_cert)->next = malloc(sizeof(GRSTx509Cert)); + *grst_cert = (*grst_cert)->next; + bzero(*grst_cert, sizeof(GRSTx509Cert)); + + (*grst_cert)->notbefore = time1_time; + (*grst_cert)->notafter = time2_time; + asprintf(&((*grst_cert)->value), "%.*s", + taglist[itag].length, + &asn1string[taglist[itag].start+ + taglist[itag].headerlength]); + + (*grst_cert)->errors = chain_errors; /* ie may be invalid */ + (*grst_cert)->type = GRST_CERT_TYPE_VOMS; + (*grst_cert)->issuer = strdup(acvomsdn); + (*grst_cert)->dn = strdup(user_cert->dn); + (*grst_cert)->delegation = delegation; + } + else break; + } + } + + return GRST_RET_OK; +} + +/// Check certificate chain for GSI proxy acceptability. +int GRSTx509ChainLoadCheck(GRSTx509Chain **chain, + STACK_OF(X509) *certstack, X509 *lastcert, + char *capath, char *vomsdir) +/// +/// Returns GRST_RET_OK if valid; OpenSSL X509 errors otherwise. +/// +/// The GridSite version handles old and new style Globus proxies, and +/// proxies derived from user certificates issued with "X509v3 Basic +/// Constraints: CA:FALSE" (eg UK e-Science CA) +/// +/// TODO: we do not yet check ProxyCertInfo and ProxyCertPolicy extensions +/// (although via GRSTx509KnownCriticalExts() we can accept them.) +{ + X509 *cert; /* Points to the current cert in the loop */ + X509 *cacert = NULL; /* The CA root cert */ + int depth = 0; /* Depth of cert chain */ + int chain_errors = 0; /* records previous errors */ + int first_non_ca; /* number of the EEC issued to user by CA */ + size_t len,len2; /* Lengths of issuer and cert DN */ + int IsCA; /* Holds whether cert is allowed to sign */ + int prevIsCA; /* Holds whether previous cert in chain is + allowed to sign */ + int prevIsLimited; /* previous cert was proxy and limited */ + int i,j,ret; /* Iteration/temp variables */ + char *proxy_part_DN; /* Pointer to end part of current-cert-in-chain + maybe eg "/CN=proxy" */ + char s[80], *p; + char *cacertpath; + unsigned long subjecthash = 0; /* hash of the name of first cert */ + unsigned long issuerhash = 0; /* hash of issuer name of first cert */ + FILE *fp; + X509_EXTENSION *ex; + time_t now; + GRSTx509Cert *grst_cert, *new_grst_cert, *user_cert = NULL; + + GRSTerrorLog(GRST_LOG_DEBUG, "GRSTx509ChainLoadCheck() starts"); + + time(&now); + + first_non_ca = 0; /* set to something predictable if things fail */ + + /* Set necessary preliminary values */ + IsCA = TRUE; /* =prevIsCA - start from a CA */ + prevIsLimited = 0; + + /* Get the client cert chain */ + if (certstack != NULL) + depth = sk_X509_num(certstack); /* How deep is that chain? */ + + if ((depth == 0) && (lastcert == NULL)) + { + *chain = NULL; + return GRST_RET_FAILED; + } + + cert = sk_X509_value(certstack, depth - 1); + subjecthash = X509_NAME_hash(X509_get_subject_name(cert)); + issuerhash = X509_NAME_hash(X509_get_issuer_name(cert)); + asprintf(&cacertpath, "%s/%.8x.0", capath, issuerhash); + + GRSTerrorLog(GRST_LOG_DEBUG, "Look for CA root file %s", cacertpath); + + fp = fopen(cacertpath, "r"); + free(cacertpath); + + if (fp == NULL) chain_errors |= GRST_CERT_BAD_CHAIN; + else + { + cacert = PEM_read_X509(fp, NULL, NULL, NULL); + fclose(fp); + if (cacert != NULL) + GRSTerrorLog(GRST_LOG_DEBUG, " Loaded CA root cert from file"); + else + GRSTerrorLog(GRST_LOG_DEBUG, " Failed to load CA root cert file"); + } + + *chain = malloc(sizeof(GRSTx509Chain)); + bzero(*chain, sizeof(GRSTx509Chain)); + + /* Check the client chain */ + for (i = depth - ((subjecthash == issuerhash) ? 1 : 0); + i >= ((lastcert == NULL) ? 0 : -1); + --i) + /* loop through client-presented chain starting at CA end */ + { + GRSTerrorLog(GRST_LOG_DEBUG, "Process cert at depth %d in chain", i); + + prevIsCA=IsCA; + + new_grst_cert = malloc(sizeof(GRSTx509Cert)); + bzero(new_grst_cert, sizeof(GRSTx509Cert)); + new_grst_cert->errors = chain_errors; + + if ((*chain)->firstcert == NULL) + { + GRSTerrorLog(GRST_LOG_DEBUG, "Initialise chain"); + (*chain)->firstcert = new_grst_cert; + } + else grst_cert->next = new_grst_cert; + + grst_cert = new_grst_cert; + + /* Choose X509 certificate and point to it with 'cert' */ + if (i < 0) cert = lastcert; + else if (i == depth) + cert = cacert; /* the self-signed CA from the store*/ + else if ((i == depth - 1) && (subjecthash == issuerhash)) + cert = cacert; /* ie claims to be a copy of a self-signed CA */ + else cert = sk_X509_value(certstack, i); + + if (cert != NULL) + { + if ((i == depth - 1) && (subjecthash != issuerhash)) + { + /* if first cert does not claim to be a self-signed copy + of a CA root cert in the store, we check the signature */ + + if (cacert == NULL) + { + chain_errors |= GRST_CERT_BAD_CHAIN; + ret = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT; + } + else + { + ret = X509_check_issued(cacert, cert); + + GRSTerrorLog(GRST_LOG_DEBUG, + "Cert sig check %d returns %d", i, ret); + + if (ret != X509_V_OK) + new_grst_cert->errors |= GRST_CERT_BAD_SIG; + } + } + else if ((i == depth - 2) && (subjecthash == issuerhash)) + { + /* first cert claimed to be a self-signed copy of a CA root + cert in the store, we check the signature of the second + cert, using OUR copy of the CA cert DIRECT from the store */ + + if (cacert == NULL) + { + chain_errors |= GRST_CERT_BAD_CHAIN; + ret = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT; + } + else + { + ret = X509_check_issued(cacert, cert); + + GRSTerrorLog(GRST_LOG_DEBUG, + "Cert sig check %d returns %d", i, ret); + + if (ret != X509_V_OK) + new_grst_cert->errors |= GRST_CERT_BAD_SIG; + } + } + else if (i < depth - 1) + { + /* otherwise a normal part of the chain: note that if the + first cert claims to be a self-signed copy of a CA root + cert in the store, we never use it for sig checking */ + + ret = X509_check_issued(sk_X509_value(certstack, i + 1), cert); + + GRSTerrorLog(GRST_LOG_DEBUG, + "Cert sig check %d returns %d", i, ret); + + if ((ret != X509_V_OK) && + (ret != X509_V_ERR_KEYUSAGE_NO_CERTSIGN)) + new_grst_cert->errors |= GRST_CERT_BAD_SIG; + + /* NO_CERTSIGN can still be ok due to Proxy Certificates */ + } + + p = i2s_ASN1_INTEGER(NULL, X509_get_serialNumber(cert)); + strncpy(new_grst_cert->serial, p, GRST_X509_SERIAL_DIGITS); + new_grst_cert->serial[GRST_X509_SERIAL_DIGITS] = '\0'; + free(p); + + new_grst_cert->notbefore = GRSTasn1TimeToTimeT( + ASN1_STRING_data(X509_get_notBefore(cert)), 0); + new_grst_cert->notafter = GRSTasn1TimeToTimeT( + ASN1_STRING_data(X509_get_notAfter(cert)), 0); + + /* we check times and record if invalid */ + + if (now < new_grst_cert->notbefore) + new_grst_cert->errors |= GRST_CERT_BAD_TIME; + + if (now > new_grst_cert->notafter) + new_grst_cert->errors |= GRST_CERT_BAD_TIME; + + new_grst_cert->dn = X509_NAME_oneline(X509_get_subject_name(cert),NULL,0); + new_grst_cert->issuer = X509_NAME_oneline(X509_get_issuer_name(cert),NULL,0); + len = strlen(new_grst_cert->dn); + len2 = strlen(new_grst_cert->issuer); + + /* always treat a first cert from the CA files as a + CA: this is really for lousy CAs that dont create + proper v3 root certificates */ + + if (i == depth) IsCA == TRUE; + else IsCA = (GRSTx509IsCA(cert) == GRST_RET_OK); + + /* If any forebear certificate is not allowed to sign we must + assume all decendents are proxies and cannot sign either */ + if (prevIsCA) + { + if (IsCA) + { + new_grst_cert->type = GRST_CERT_TYPE_CA; + } + else + { + new_grst_cert->type = GRST_CERT_TYPE_EEC; + first_non_ca = i; + user_cert = new_grst_cert; + new_grst_cert->delegation + = (lastcert == NULL) ? i : i + 1; + } + } + else + { + new_grst_cert->type = GRST_CERT_TYPE_PROXY; + + IsCA = FALSE; + /* Force proxy check next iteration. Important because I can + sign any CA I create! */ + + new_grst_cert->delegation = (lastcert == NULL) ? i : i + 1; + } + + if (!prevIsCA) + { + /* issuer didn't have CA status, so this is (at best) a proxy: + check for bad proxy extension*/ + + if (prevIsLimited) /* we reject proxies of limited proxies! */ + { + new_grst_cert->errors |= GRST_CERT_BAD_CHAIN; + chain_errors |= GRST_CERT_BAD_CHAIN; + } + + /* User not allowed to sign shortened DN */ + if (len2 > len) + { + new_grst_cert->errors |= GRST_CERT_BAD_CHAIN; + chain_errors |= GRST_CERT_BAD_CHAIN; + } + + /* Proxy subject must begin with issuer. */ + if (strncmp(new_grst_cert->dn, new_grst_cert->issuer, len2) != 0) + { + new_grst_cert->errors |= GRST_CERT_BAD_CHAIN; + chain_errors |= GRST_CERT_BAD_CHAIN; + } + + /* Set pointer to end of base DN in cert_DN */ + proxy_part_DN = &(new_grst_cert->dn[len2]); + + /* First attempt at support for Old and New style GSI + proxies: /CN=anything is ok for now */ + if (strncmp(proxy_part_DN, "/CN=", 4) != 0) + { + new_grst_cert->errors |= GRST_CERT_BAD_CHAIN; + chain_errors |= GRST_CERT_BAD_CHAIN; + } + + if (strncmp(proxy_part_DN, "/CN=limited proxy", 17) == 0) + prevIsLimited = 1; /* ready for next cert ... */ + + for (j=0; j < X509_get_ext_count(cert); ++j) + { + ex = X509_get_ext(cert, j); + OBJ_obj2txt(s,sizeof(s),X509_EXTENSION_get_object(ex),1); + + if (strcmp(s, GRST_VOMS_OID) == 0) /* a VOMS extension */ + { + GRSTx509ChainVomsAdd(&grst_cert, + new_grst_cert->notbefore, + new_grst_cert->notafter, + (lastcert == NULL) ? i : i+1, + ex, + user_cert, + vomsdir, + capath); + } + } + } + } + + + } /* end of for loop */ + + if (cacert != NULL) X509_free(cacert); + + return GRST_RET_OK; +} + +/// Check certificate chain for GSI proxy acceptability. +int GRSTx509CheckChain(int *first_non_ca, X509_STORE_CTX *ctx) +/// +/// Returns X509_V_OK/GRST_RET_OK if valid; OpenSSL X509 errors otherwise. +/// +/// Inspired by GSIcheck written by Mike Jones, SVE, Manchester Computing, +/// The University of Manchester. +/// +/// The GridSite version handles old and new style Globus proxies, and +/// proxies derived from user certificates issued with "X509v3 Basic +/// Constraints: CA:FALSE" (eg UK e-Science CA) +/// +/// We do not check chain links between certs here: this is done by +/// GRST_check_issued/X509_check_issued in mod_ssl's ssl_engine_init.c +/// +/// TODO: we do not yet check ProxyCertInfo and ProxyCertPolicy extensions +/// (although via GRSTx509KnownCriticalExts() we can accept them.) +{ + STACK_OF(X509) *certstack; /* Points to the client's cert chain */ + X509 *cert; /* Points to the client's cert */ + int depth; /* Depth of cert chain */ + size_t len,len2; /* Lengths of issuer and cert DN */ + int IsCA; /* Holds whether cert is allowed to sign */ + int prevIsCA; /* Holds whether previous cert in chain is + allowed to sign */ + int prevIsLimited; /* previous cert was proxy and limited */ + int i,j; /* Iteration variables */ + char *cert_DN; /* Pointer to current-certificate-in-chain's + DN */ + char *issuer_DN; /* Pointer to + issuer-of-current-cert-in-chain's DN */ + char *proxy_part_DN; /* Pointer to end part of current-cert-in-chain + maybe eg "/CN=proxy" */ + time_t now; + + time(&now); + + *first_non_ca = 0; /* set to something predictable if things fail */ + + /* Check for context */ + if (!ctx) return X509_V_ERR_INVALID_CA; + /* Can't GSI-verify if there is no context. Here and throughout this + function we report all errors as X509_V_ERR_INVALID_CA. */ + + /* Set necessary preliminary values */ + IsCA = TRUE; /* =prevIsCA - start from a CA */ + prevIsLimited = 0; + + /* Get the client cert chain */ + certstack = X509_STORE_CTX_get_chain(ctx); /* Get the client's chain */ + depth = sk_X509_num(certstack); /* How deep is that chain? */ + + /* Check the client chain */ + for (i=depth-1; i >= 0; --i) + /* loop through client-presented chain starting at CA end */ + { + prevIsCA=IsCA; + + /* Check for X509 certificate and point to it with 'cert' */ + if (cert = sk_X509_value(certstack, i)) + { + /* we check times and reject immediately if invalid */ + + if (now < + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(cert)),0)) + return X509_V_ERR_INVALID_CA; + + if (now > + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(cert)),0)) + return X509_V_ERR_INVALID_CA; + + /* If any forebear certificate is not allowed to sign we must + assume all decendents are proxies and cannot sign either */ + if (prevIsCA) + { + /* always treat the first cert (from the CA files) as a CA */ + if (i == depth-1) IsCA = TRUE; + /* check if this cert is valid CA for signing certs */ + else IsCA = (GRSTx509IsCA(cert) == GRST_RET_OK); + + if (!IsCA) *first_non_ca = i; + } + else + { + IsCA = FALSE; + /* Force proxy check next iteration. Important because I can + sign any CA I create! */ + } + + cert_DN = X509_NAME_oneline(X509_get_subject_name(cert),NULL,0); + issuer_DN = X509_NAME_oneline(X509_get_issuer_name(cert),NULL,0); + len = strlen(cert_DN); + len2 = strlen(issuer_DN); + + /* issuer didn't have CA status, so this is (at best) a proxy: + check for bad proxy extension*/ + + if (!prevIsCA) + { + if (prevIsLimited) /* we reject proxies of limited proxies! */ + return X509_V_ERR_INVALID_CA; + + /* User not allowed to sign shortened DN */ + if (len2 > len) return X509_V_ERR_INVALID_CA; + + /* Proxy subject must begin with issuer. */ + if (strncmp(cert_DN, issuer_DN, len2) != 0) + return X509_V_ERR_INVALID_CA; + + /* Set pointer to end of base DN in cert_DN */ + proxy_part_DN = &cert_DN[len2]; + + /* First attempt at support for Old and New style GSI + proxies: /CN=anything is ok for now */ + if (strncmp(proxy_part_DN, "/CN=", 4) != 0) + return X509_V_ERR_INVALID_CA; + + if ((strncmp(proxy_part_DN, "/CN=limited proxy", 17) == 0) && + (i > 0)) prevIsLimited = 1; /* ready for next cert ... */ + } + } + } + + /* Check cert whose private key is being used by client. If previous in + chain is not allowed to be a CA then need to check this final cert for + valid proxy-icity too */ + if (!prevIsCA) + { + if (prevIsLimited) return X509_V_ERR_INVALID_CA; + /* we do not accept proxies signed by limited proxies */ + + if (cert = sk_X509_value(certstack, 0)) + { + /* Load DN & length of DN and either its issuer or the + first-bad-issuer-in-chain */ + cert_DN = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + issuer_DN = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0); + len = strlen(cert_DN); + len2 = strlen(issuer_DN); + + /* issuer didn't have CA status, check for bad proxy extension */ + + if (len2 > len) return X509_V_ERR_INVALID_CA; + /* User not allowed to sign shortened DN */ + + if (strncmp(cert_DN, issuer_DN, len2) != 0) + return X509_V_ERR_INVALID_CA; + /* Proxy subject must begin with issuer. */ + + proxy_part_DN = &cert_DN[len2]; + /* Set pointer to end of DN base in cert_DN */ + + /* Remander of subject must be either "/CN=proxy" or + "/CN=limited proxy" (or /CN=XYZ for New style GSI) */ + + /* First attempt at support for Old and New style GSI + proxies: /CN=anything is ok for now. */ + if (strncmp(proxy_part_DN, "/CN=", 4) != 0) + return X509_V_ERR_INVALID_CA; + } + } + + return X509_V_OK; /* this is also GRST_RET_OK, of course - by choice */ +} + +/// Example VerifyCallback routine +int GRSTx509VerifyCallback (int ok, X509_STORE_CTX *ctx) +{ + int errnum = X509_STORE_CTX_get_error(ctx); + int errdepth = X509_STORE_CTX_get_error_depth(ctx); + int first_non_ca; + +#ifndef X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION +#define X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION 34 +#endif + + if (errnum == X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION) + { + if (GRSTx509KnownCriticalExts(X509_STORE_CTX_get_current_cert(ctx)) + == GRST_RET_OK) + { + ok = TRUE; + errnum = X509_V_OK; + X509_STORE_CTX_set_error(ctx, errnum); + } + } + else if ((errdepth == 0) && + (errnum == X509_V_OK) && + (GRSTx509CheckChain(&first_non_ca, ctx) != X509_V_OK)) ok = FALSE; + + + return ok; + +// check this + +// if (ok) return GRST_RET_OK; +// else return GRST_RET_FAILED; +} + +/// Get the VOMS attributes in the given extension +int GRSTx509ParseVomsExt(int *lastcred, int maxcreds, size_t credlen, + char *creds, time_t time1_time, time_t time2_time, + X509_EXTENSION *ex, + char *ucuserdn, char *ucissuerdn, char *ucserial, + char *vomsdir) +/// +/// Puts any VOMS credentials found into the Compact Creds string array +/// starting at *creds. Always returns GRST_RET_OK - even for invalid +/// credentials, which are just ignored. +{ +#define MAXTAG 500 +#define GRST_ASN1_COORDS_FQAN "-1-1-%d-1-7-1-2-1-2-%d" +#define GRST_ASN1_COORDS_ISSUER_DN "-1-1-%d-1-2-1-1-1-1-%%d-1-%%d" +#define GRST_ASN1_COORDS_TIME1 "-1-1-%d-1-6-1" +#define GRST_ASN1_COORDS_TIME2 "-1-1-%d-1-6-2" + ASN1_OCTET_STRING *asn1data; + char *asn1string, acissuerdn[200], acvomsdn[200], + dn_coords[200], fqan_coords[200], time1_coords[200], + time2_coords[200], serial_coords[200]; + unsigned char *p; + long asn1length; + int lasttag=-1, itag, i, acnumber = 1; + char *acissuerserial = NULL; + struct GRSTasn1TagList taglist[MAXTAG+1]; + time_t actime1, actime2, time_now; + ASN1_INTEGER acissuerserialASN1; + + asn1data = X509_EXTENSION_get_data(ex); + asn1string = ASN1_STRING_data(asn1data); + asn1length = ASN1_STRING_length(asn1data); + + GRSTasn1ParseDump(NULL, asn1string, asn1length, taglist, MAXTAG, &lasttag); + + for (acnumber = 1; ; ++acnumber) /* go through ACs one by one */ + { + /* check names */ + + snprintf(dn_coords, sizeof(dn_coords), GRST_ASN1_COORDS_ISSUER_DN, acnumber); + if (GRSTasn1GetX509Name(acissuerdn, sizeof(acissuerdn), dn_coords, + asn1string, taglist, lasttag) != GRST_RET_OK) break; + + if ((GRSTx509NameCmp(ucuserdn, acissuerdn) != 0) && /* old */ + (GRSTx509NameCmp(ucissuerdn, acissuerdn) != 0)) /* new */ + continue; + + /* check serial numbers */ + + snprintf(serial_coords, sizeof(serial_coords), + GRST_ASN1_COORDS_ISSUER_SERIAL, acnumber); + + itag = GRSTasn1SearchTaglist(taglist, lasttag, serial_coords); + + if (itag > -1) + { + acissuerserialASN1.length = taglist[itag].length; + acissuerserialASN1.type = V_ASN1_INTEGER; + acissuerserialASN1.data = &asn1string[taglist[itag].start+taglist[itag].headerlength]; + + acissuerserial = i2s_ASN1_INTEGER(NULL, &acissuerserialASN1); +/* + p = &asn1string[taglist[itag].start+taglist[itag].headerlength]; + + if (taglist[itag].length == 2) + acissuerserial = p[1] + p[0] * 0x100; + else if (taglist[itag].length == 3) + acissuerserial = p[2] + p[1] * 0x100 + p[0] * 0x10000; + else if (taglist[itag].length == 4) + acissuerserial = p[3] + p[2] * 0x100 + p[1] * 0x10000 + + p[0] * 0x1000000; +*/ + } + + if (strcmp(acissuerserial, ucserial) != 0) continue; + + if (GRSTx509VerifyVomsSig(&time1_time, &time2_time, + asn1string, taglist, lasttag, vomsdir, acnumber) + != GRST_RET_OK) continue; + + snprintf(time1_coords, sizeof(time1_coords), GRST_ASN1_COORDS_TIME1, acnumber); + itag = GRSTasn1SearchTaglist(taglist, lasttag, time1_coords); + actime1 = GRSTasn1TimeToTimeT(&asn1string[taglist[itag].start+ + taglist[itag].headerlength], + taglist[itag].length); + if (actime1 > time1_time) time1_time = actime1; + + snprintf(time2_coords, sizeof(time2_coords), GRST_ASN1_COORDS_TIME2, acnumber); + itag = GRSTasn1SearchTaglist(taglist, lasttag, time2_coords); + actime2 = GRSTasn1TimeToTimeT(&asn1string[taglist[itag].start+ + taglist[itag].headerlength], + taglist[itag].length); + if (actime2 < time2_time) time2_time = actime2; + + time(&time_now); + if ((time1_time > time_now + 300) || (time2_time < time_now)) + continue; /* expiration isnt invalidity ...? */ + + for (i=1; ; ++i) + { + snprintf(fqan_coords, sizeof(fqan_coords), GRST_ASN1_COORDS_FQAN, acnumber, i); + itag = GRSTasn1SearchTaglist(taglist, lasttag, fqan_coords); + + if (itag > -1) + { + if (*lastcred < maxcreds - 1) + { + ++(*lastcred); + snprintf(&creds[*lastcred * (credlen + 1)], credlen+1, + "VOMS %010lu %010lu 0 %.*s", + time1_time, time2_time, + taglist[itag].length, + &asn1string[taglist[itag].start+ + taglist[itag].headerlength]); + } + } + else break; + } + } + + return GRST_RET_OK; +} + +/// Get the VOMS attributes in the extensions to the given cert stack +int GRSTx509GetVomsCreds(int *lastcred, int maxcreds, size_t credlen, + char *creds, X509 *usercert, STACK_OF(X509) *certstack, + char *vomsdir) +/// +/// Puts any VOMS credentials found into the Compact Creds string array +/// starting at *creds. Always returns GRST_RET_OK. +{ + int i, j; + char s[80], *ucserial; + unsigned char *ucuser, *ucissuer; + X509_EXTENSION *ex; + ASN1_STRING *asn1str; + X509 *cert; + time_t time1_time = 0, time2_time = 0, uctime1_time, uctime2_time; + + uctime1_time = + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(usercert)),0); + uctime2_time = + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(usercert)),0); + ucuser = + X509_NAME_oneline(X509_get_subject_name(usercert), NULL, 0); + ucissuer = + X509_NAME_oneline(X509_get_issuer_name(usercert), NULL, 0); + ucserial = i2s_ASN1_INTEGER(NULL, X509_get_serialNumber(usercert)); + + for (j=sk_X509_num(certstack)-1; j >= 0; --j) + { + cert = sk_X509_value(certstack, j); + + time1_time = + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(cert)),0); + uctime1_time = (time1_time > uctime1_time) ? time1_time:uctime1_time; + + time2_time = + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(cert)),0); + uctime2_time = (time2_time < uctime2_time) ? time2_time:uctime2_time; + + for (i=0; i < X509_get_ext_count(cert); ++i) + { + ex = X509_get_ext(cert, i); + OBJ_obj2txt(s, sizeof(s), X509_EXTENSION_get_object(ex), 1); + + if (strcmp(s, GRST_VOMS_OID) == 0) /* a VOMS extension */ + { + GRSTx509ParseVomsExt(lastcred, maxcreds, credlen, creds, + uctime1_time, uctime2_time, + ex, ucuser, ucissuer, ucserial, + vomsdir); + } + } + } + + return GRST_RET_OK; +} + +/// Turn a Compact Cred line into a GRSTgaclCred object +GRSTgaclCred *GRSTx509CompactToCred(char *grst_cred) +/// +/// Returns pointer to created GRSTgaclCred or NULL or failure. +{ + int delegation; + char *p, *encoded; + time_t now, notbefore, notafter; + GRSTgaclCred *cred = NULL; + + time(&now); + + if (grst_cred == NULL) return NULL; /* just in case */ + + if (strncmp(grst_cred, "X509USER ", 9) == 0) + { + if ((sscanf(grst_cred, "X509USER %lu %lu %d", + ¬before, ¬after, &delegation) == 3) + && (now >= notbefore) + && (now <= notafter) + && (p = index(grst_cred, ' ')) + && (p = index(++p, ' ')) + && (p = index(++p, ' ')) + && (p = index(++p, ' '))) + { + encoded = GRSThttpUrlMildencode(&p[1]); + cred = GRSTgaclCredCreate("dn:", encoded); + free(encoded); + GRSTgaclCredSetDelegation(cred, delegation); + } + + return cred; + } + + if (strncmp(grst_cred, "VOMS ", 5) == 0) + { + if ((sscanf(grst_cred, "VOMS %lu %lu %d", + ¬before, ¬after, &delegation) == 3) + && (now >= notbefore) + && (now <= notafter) + && (p = index(grst_cred, ' ')) + && (p = index(++p, ' ')) + && (p = index(++p, ' ')) + && (p = index(++p, ' '))) + { + /* include /VO/group/subgroup/Role=role/Capability=cap */ + + if (p[1] != '/') return NULL; /* must begin with / */ + + encoded = GRSThttpUrlMildencode(&p[1]); + cred = GRSTgaclCredCreate("fqan:", encoded); + free(encoded); + GRSTgaclCredSetDelegation(cred, delegation); + } + + return cred; + } + + return NULL; /* dont recognise this credential type */ +} + +/// Get the credentials in an X509 cert/GSI proxy, including any VOMS +int GRSTx509CompactCreds(int *lastcred, int maxcreds, size_t credlen, + char *creds, STACK_OF(X509) *certstack, char *vomsdir, + X509 *peercert) +/// +/// Credentials are placed in Compact Creds string array at *creds. +/// +/// Function returns GRST_RET_OK on success, or GRST_RET_FAILED if +/// some inconsistency found in certificate. +{ + int i, j, delegation = 0; + char credtemp[credlen+1]; + X509 *cert, *usercert = NULL, *gsiproxycert = NULL; + + *lastcred = -1; + + for (i = sk_X509_num(certstack) - 1; i >= 0; --i) + { + cert = sk_X509_value(certstack, i); + + if (usercert != NULL) + { /* found a (GSI proxy) cert after the user cert */ + gsiproxycert = cert; + ++delegation; + } + + if ((usercert == NULL) && + (i < sk_X509_num(certstack) - 1) && + (GRSTx509IsCA(cert) != GRST_RET_OK)) usercert = cert; + /* found the 1st non-CA cert */ + } + + if (peercert != NULL) + { + if (usercert != NULL) /* found a (GSI proxy) cert after user cert */ + { + gsiproxycert = peercert; + ++delegation; + } + + if ((usercert == NULL) && + (GRSTx509IsCA(peercert) != GRST_RET_OK)) usercert = peercert; + /* found the 1st non-CA cert */ + } + + if ((usercert == NULL) /* if no usercert ("EEC"), we're not interested */ + || + (snprintf(credtemp, credlen+1, "X509USER %010lu %010lu %d %s", + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(usercert)),0), + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(usercert)),0), + delegation, + X509_NAME_oneline(X509_get_subject_name(usercert), NULL, 0)) >= credlen+1) + || + (*lastcred >= maxcreds-1)) + { + *lastcred = -1; /* just in case the caller looks at it */ + return GRST_RET_FAILED; /* tell caller that things didn't work out */ + } + + ++(*lastcred); + strcpy(&creds[*lastcred * (credlen + 1)], credtemp); + + if ((gsiproxycert != NULL) + && + (snprintf(credtemp, credlen+1, "GSIPROXY %010lu %010lu %d %s", + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(gsiproxycert)),0), + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(gsiproxycert)),0), + delegation, + X509_NAME_oneline(X509_get_subject_name(gsiproxycert), NULL, 0)) < credlen+1) + && + (*lastcred < maxcreds-1)) + { + ++(*lastcred); + strcpy(&creds[*lastcred * (credlen + 1)], credtemp); + + GRSTx509GetVomsCreds(lastcred, maxcreds, credlen, creds, + usercert, certstack, vomsdir); + + } + + return GRST_RET_OK; +} + +/// Find proxy file name of the current user +char *GRSTx509FindProxyFileName(void) +/// +/// Return a string with the proxy file name or NULL if not present. +/// This function does not check if the proxy has expired. +{ + char *p; + + p = getenv("X509_USER_PROXY"); + + if (p != NULL) return strdup(p); + + p = malloc(sizeof("/tmp/x509up_uXYYYXXXYYY")); + + sprintf(p, "/tmp/x509up_u%d", getuid()); + + return p; +} + +static void mpcerror(FILE *debugfp, char *msg) +{ + if (debugfp != NULL) + { + fputs(msg, debugfp); + ERR_print_errors_fp(debugfp); + } +} + +/// Make a GSI Proxy chain from a request, certificate and private key +int GRSTx509MakeProxyCert(char **proxychain, FILE *debugfp, + char *reqtxt, char *cert, char *key, int minutes) +/// +/// The proxy chain is returned in *proxychain. If debugfp is non-NULL, +/// errors are output to that file pointer. The proxy will expired in +/// the given number of minutes starting from the current time. +{ + char *ptr, *certchain, s[41]; + static unsigned char pci_str[] = { 0x30, 0x0c, 0x30, 0x0a, 0x06, 0x08, + 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x15, 0x01, 0 }, + kyu_str[] = { 0x03, 0x02, 0x03, + X509v3_KU_DIGITAL_SIGNATURE | + X509v3_KU_KEY_ENCIPHERMENT | + X509v3_KU_KEY_AGREEMENT, + 0 }; + int i, ncerts, any_rfc_proxies = 0; + long serial = 1234, ptrlen; + EVP_PKEY *pkey, *CApkey; + const EVP_MD *digest; + X509 *certs[GRST_MAX_CHAIN_LEN]; + X509_REQ *req; + X509_NAME *name, *CAsubject, *newsubject; + X509_NAME_ENTRY *ent; + ASN1_OBJECT *pci_obj = NULL, *kyu_obj; + ASN1_OCTET_STRING *pci_oct, *kyu_oct; + X509_EXTENSION *pci_ex, *kyu_ex; + FILE *fp; + BIO *reqmem, *certmem; + time_t notAfter; + + /* read in the request */ + reqmem = BIO_new(BIO_s_mem()); + BIO_puts(reqmem, reqtxt); + + if (!(req = PEM_read_bio_X509_REQ(reqmem, NULL, NULL, NULL))) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error reading request from BIO memory\n"); + BIO_free(reqmem); + return GRST_RET_FAILED; + } + + BIO_free(reqmem); + + /* verify signature on the request */ + if (!(pkey = X509_REQ_get_pubkey(req))) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error getting public key from request\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + if (X509_REQ_verify(req, pkey) != 1) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error verifying signature on certificate\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + /* read in the signing certificate */ + if (!(fp = fopen(cert, "r"))) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error opening signing certificate file\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + for (ncerts = 1; ncerts < GRST_MAX_CHAIN_LEN; ++ncerts) + if ((certs[ncerts] = PEM_read_X509(fp, NULL, NULL, NULL)) == NULL) break; + + if (ncerts == 1) /* zeroth cert with be new proxy cert */ + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error reading signing certificate file\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + fclose(fp); + + CAsubject = X509_get_subject_name(certs[1]); + + /* read in the CA private key */ + if (!(fp = fopen(key, "r"))) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error reading signing private key file\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + if (!(CApkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL))) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error reading signing private key in file\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + fclose(fp); + + /* get subject name */ + if (!(name = X509_REQ_get_subject_name(req))) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error getting subject name from request\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + /* create new certificate */ + if (!(certs[0] = X509_new())) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error creating X509 object\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + /* set version number for the certificate (X509v3) and the serial number + + We now use 2 = v3 for the GSI proxy, rather than the old Globus + behaviour of 3 = v4. See Savannah Bug #53721 */ + + if (X509_set_version(certs[0], 2L) != 1) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error setting certificate version\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + ASN1_INTEGER_set(X509_get_serialNumber(certs[0]), (long) time(NULL)); + + if (!(name = X509_get_subject_name(certs[1]))) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error getting subject name from CA certificate\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + if (X509_set_issuer_name(certs[0], name) != 1) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error setting issuer name of certificate\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + /* set public key in the certificate */ + if (X509_set_pubkey(certs[0], pkey) != 1) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error setting public key of the certificate\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + /* set duration for the certificate */ + if (!(X509_gmtime_adj(X509_get_notBefore(certs[0]), -GRST_BACKDATE_SECONDS))) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error setting beginning time of the certificate\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + if (!(X509_gmtime_adj(X509_get_notAfter(certs[0]), 60 * minutes))) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error setting ending time of the certificate\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + /* go through chain making sure this proxy is not longer lived */ + + pci_obj = OBJ_txt2obj(GRST_PROXYCERTINFO_OID, 0); + + notAfter = + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(certs[0])), 0); + + for (i=1; i < ncerts; ++i) + { + if (notAfter > + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(certs[i])), + 0)) + { + notAfter = + GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(certs[i])), + 0); + + ASN1_UTCTIME_set(X509_get_notAfter(certs[0]), notAfter); + } + + if (X509_get_ext_by_OBJ(certs[i], pci_obj, -1) > 0) + any_rfc_proxies = 1; + } + + /* if any earlier proxies are RFC 3820, then new proxy must be + an RFC 3820 proxy too with the required extensions */ + if (any_rfc_proxies) + { + /* key usage */ + kyu_obj = OBJ_txt2obj(GRST_KEYUSAGE_OID, 0); + kyu_ex = X509_EXTENSION_new(); + + X509_EXTENSION_set_object(kyu_ex, kyu_obj); + ASN1_OBJECT_free(kyu_obj); + X509_EXTENSION_set_critical(kyu_ex, 1); + + kyu_oct = ASN1_OCTET_STRING_new(); + ASN1_OCTET_STRING_set(kyu_oct, kyu_str, strlen(kyu_str)); + X509_EXTENSION_set_data(kyu_ex, kyu_oct); + ASN1_OCTET_STRING_free(kyu_oct); + + X509_add_ext(certs[0], kyu_ex, -1); + X509_EXTENSION_free(kyu_ex); + + /* proxy certificate info */ + pci_ex = X509_EXTENSION_new(); + + X509_EXTENSION_set_object(pci_ex, pci_obj); + X509_EXTENSION_set_critical(pci_ex, 1); + + pci_oct = ASN1_OCTET_STRING_new(); + ASN1_OCTET_STRING_set(pci_oct, pci_str, strlen(pci_str)); + X509_EXTENSION_set_data(pci_ex, pci_oct); + ASN1_OCTET_STRING_free(pci_oct); + + X509_add_ext(certs[0], pci_ex, -1); + X509_EXTENSION_free(pci_ex); + } + ASN1_OBJECT_free(pci_obj); + + /* set issuer and subject name of the cert from the req and the CA */ + + if (any_rfc_proxies) /* user CN=number rather than CN=proxy */ + { + snprintf(s, sizeof(s), "%ld", (long) time(NULL)); + ent = X509_NAME_ENTRY_create_by_NID(NULL, OBJ_txt2nid("commonName"), + MBSTRING_ASC, s, -1); + } + else ent = X509_NAME_ENTRY_create_by_NID(NULL, OBJ_txt2nid("commonName"), + MBSTRING_ASC, "proxy", -1); + + newsubject = X509_NAME_dup(CAsubject); + + X509_NAME_add_entry(newsubject, ent, -1, 0); + + if (X509_set_subject_name(certs[0], newsubject) != 1) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error setting subject name of certificate\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + X509_NAME_free(newsubject); + X509_NAME_ENTRY_free(ent); + + /* sign the certificate with the signing private key */ + if (EVP_PKEY_type(CApkey->type) == EVP_PKEY_RSA) + digest = EVP_md5(); + else + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error checking signing private key for a valid digest\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + if (!(X509_sign(certs[0], CApkey, digest))) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error signing certificate\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + /* store the completed certificate chain */ + + certchain = strdup(""); + + for (i=0; i < ncerts; ++i) + { + certmem = BIO_new(BIO_s_mem()); + + if (PEM_write_bio_X509(certmem, certs[i]) != 1) + { + mpcerror(debugfp, + "GRSTx509MakeProxyCert(): error writing certificate to memory BIO\n"); + + X509_REQ_free(req); + return GRST_RET_FAILED; + } + + ptrlen = BIO_get_mem_data(certmem, &ptr); + + certchain = realloc(certchain, strlen(certchain) + ptrlen + 1); + + strncat(certchain, ptr, ptrlen); + + BIO_free(certmem); + X509_free(certs[i]); + } + + EVP_PKEY_free(pkey); + EVP_PKEY_free(CApkey); + X509_REQ_free(req); + + *proxychain = certchain; + return GRST_RET_OK; +} + +/// Find a proxy file in the proxy cache +char *GRSTx509CachedProxyFind(char *proxydir, char *delegation_id, + char *user_dn) +/// +/// Returns the full path and file name of proxy file associated +/// with given delegation ID and user DN. +/// +/// Return a pointer to a malloc'd string with the full path of the +/// proxy file corresponding to the given delegation_id, or NULL +/// if not found. +{ + char *user_dn_enc, *proxyfile; + struct stat statbuf; + + user_dn_enc = GRSThttpUrlEncode(user_dn); + + asprintf(&proxyfile, "%s/%s/%s/userproxy.pem", + proxydir, user_dn_enc, delegation_id); + + free(user_dn_enc); + + if ((stat(proxyfile, &statbuf) != 0) || !S_ISREG(statbuf.st_mode)) + { + free(proxyfile); + return NULL; + } + + return proxyfile; +} + +/// Find a temporary proxy private key file in the proxy cache +char *GRSTx509CachedProxyKeyFind(char *proxydir, char *delegation_id, + char *user_dn) +/// +/// Returns the full path and file name of the private key file associated +/// with given delegation ID and user DN. +/// +/// Return a pointer to a malloc'd string with the full path of the +/// private proxy key corresponding to the given delegation_id, or NULL +/// if not found. +{ + char *user_dn_enc, *prvkeyfile; + struct stat statbuf; + + user_dn_enc = GRSThttpUrlEncode(user_dn); + + asprintf(&prvkeyfile, "%s/cache/%s/%s/userkey.pem", + proxydir, user_dn_enc, delegation_id); + + free(user_dn_enc); + + if ((stat(prvkeyfile, &statbuf) != 0) || !S_ISREG(statbuf.st_mode)) + { + free(prvkeyfile); + return NULL; + } + + return prvkeyfile; +} + +static void mkdir_printf(mode_t mode, char *fmt, ...) +{ + int ret; + char *path; + va_list ap; + + va_start(ap, fmt); + vasprintf(&path, fmt, ap); + va_end(ap); + + ret = mkdir(path, mode); + + free(path); +} + +/// Create a X.509 request for a GSI proxy and its private key +int GRSTx509CreateProxyRequest(char **reqtxt, char **keytxt, char *ocspurl) +/// +/// Returns GRST_RET_OK on success, non-zero otherwise. Request string +/// and private key are PEM encoded strings +{ + int i; + char *ptr; + size_t ptrlen; + RSA *keypair; + X509_NAME *subject; + X509_NAME_ENTRY *ent; + EVP_PKEY *pkey; + X509_REQ *certreq; + BIO *reqmem, *keymem; + const EVP_MD *digest; + struct stat statbuf; + + /* create key pair and put it in a PEM string */ + + if ((keypair = RSA_generate_key(GRST_KEYSIZE, 65537, NULL, NULL)) == NULL) + return 1; + + keymem = BIO_new(BIO_s_mem()); + if (!PEM_write_bio_RSAPrivateKey(keymem, keypair, NULL, NULL, 0, NULL, NULL)) + { + BIO_free(keymem); + return 3; + } + + ptrlen = BIO_get_mem_data(keymem, &ptr); + + *keytxt = malloc(ptrlen + 1); + memcpy(*keytxt, ptr, ptrlen); + (*keytxt)[ptrlen] = '\0'; + + BIO_free(keymem); + + /* now create the certificate request */ + + certreq = X509_REQ_new(); + + OpenSSL_add_all_algorithms(); + + pkey = EVP_PKEY_new(); + EVP_PKEY_assign_RSA(pkey, keypair); + + X509_REQ_set_pubkey(certreq, pkey); + + subject = X509_NAME_new(); + ent = X509_NAME_ENTRY_create_by_NID(NULL, OBJ_txt2nid("organizationName"), + MBSTRING_ASC, "Dummy", -1); + X509_NAME_add_entry (subject, ent, -1, 0); + X509_REQ_set_subject_name (certreq, subject); + + digest = EVP_md5(); + X509_REQ_sign(certreq, pkey, digest); + + reqmem = BIO_new(BIO_s_mem()); + PEM_write_bio_X509_REQ(reqmem, certreq); + ptrlen = BIO_get_mem_data(reqmem, &ptr); + + *reqtxt = malloc(ptrlen + 1); + memcpy(*reqtxt, ptr, ptrlen); + (*reqtxt)[ptrlen] = '\0'; + + BIO_free(reqmem); + + X509_REQ_free(certreq); + + return 0; +} + +/// Make and store a X.509 request for a GSI proxy +int GRSTx509MakeProxyRequest(char **reqtxt, char *proxydir, + char *delegation_id, char *user_dn) +/// +/// Returns GRST_RET_OK on success, non-zero otherwise. Request string +/// is PEM encoded, and the key is stored in the temporary cache under +/// proxydir +{ + int i = 0; + char *docroot = NULL, *prvkeyfile = NULL, *ptr = NULL, *user_dn_enc = NULL; + size_t ptrlen = 0; + FILE *fp = NULL; + RSA *keypair = NULL; + BIO *reqmem = NULL; + canl_ctx c_ctx = NULL; + canl_cred proxy_bob = NULL; + X509_REQ *req = NULL; + int retval = GRST_RET_OK; + + if (strcmp(user_dn, "cache") == 0) + return GRST_RET_FAILED; + + user_dn_enc = GRSThttpUrlEncode(user_dn); + + /* create directories if necessary */ + + mkdir_printf(S_IRUSR | S_IWUSR | S_IXUSR, + "%s/cache", proxydir); + mkdir_printf(S_IRUSR | S_IWUSR | S_IXUSR, + "%s/cache/%s", proxydir, user_dn_enc); + mkdir_printf(S_IRUSR | S_IWUSR | S_IXUSR, + "%s/cache/%s/%s", proxydir, user_dn_enc, delegation_id); + + /* make the new proxy private key */ + + asprintf(&prvkeyfile, "%s/cache/%s/%s/userkey.pem", + proxydir, user_dn_enc, delegation_id); + + if (prvkeyfile == NULL){ + free(user_dn_enc); + return GRST_RET_FAILED; + } + + /*Make new canl_ctx, TODO MP how to initialize it only once?*/ + c_ctx = canl_create_ctx(); + if (c_ctx == NULL) { + free(prvkeyfile); + return 10; /* TODO MP we can use caNl error codes now */ + } + + ret = canl_cred_new(c_ctx, &proxy_bob); + if (ret){ + retval = 11; + goto end; + } + + /*use caNl to generate X509 request*/ + ret = canl_cred_new_req(c_ctx, proxy_bob, GRST_KEYSIZE); + if (ret) { + retval = 12; + goto end; + } + + ret = canl_cred_save_req(c_ctx, proxy_bob, &req); + if (ret) { + retval = 13; + goto end; + } + + if ((fp = fopen(prvkeyfile, "w")) == NULL) + { + retval = 14; + goto end; + } + + /*Save private key in cache*/ + chmod(prvkeyfile, S_IRUSR | S_IWUSR); + free(prvkeyfile); + prvkeyfile = NULL; + free(user_dn_enc); + user_dn_enc = NULL; + + keypair = canl_cred_get_keypair(); + if (!keypair) { + retval = 15; + goto end; + } + + if (!PEM_write_RSAPrivateKey(fp, keypair, NULL, NULL, 0, NULL, NULL)) { + retval = 3; + goto end; + } + + if (fclose(fp) != 0){ + retval = 4; + goto end; + } + + /* TODO MP ask. "Dummy" vs "proxy" in sslutils.c from Voms + ent=X509_NAME_ENTRY_create_by_NID(NULL, OBJ_txt2nid("organizationName"), + MBSTRING_ASC, "Dummy", -1); + TODO MD5 vs SHA1 defaults. + */ + + /*Convert request into string*/ + reqmem = BIO_new(BIO_s_mem()); + PEM_write_bio_X509_REQ(reqmem, req); + ptrlen = BIO_get_mem_data(reqmem, &ptr); + + *reqtxt = malloc(ptrlen + 1); + memcpy(*reqtxt, ptr, ptrlen); + (*reqtxt)[ptrlen] = '\0'; +end: + if (proxy_bob) + canl_cred_free(ctx, proxy_bob); + if (reqmem) + BIO_free(reqmem); + if (req) + X509_REQ_free(req); + if (prvkeyfile) + free(prvkeyfile); + if (user_dn_enc) + free(user_dn_enc); + if (c_ctx) + canl_free_ctx(c_ctx); + + return retval; +} + +/// Destroy stored GSI proxy files +int GRSTx509ProxyDestroy(char *proxydir, char *delegation_id, char *user_dn) +/// +/// Returns GRST_RET_OK on success, non-zero otherwise. +/// (Including GRST_RET_NO_SUCH_FILE if the private key or cert chain +/// were not found.) +{ + int ret = GRST_RET_OK; + char *docroot, *filename, *user_dn_enc; + + if (strcmp(user_dn, "cache") == 0) return GRST_RET_FAILED; + + user_dn_enc = GRSThttpUrlEncode(user_dn); + + /* proxy file */ + + asprintf(&filename, "%s/%s/%s/userproxy.pem", + proxydir, user_dn_enc, delegation_id); + + if (filename == NULL) + { + free(user_dn_enc); + return GRST_RET_FAILED; + } + + if (unlink(filename) != 0) ret = GRST_RET_NO_SUCH_FILE; + free(filename); + + /* voms file */ + + asprintf(&filename, "%s/%s/%s/voms.attributes", + proxydir, user_dn_enc, delegation_id); + + if (filename == NULL) + { + free(user_dn_enc); + return GRST_RET_FAILED; + } + + unlink(filename); + free(filename); + + return ret; +} + +/// Get start and finish validity times of stored GSI proxy file +int GRSTx509ProxyGetTimes(char *proxydir, char *delegation_id, char *user_dn, + time_t *start, time_t *finish) +/// +/// Returns GRST_RET_OK on success, non-zero otherwise. +/// (Including GRST_RET_NO_SUCH_FILE if the cert chain was not found.) +{ + char *docroot, *filename, *user_dn_enc; + FILE *fp; + X509 *cert; + + if (strcmp(user_dn, "cache") == 0) return GRST_RET_FAILED; + + user_dn_enc = GRSThttpUrlEncode(user_dn); + + asprintf(&filename, "%s/%s/%s/userproxy.pem", + proxydir, user_dn_enc, delegation_id); + + free(user_dn_enc); + + if (filename == NULL) return GRST_RET_FAILED; + + fp = fopen(filename, "r"); + free(filename); + + if (fp == NULL) return GRST_RET_NO_SUCH_FILE; + + cert = PEM_read_X509(fp, NULL, NULL, NULL); /* first cert is X.509 PC */ + + fclose(fp); + + *start = GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(cert)),0); + *finish = GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(cert)),0); + + X509_free(cert); + + return GRST_RET_OK; +} + +/// Create a stack of X509 certificate from a PEM-encoded string +int GRSTx509StringToChain(STACK_OF(X509) **certstack, char *certstring) +/// +/// Creates a dynamically allocated stack of X509 certificate objects +/// by walking through the PEM-encoded X509 certificates. +/// +/// Returns GRST_RET_OK on success, non-zero otherwise. +{ + STACK_OF(X509_INFO) *sk=NULL; + BIO *certbio; + X509_INFO *xi; + + *certstack = sk_X509_new_null(); + if (*certstack == NULL) return GRST_RET_FAILED; + + certbio = BIO_new_mem_buf(certstring, -1); + + if (!(sk=PEM_X509_INFO_read_bio(certbio, NULL, NULL, NULL))) + { + BIO_free(certbio); + sk_X509_INFO_free(sk); + sk_X509_free(*certstack); + return GRST_RET_FAILED; + } + + while (sk_X509_INFO_num(sk)) + { + xi=sk_X509_INFO_shift(sk); + if (xi->x509 != NULL) + { + sk_X509_push(*certstack, xi->x509); + xi->x509=NULL; + } + X509_INFO_free(xi); + } + + if (!sk_X509_num(*certstack)) + { + BIO_free(certbio); + sk_X509_INFO_free(sk); + sk_X509_free(*certstack); + return GRST_RET_FAILED; + } + + BIO_free(certbio); + sk_X509_INFO_free(sk); + + return GRST_RET_OK; +} + +/// Returns a Delegation ID based on hash of GRST_CRED_0, ... +char *GRSTx509MakeDelegationID(void) +/// +/// Returns a malloc'd string with Delegation ID made by SHA1-hashing the +/// values of the compact credentials exported by mod_gridsite +{ + unsigned char hash_delegation_id[EVP_MAX_MD_SIZE]; + int size_needed = 0, i, delegation_id_len; + char cred_name[14], *cred_value, *delegation_id; + const EVP_MD *m; + EVP_MD_CTX ctx; + + OpenSSL_add_all_digests(); + + m = EVP_sha1(); + if (m == NULL) return NULL; + + EVP_DigestInit(&ctx, m); + + for (i=0; i <= 999; ++i) + { + snprintf(cred_name, sizeof(cred_name), "GRST_CRED_%d", i); + if ((cred_value = getenv(cred_name)) == NULL) break; + + EVP_DigestUpdate(&ctx, cred_value, strlen(cred_value)); + } + + EVP_DigestFinal(&ctx, hash_delegation_id, &delegation_id_len); + + delegation_id = malloc(17); + + for (i=0; i <=7; ++i) + sprintf(&delegation_id[i*2], "%02x", hash_delegation_id[i]); + + delegation_id[16] = '\0'; + + return delegation_id; +} + +#if 0 +/// Return the short file name for the given delegation_id and user_dn +char *GRSTx509MakeProxyFileName(char *delegation_id, + STACK_OF(X509) *certstack) +/// +/// Returns a malloc'd string with the short file name (no paths) that +/// derived from the hashed delegation_id and user_dn +/// +/// File name is SHA1_HASH(DelegationID)+"-"+SHA1_HASH(DN) where DN +/// is DER encoded version of user_dn with any trailing CN=proxy removed +/// Hashes are the most significant 8 bytes, in lowercase hexadecimal. +{ + int i, depth, prevIsCA = 1, IsCA, hash_name_len, delegation_id_len, + der_name_len; + unsigned char *der_name, *buf, hash_name[EVP_MAX_MD_SIZE], + hash_delegation_id[EVP_MAX_MD_SIZE], + filename[34]; + X509_NAME *subject_name; + X509 *cert; + const EVP_MD *m; + EVP_MD_CTX ctx; + + depth = sk_X509_num(certstack); + + for (i=depth-1; i >= 0; --i) + /* loop through the proxy chain starting at CA end */ + { + if (cert = sk_X509_value(certstack, i)) + { + IsCA = (GRSTx509IsCA(cert) == GRST_RET_OK); + + if (prevIsCA && !IsCA) /* the full certificate of the user */ + { + break; + } + } + } + + if (i < 0) return NULL; /* not found: something wrong with the chain */ + + if ((subject_name = X509_get_subject_name(cert)) == NULL) return NULL; + + der_name_len = i2d_X509_NAME(X509_get_subject_name(cert), NULL); + if (der_name_len == 0) return NULL; + + buf = OPENSSL_malloc(der_name_len); + der_name = buf; + + + if (!i2d_X509_NAME(X509_get_subject_name(cert), &der_name)) + { + OPENSSL_free(der_name); + return NULL; + } + + OpenSSL_add_all_digests(); + + m = EVP_sha1(); + if (m == NULL) + { + OPENSSL_free(der_name); + return NULL; + } + + + EVP_DigestInit(&ctx, m); + EVP_DigestUpdate(&ctx, delegation_id, strlen(delegation_id)); + EVP_DigestFinal(&ctx, hash_delegation_id, &delegation_id_len); + + /* lots of nasty hard coded numbers: + "8bytes/16chars delegation ID" + "-" + "8bytes/16chars DN" */ + + for (i=0; i <=7; ++i) + sprintf(&filename[i*2], "%02x", hash_delegation_id[i]); + + filename[16] = '-'; + + EVP_DigestInit(&ctx, m); + EVP_DigestUpdate(&ctx, buf, der_name_len); + EVP_DigestFinal(&ctx, hash_name, &hash_name_len); + + for (i=0; i <=7; ++i) + sprintf(&filename[17 + i*2], "%02x", hash_name[i]); + + return strdup(filename); +} +#endif + +/// Store a GSI proxy chain in the proxy cache, along with the private key +int GRSTx509CacheProxy(char *proxydir, char *delegation_id, + char *user_dn, char *proxychain) +/// +/// Returns GRST_RET_OK on success, non-zero otherwise. The existing +/// private key with the same delegation ID and user DN is moved out of +/// the temporary cache. +{ + int ret = 0; + int retval = GRST_RET_FAILED; + char *user_dn_enc, *prvkeyfile, *proxyfile; + STACK_OF(X509) *certstack; + FILE *ifp = NULL; + canl_ctx c_ctx = NULL; + canl_cred proxy_bob = NULL; + + if (strcmp(user_dn, "cache") == 0) + return GRST_RET_FAILED; + + + /* get the X509 stack */ + if (GRSTx509StringToChain(&certstack, proxychain) != GRST_RET_OK){ + return GRST_RET_FAILED;/* TODO MP we can use caNl error codes now */ + } + + /*Make new canl_ctx, TODO MP how to initialize it only once?*/ + c_ctx = canl_create_ctx(); + if (c_ctx == NULL) + goto end; + + ret = canl_cred_new(c_ctx, &proxy_bob); + if (ret){ + goto end; /* TODO MP we can use caNl error codes now */ + } + + /*Use caNl to load certstack into proxy_bob*/ + ret = canl_cred_load_chain(c_ctx, proxy_bob, certstack); + if (ret){ + goto end; + } + + /* create directories if necessary, and set proxy filename */ + user_dn_enc = GRSThttpUrlEncode(user_dn); + + mkdir_printf(S_IRUSR | S_IWUSR | S_IXUSR, + "%s/%s", proxydir, user_dn_enc); + mkdir_printf(S_IRUSR | S_IWUSR | S_IXUSR, + "%s/%s/%s", proxydir, user_dn_enc, delegation_id); + + asprintf(&proxyfile, "%s/%s/%s/userproxy.pem", + proxydir, user_dn_enc, delegation_id); + free(user_dn_enc); + user_dn_enc = NULL; + + /* find the existing private key file */ + prvkeyfile = GRSTx509CachedProxyKeyFind(proxydir, delegation_id, user_dn); + if (prvkeyfile == NULL) + goto end; + if ((ifp = fopen(prvkeyfile, "r")) == NULL) + goto end; + + /* insert proxy private key into canl structure, + read from private key file */ + ret = canl_cred_load_priv_key_file(c_ctx, proxy_bob, ifp); + if (ret) + goto end; + if (fclose(ifp) !=0) + goto end; + ifp = NULL; + unlink(prvkeyfile); + free(prvkeyfile); + prvkeyfile = NULL; + + ret = canl_cred_save_proxyfile(c_ctx, proxy_bob, proxyfile); + if (ret) + goto end; + + retval = GRST_RET_OK; + +end: + if (proxyfile) + free(proxyfile); + if (proxy_bob) + canl_cred_free(ctx, proxy_bob); + if (certstack) + sk_X509_free(certstack); + if (prvkeyfile) + free(prvkeyfile); + if (user_dn_enc) + free(user_dn_enc); + if (c_ctx) + canl_free_ctx(c_ctx); + if (ifp) + fclose(ifp); + + return retval; +} -- 1.8.2.3