diff --git a/src/crl.c b/src/crl.c index b5a9208..60f955a 100644 --- a/src/crl.c +++ b/src/crl.c @@ -1,1426 +1,1459 @@ /* crl.c - CRL parser * Copyright (C) 2002, 2004, 2005, 2012 g10 Code GmbH * * This file is part of KSBA. * * KSBA is free software; you can redistribute it and/or modify * it under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at * your option) any later version. * * or both in parallel, as here. * * KSBA is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public * License for more details. * * You should have received a copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . */ #include #include #include #include #include #include #include "util.h" #include "convert.h" #include "keyinfo.h" #include "der-encoder.h" #include "ber-help.h" #include "ber-decoder.h" #include "crl.h" +#include "stringbuf.h" static const char oidstr_crlNumber[] = "2.5.29.20"; static const char oidstr_crlReason[] = "2.5.29.21"; #if 0 static const char oidstr_issuingDistributionPoint[] = "2.5.29.28"; #endif static const char oidstr_certificateIssuer[] = "2.5.29.29"; static const char oidstr_authorityKeyIdentifier[] = "2.5.29.35"; /* We better buffer the hashing. */ static inline void do_hash (ksba_crl_t crl, const void *buffer, size_t length) { while (length) { size_t n = length; if (crl->hashbuf.used + n > sizeof crl->hashbuf.buffer) n = sizeof crl->hashbuf.buffer - crl->hashbuf.used; memcpy (crl->hashbuf.buffer+crl->hashbuf.used, buffer, n); crl->hashbuf.used += n; if (crl->hashbuf.used == sizeof crl->hashbuf.buffer) { if (crl->hash_fnc) crl->hash_fnc (crl->hash_fnc_arg, crl->hashbuf.buffer, crl->hashbuf.used); crl->hashbuf.used = 0; } buffer = (const char *)buffer + n; length -= n; } } #define HASH(a,b) do_hash (crl, (a), (b)) /** * ksba_crl_new: * * Create a new and empty CRL object * * Return value: A CRL object or an error code. **/ gpg_error_t ksba_crl_new (ksba_crl_t *r_crl) { *r_crl = xtrycalloc (1, sizeof **r_crl); if (!*r_crl) return gpg_error_from_errno (errno); return 0; } /** * ksba_crl_release: * @crl: A CRL object * * Release a CRL object. **/ void ksba_crl_release (ksba_crl_t crl) { if (!crl) return; xfree (crl->algo.oid); xfree (crl->algo.parm); _ksba_asn_release_nodes (crl->issuer.root); xfree (crl->issuer.image); xfree (crl->item.serial); xfree (crl->sigval); while (crl->extension_list) { crl_extn_t tmp = crl->extension_list->next; xfree (crl->extension_list->oid); xfree (crl->extension_list); crl->extension_list = tmp; } xfree (crl); } gpg_error_t ksba_crl_set_reader (ksba_crl_t crl, ksba_reader_t r) { if (!crl || !r) return gpg_error (GPG_ERR_INV_VALUE); crl->reader = r; return 0; } /* Provide a hash function so that we are able to hash the data */ void ksba_crl_set_hash_function (ksba_crl_t crl, void (*hash_fnc)(void *, const void *, size_t), void *hash_fnc_arg) { if (crl) { crl->hash_fnc = hash_fnc; crl->hash_fnc_arg = hash_fnc_arg; } } /* access functions */ /** * ksba_crl_get_digest_algo: * @cms: CMS object * * Figure out the the digest algorithm used for the signature and return * its OID. * * Return value: NULL if the signature algorithm is not yet available * or there is a mismatched between "tbsCertList.signature" and * "signatureAlgorithm"; on success the OID is returned which is valid * as long as the CRL object is valid. **/ const char * ksba_crl_get_digest_algo (ksba_crl_t crl) { if (!crl) return NULL; /* fixme: implement the described check */ return crl->algo.oid; } /** * ksba_crl_get_issuer: * @cms: CMS object * @r_issuer: returns the issuer * * This functions returns the issuer of the CRL. The caller must * release the returned object. * * Return value: 0 on success or an error code **/ gpg_error_t ksba_crl_get_issuer (ksba_crl_t crl, char **r_issuer) { gpg_error_t err; AsnNode n; const unsigned char *image; if (!crl || !r_issuer) return gpg_error (GPG_ERR_INV_VALUE); if (!crl->issuer.root) return gpg_error (GPG_ERR_NO_DATA); n = crl->issuer.root; image = crl->issuer.image; if (!n || !n->down) return gpg_error (GPG_ERR_NO_VALUE); n = n->down; /* dereference the choice node */ if (n->off == -1) { /* fputs ("get_issuer problem at node:\n", stderr); */ /* _ksba_asn_node_dump_all (n, stderr); */ return gpg_error (GPG_ERR_GENERAL); } err = _ksba_dn_to_str (image, n, r_issuer); return err; } /* Return the CRL extension in OID, CRITICAL, DER and DERLEN. The caller should iterate IDX from 0 upwards until GPG_ERR_EOF is returned. Note, that the returned values are valid as long as the context is valid and no new parsing has been started. */ gpg_error_t ksba_crl_get_extension (ksba_crl_t crl, int idx, char const **oid, int *critical, unsigned char const **der, size_t *derlen) { crl_extn_t e; if (!crl) return gpg_error (GPG_ERR_INV_VALUE); if (idx < 0) return gpg_error (GPG_ERR_INV_INDEX); for (e=crl->extension_list; e && idx; e = e->next, idx-- ) ; if (!e) return gpg_error (GPG_ERR_EOF); if (oid) *oid = e->oid; if (critical) *critical = e->critical; if (der) *der = e->der; if (derlen) *derlen = e->derlen; return 0; } /* Return the authorityKeyIdentifier in r_name and r_serial or in r_keyID. GPG_ERR_NO_DATA is returned if no authorityKeyIdentifier or only one using the keyIdentifier method is available and R_KEYID is NULL. FIXME: This function shares a lot of code with the one in cert.c */ gpg_error_t ksba_crl_get_auth_key_id (ksba_crl_t crl, ksba_sexp_t *r_keyid, ksba_name_t *r_name, ksba_sexp_t *r_serial) { gpg_error_t err; size_t derlen; const unsigned char *der; const unsigned char *keyid_der = NULL; size_t keyid_derlen = 0; struct tag_info ti; char numbuf[30]; size_t numbuflen; crl_extn_t e; if (r_keyid) *r_keyid = NULL; if (!crl || !r_name || !r_serial) return gpg_error (GPG_ERR_INV_VALUE); *r_name = NULL; *r_serial = NULL; for (e=crl->extension_list; e; e = e->next) if (!strcmp (e->oid, oidstr_authorityKeyIdentifier)) break; if (!e) return gpg_error (GPG_ERR_NO_DATA); /* not available */ /* Check that there is only one */ { crl_extn_t e2; for (e2 = e->next; e2; e2 = e2->next) if (!strcmp (e2->oid, oidstr_authorityKeyIdentifier)) return gpg_error (GPG_ERR_DUP_VALUE); } der = e->der; derlen = e->derlen; err = _ksba_ber_parse_tl (&der, &derlen, &ti); if (err) return err; if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE && ti.is_constructed) ) return gpg_error (GPG_ERR_INV_CRL_OBJ); if (ti.ndef) return gpg_error (GPG_ERR_NOT_DER_ENCODED); if (ti.length > derlen) return gpg_error (GPG_ERR_BAD_BER); err = _ksba_ber_parse_tl (&der, &derlen, &ti); if (err) return err; if (ti.class != CLASS_CONTEXT) return gpg_error (GPG_ERR_INV_CRL_OBJ); /* We expected a tag. */ if (ti.ndef) return gpg_error (GPG_ERR_NOT_DER_ENCODED); if (derlen < ti.length) return gpg_error (GPG_ERR_BAD_BER); if (ti.tag == 0) { /* keyIdentifier: Just save it away for later use. */ keyid_der = der; keyid_derlen = ti.length; der += ti.length; derlen -= ti.length; /* If the keyid has been requested but no other data follows, we directly jump to the end. */ if (r_keyid && !derlen) goto build_keyid; if (!derlen) return gpg_error (GPG_ERR_NO_DATA); /* not available */ err = _ksba_ber_parse_tl (&der, &derlen, &ti); if (err) return err; if (ti.class != CLASS_CONTEXT) return gpg_error (GPG_ERR_INV_CRL_OBJ); /* we expected a tag */ if (ti.ndef) return gpg_error (GPG_ERR_NOT_DER_ENCODED); if (derlen < ti.length) return gpg_error (GPG_ERR_BAD_BER); } if (ti.tag != 1 || !derlen) return gpg_error (GPG_ERR_INV_CRL_OBJ); err = _ksba_name_new_from_der (r_name, der, ti.length); if (err) return err; der += ti.length; derlen -= ti.length; /* Fixme: we should release r_name before returning on error */ err = _ksba_ber_parse_tl (&der, &derlen, &ti); if (err) return err; if (ti.class != CLASS_CONTEXT) return gpg_error (GPG_ERR_INV_CRL_OBJ); /* we expected a tag */ if (ti.ndef) return gpg_error (GPG_ERR_NOT_DER_ENCODED); if (derlen < ti.length) return gpg_error (GPG_ERR_BAD_BER); if (ti.tag != 2 || !derlen) return gpg_error (GPG_ERR_INV_CRL_OBJ); sprintf (numbuf,"(%u:", (unsigned int)ti.length); numbuflen = strlen (numbuf); *r_serial = xtrymalloc (numbuflen + ti.length + 2); if (!*r_serial) return gpg_error_from_errno (errno); strcpy (*r_serial, numbuf); memcpy (*r_serial+numbuflen, der, ti.length); (*r_serial)[numbuflen + ti.length] = ')'; (*r_serial)[numbuflen + ti.length + 1] = 0; build_keyid: if (r_keyid && keyid_der && keyid_derlen) { sprintf (numbuf,"(%u:", (unsigned int)keyid_derlen); numbuflen = strlen (numbuf); *r_keyid = xtrymalloc (numbuflen + keyid_derlen + 2); if (!*r_keyid) return gpg_error (GPG_ERR_ENOMEM); strcpy (*r_keyid, numbuf); memcpy (*r_keyid+numbuflen, keyid_der, keyid_derlen); (*r_keyid)[numbuflen + keyid_derlen] = ')'; (*r_keyid)[numbuflen + keyid_derlen + 1] = 0; } return 0; } /* Return the optional crlNumber in NUMBER or GPG_ERR_NO_DATA if it is not available. Caller must release NUMBER if the fuction retruned with success. */ gpg_error_t ksba_crl_get_crl_number (ksba_crl_t crl, ksba_sexp_t *number) { gpg_error_t err; size_t derlen; const unsigned char *der; struct tag_info ti; char numbuf[30]; size_t numbuflen; crl_extn_t e; if (!crl || !number) return gpg_error (GPG_ERR_INV_VALUE); *number = NULL; for (e=crl->extension_list; e; e = e->next) if (!strcmp (e->oid, oidstr_crlNumber)) break; if (!e) return gpg_error (GPG_ERR_NO_DATA); /* not available */ /* Check that there is only one. */ { crl_extn_t e2; for (e2 = e->next; e2; e2 = e2->next) if (!strcmp (e2->oid, oidstr_crlNumber)) return gpg_error (GPG_ERR_DUP_VALUE); } der = e->der; derlen = e->derlen; err = parse_integer (&der, &derlen, &ti); if (err) return err; sprintf (numbuf,"(%u:", (unsigned int)ti.length); numbuflen = strlen (numbuf); *number = xtrymalloc (numbuflen + ti.length + 2); if (!*number) return gpg_error_from_errno (errno); strcpy (*number, numbuf); memcpy (*number+numbuflen, der, ti.length); (*number)[numbuflen + ti.length] = ')'; (*number)[numbuflen + ti.length + 1] = 0; return 0; } /** * ksba_crl_get_update_times: * @crl: CRL object * @this: Returns the thisUpdate value * @next: Returns the nextUpdate value. * * THIS and NEXT may be given as NULL if the value is not required. * Return value: 0 on success or an error code **/ gpg_error_t ksba_crl_get_update_times (ksba_crl_t crl, ksba_isotime_t this, ksba_isotime_t next) { if (this) *this = 0; if (next) *next = 0; if (!crl) return gpg_error (GPG_ERR_INV_VALUE); if (!*crl->this_update) return gpg_error (GPG_ERR_INV_TIME); if (this) _ksba_copy_time (this, crl->this_update); if (next) _ksba_copy_time (next, crl->next_update); return 0; } /** * ksba_crl_get_item: * @crl: CRL object * @r_serial: Returns a S-exp with the serial number; caller must free. * @r_revocation_date: Returns the recocation date * @r_reason: Return the reason for revocation * * Return the serial number, revocation time and reason of the current * item. Any of these arguments may be passed as %NULL if the value * is not of interest. This function should be used after the parse * function came back with %KSBA_SR_GOT_ITEM. For efficiency reasons * the function should be called only once, the implementation may * return an error for the second call. * * Return value: 0 in success or an error code. **/ gpg_error_t ksba_crl_get_item (ksba_crl_t crl, ksba_sexp_t *r_serial, ksba_isotime_t r_revocation_date, ksba_crl_reason_t *r_reason) { if (r_revocation_date) *r_revocation_date = 0; if (!crl) return gpg_error (GPG_ERR_INV_VALUE); if (r_serial) { if (!crl->item.serial) return gpg_error (GPG_ERR_NO_DATA); *r_serial = crl->item.serial; crl->item.serial = NULL; } if (r_revocation_date) _ksba_copy_time (r_revocation_date, crl->item.revocation_date); if (r_reason) *r_reason = crl->item.reason; return 0; } /** * ksba_crl_get_sig_val: * @crl: CRL object * * Return the actual signature in a format suitable to be used as * input to Libgcrypt's verification function. The caller must free - * the returned string. + * the returned string. For a rsaPSS signed CRLs this function may + * also be called right after rsaPSS has been detected using + * ksba_crl_get_digest_algo and before the the signature value can be + * retrieved. In this case an S-expression of the form + * + * (sig-val (hash-algo OID)(salt-length N)) + * + * is returned. The caller should extract the actual to be used hash + * algorithm from that S-expression. Note that after the actual + * signature as been seen, a similar S-expression is returned but in + * this case also with the (rsa(s XXX)) list. * * Return value: NULL or a string with an S-Exp. **/ ksba_sexp_t ksba_crl_get_sig_val (ksba_crl_t crl) { ksba_sexp_t p; if (!crl) return NULL; + if (!crl->sigval + && crl->algo.oid && !strcmp (crl->algo.oid, "1.2.840.113549.1.1.10") + && crl->algo.parm && crl->algo.parmlen) + { + char *pss_hash; + unsigned int salt_length; + struct stringbuf sb; + + if (_ksba_keyinfo_get_pss_info (crl->algo.parm, crl->algo.parmlen, + &pss_hash, &salt_length)) + return NULL; + + init_stringbuf (&sb, 100); + put_stringbuf (&sb,"(7:sig-val(9:hash-algo"); + put_stringbuf_sexp (&sb, pss_hash); + put_stringbuf (&sb, ")(11:salt-length"); + put_stringbuf_uint (&sb, salt_length); + put_stringbuf (&sb, "))"); + + return get_stringbuf (&sb); + } + if (!crl->sigval) return NULL; p = crl->sigval; crl->sigval = NULL; return p; } /* Parser functions */ /* read one byte */ static int read_byte (ksba_reader_t reader) { unsigned char buf; size_t nread; int rc; do rc = ksba_reader_read (reader, &buf, 1, &nread); while (!rc && !nread); return rc? -1: buf; } /* read COUNT bytes into buffer. Return 0 on success */ static int read_buffer (ksba_reader_t reader, char *buffer, size_t count) { size_t nread; while (count) { if (ksba_reader_read (reader, buffer, count, &nread)) return -1; buffer += nread; count -= nread; } return 0; } /* Create a new decoder and run it for the given element */ /* Fixme: this code is duplicated from cms-parser.c */ static gpg_error_t create_and_run_decoder (ksba_reader_t reader, const char *elem_name, AsnNode *r_root, unsigned char **r_image, size_t *r_imagelen) { gpg_error_t err; ksba_asn_tree_t crl_tree; BerDecoder decoder; err = ksba_asn_create_tree ("tmttv2", &crl_tree); if (err) return err; decoder = _ksba_ber_decoder_new (); if (!decoder) { ksba_asn_tree_release (crl_tree); return gpg_error (GPG_ERR_ENOMEM); } err = _ksba_ber_decoder_set_reader (decoder, reader); if (err) { ksba_asn_tree_release (crl_tree); _ksba_ber_decoder_release (decoder); return err; } err = _ksba_ber_decoder_set_module (decoder, crl_tree); if (err) { ksba_asn_tree_release (crl_tree); _ksba_ber_decoder_release (decoder); return err; } err = _ksba_ber_decoder_decode (decoder, elem_name, 0, r_root, r_image, r_imagelen); _ksba_ber_decoder_release (decoder); ksba_asn_tree_release (crl_tree); return err; } /* Parse the extension in the buffer DER or length DERLEN and return the result in OID, CRITICAL, OFF and LEN. */ static gpg_error_t parse_one_extension (const unsigned char *der, size_t derlen, char **oid, int *critical, size_t *off, size_t *len) { gpg_error_t err; struct tag_info ti; const unsigned char *start = der; *oid = NULL; *critical = 0; *off = *len = 0; /* Extension ::= SEQUENCE { extnID OBJECT IDENTIFIER, critical BOOLEAN DEFAULT FALSE, extnValue OCTET STRING } */ err = parse_sequence (&der, &derlen, &ti); if (err) goto failure; err = parse_object_id_into_str (&der, &derlen, oid); if (err) goto failure; err = _ksba_ber_parse_tl (&der, &derlen, &ti); if (err) goto failure; if (ti.length > derlen) return gpg_error (GPG_ERR_BAD_BER); if (ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_BOOLEAN && !ti.is_constructed) { if (ti.length != 1) goto bad_ber; *critical = !!*der; parse_skip (&der, &derlen, &ti); } else { /* Undo that read. */ der -= ti.nhdr; derlen += ti.nhdr; } err = parse_octet_string (&der, &derlen, &ti); if (err) goto failure; *off = der - start; *len = ti.length; return 0; bad_ber: err = gpg_error (GPG_ERR_BAD_BER); failure: xfree (*oid); *oid = NULL; return err; } /* Store an extension into the context. */ static gpg_error_t store_one_extension (ksba_crl_t crl, const unsigned char *der, size_t derlen) { gpg_error_t err; char *oid; int critical; size_t off, len; crl_extn_t e; err = parse_one_extension (der, derlen, &oid, &critical, &off, &len); if (err) return err; e = xtrymalloc (sizeof *e + len - 1); if (!e) { err = gpg_error_from_errno (errno); xfree (oid); return err; } e->oid = oid; e->critical = critical; e->derlen = len; memcpy (e->der, der + off, len); e->next = crl->extension_list; crl->extension_list = e; return 0; } /* Parse the fixed block at the beginning. We use a custom parser here because our BER decoder is not yet able to stop at certain points */ static gpg_error_t parse_to_next_update (ksba_crl_t crl) { gpg_error_t err; struct tag_info ti; unsigned long outer_len, tbs_len; int outer_ndef, tbs_ndef; int c; unsigned char tmpbuf[500]; /* for OID or algorithmIdentifier */ size_t nread; /* read the outer sequence */ err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE && ti.is_constructed) ) return gpg_error (GPG_ERR_INV_CRL_OBJ); outer_len = ti.length; outer_ndef = ti.ndef; if (!outer_ndef && outer_len < 10) return gpg_error (GPG_ERR_TOO_SHORT); /* read the tbs sequence */ err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE && ti.is_constructed) ) return gpg_error (GPG_ERR_INV_CRL_OBJ); HASH (ti.buf, ti.nhdr); if (!outer_ndef) { if (outer_len < ti.nhdr) return gpg_error (GPG_ERR_BAD_BER); /* Triplet header larger than outer sequence */ outer_len -= ti.nhdr; if (!ti.ndef && outer_len < ti.length) return gpg_error (GPG_ERR_BAD_BER); /* Triplet larger than outer sequence */ outer_len -= ti.length; } tbs_len = ti.length; tbs_ndef = ti.ndef; if (!tbs_ndef && tbs_len < 10) return gpg_error (GPG_ERR_TOO_SHORT); /* read the optional version integer */ crl->crl_version = -1; err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; if ( ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_INTEGER) { if ( ti.is_constructed || !ti.length ) return gpg_error (GPG_ERR_INV_CRL_OBJ); HASH (ti.buf, ti.nhdr); if (!tbs_ndef) { if (tbs_len < ti.nhdr) return gpg_error (GPG_ERR_BAD_BER); tbs_len -= ti.nhdr; if (tbs_len < ti.length) return gpg_error (GPG_ERR_BAD_BER); tbs_len -= ti.length; } /* fixme: we should also check the outer data length here and in the follwing code. It might however be easier to to thsi at the end of this sequence */ if (ti.length != 1) return gpg_error (GPG_ERR_UNSUPPORTED_CRL_VERSION); if ( (c=read_byte (crl->reader)) == -1) { err = ksba_reader_error (crl->reader); return err? err : gpg_error (GPG_ERR_GENERAL); } if ( !(c == 0 || c == 1) ) return gpg_error (GPG_ERR_UNSUPPORTED_CRL_VERSION); { unsigned char tmp = c; HASH (&tmp, 1); } crl->crl_version = c; err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; } /* read the algorithm identifier */ if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE && ti.is_constructed) ) return gpg_error (GPG_ERR_INV_CRL_OBJ); if (!tbs_ndef) { if (tbs_len < ti.nhdr) return gpg_error (GPG_ERR_BAD_BER); tbs_len -= ti.nhdr; if (!ti.ndef && tbs_len < ti.length) return gpg_error (GPG_ERR_BAD_BER); tbs_len -= ti.length; } if (ti.nhdr + ti.length >= DIM(tmpbuf)) return gpg_error (GPG_ERR_TOO_LARGE); memcpy (tmpbuf, ti.buf, ti.nhdr); err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length); if (err) return err; HASH (tmpbuf, ti.nhdr+ti.length); xfree (crl->algo.oid); crl->algo.oid = NULL; xfree (crl->algo.parm); crl->algo.parm = NULL; err = _ksba_parse_algorithm_identifier2 (tmpbuf, ti.nhdr+ti.length, &nread, &crl->algo.oid, &crl->algo.parm, &crl->algo.parmlen); if (err) return err; assert (nread <= ti.nhdr + ti.length); if (nread < ti.nhdr + ti.length) return gpg_error (GPG_ERR_TOO_SHORT); /* read the name */ { unsigned long n = ksba_reader_tell (crl->reader); err = create_and_run_decoder (crl->reader, "TMTTv2.CertificateList.tbsCertList.issuer", &crl->issuer.root, &crl->issuer.image, &crl->issuer.imagelen); if (err) return err; /* imagelen might be larger than the valid data (due to read ahead). So we need to get the count from the reader */ n = ksba_reader_tell (crl->reader) - n; if (n > crl->issuer.imagelen) return gpg_error (GPG_ERR_BUG); HASH (crl->issuer.image, n); if (!tbs_ndef) { if (tbs_len < n) return gpg_error (GPG_ERR_BAD_BER); tbs_len -= n; } } /* read the thisUpdate time */ err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; if ( !(ti.class == CLASS_UNIVERSAL && (ti.tag == TYPE_UTC_TIME || ti.tag == TYPE_GENERALIZED_TIME) && !ti.is_constructed) ) return gpg_error (GPG_ERR_INV_CRL_OBJ); if (!tbs_ndef) { if (tbs_len < ti.nhdr) return gpg_error (GPG_ERR_BAD_BER); tbs_len -= ti.nhdr; if (!ti.ndef && tbs_len < ti.length) return gpg_error (GPG_ERR_BAD_BER); tbs_len -= ti.length; } if (ti.nhdr + ti.length >= DIM(tmpbuf)) return gpg_error (GPG_ERR_TOO_LARGE); memcpy (tmpbuf, ti.buf, ti.nhdr); err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length); if (err) return err; HASH (tmpbuf, ti.nhdr+ti.length); _ksba_asntime_to_iso (tmpbuf+ti.nhdr, ti.length, ti.tag == TYPE_UTC_TIME, crl->this_update); /* Read the optional nextUpdate time. */ err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; if ( ti.class == CLASS_UNIVERSAL && (ti.tag == TYPE_UTC_TIME || ti.tag == TYPE_GENERALIZED_TIME) && !ti.is_constructed ) { if (!tbs_ndef) { if (tbs_len < ti.nhdr) return gpg_error (GPG_ERR_BAD_BER); tbs_len -= ti.nhdr; if (!ti.ndef && tbs_len < ti.length) return gpg_error (GPG_ERR_BAD_BER); tbs_len -= ti.length; } if (ti.nhdr + ti.length >= DIM(tmpbuf)) return gpg_error (GPG_ERR_TOO_LARGE); memcpy (tmpbuf, ti.buf, ti.nhdr); err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length); if (err) return err; HASH (tmpbuf, ti.nhdr+ti.length); _ksba_asntime_to_iso (tmpbuf+ti.nhdr, ti.length, ti.tag == TYPE_UTC_TIME, crl->next_update); err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; } /* Read the first sequence tag of the optional SEQ of SEQ. */ if (tbs_ndef || tbs_len) { if (ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE && ti.is_constructed ) { /* yes, there is one */ HASH (ti.buf, ti.nhdr); if (!tbs_ndef) { if (tbs_len < ti.nhdr) return gpg_error (GPG_ERR_BAD_BER); tbs_len -= ti.nhdr; if (!ti.ndef && tbs_len < ti.length) return gpg_error (GPG_ERR_BAD_BER); tbs_len -= ti.length; } crl->state.have_seqseq = 1; crl->state.seqseq_ndef = ti.ndef; crl->state.seqseq_len = ti.length; /* and read the next */ err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; } } /* We need to save some stuff for the next round. */ crl->state.ti = ti; crl->state.outer_ndef = outer_ndef; crl->state.outer_len = outer_len; crl->state.tbs_ndef = tbs_ndef; crl->state.tbs_len = tbs_len; return 0; } /* Store an entry extension into the current item. */ static gpg_error_t store_one_entry_extension (ksba_crl_t crl, const unsigned char *der, size_t derlen) { gpg_error_t err; char *oid; int critical; size_t off, len; err = parse_one_extension (der, derlen, &oid, &critical, &off, &len); if (err) return err; if (!strcmp (oid, oidstr_crlReason)) { struct tag_info ti; const unsigned char *buf = der+off; size_t mylen = len; err = parse_enumerated (&buf, &mylen, &ti, 1); if (err) return err; /* Note that we OR the values so that in case this extension is repeated we can track all reason codes. */ switch (*buf) { case 0: crl->item.reason |= KSBA_CRLREASON_UNSPECIFIED; break; case 1: crl->item.reason |= KSBA_CRLREASON_KEY_COMPROMISE; break; case 2: crl->item.reason |= KSBA_CRLREASON_CA_COMPROMISE; break; case 3: crl->item.reason |= KSBA_CRLREASON_AFFILIATION_CHANGED; break; case 4: crl->item.reason |= KSBA_CRLREASON_SUPERSEDED; break; case 5: crl->item.reason |= KSBA_CRLREASON_CESSATION_OF_OPERATION; break; case 6: crl->item.reason |= KSBA_CRLREASON_CERTIFICATE_HOLD; break; case 8: crl->item.reason |= KSBA_CRLREASON_REMOVE_FROM_CRL; break; case 9: crl->item.reason |= KSBA_CRLREASON_PRIVILEGE_WITHDRAWN; break; case 10: crl->item.reason |= KSBA_CRLREASON_AA_COMPROMISE; break; default: crl->item.reason |= KSBA_CRLREASON_OTHER; break; } } if (!strcmp (oid, oidstr_certificateIssuer)) { /* FIXME: We need to implement this. */ } else if (critical) err = gpg_error (GPG_ERR_UNKNOWN_CRIT_EXTN); xfree (oid); return err; } /* Parse the revokedCertificates SEQUENCE of SEQUENCE using a custom parser for efficiency and return after each entry */ static gpg_error_t parse_crl_entry (ksba_crl_t crl, int *got_entry) { gpg_error_t err; struct tag_info ti = crl->state.ti; unsigned long seqseq_len= crl->state.seqseq_len; int seqseq_ndef = crl->state.seqseq_ndef; unsigned long len; int ndef; unsigned char tmpbuf[4096]; /* for time, serial number and extensions */ char numbuf[22]; int numbuflen; /* Check the length to see whether we are at the end of the seq but do this only when we know that we have this optional seq of seq. */ if (!crl->state.have_seqseq) return 0; /* ready (no entries at all) */ if (!seqseq_ndef && !seqseq_len) return 0; /* ready */ /* if this is not a SEQUENCE the CRL is invalid */ if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE && ti.is_constructed) ) return gpg_error (GPG_ERR_INV_CRL_OBJ); HASH (ti.buf, ti.nhdr); if (!seqseq_ndef) { if (seqseq_len < ti.nhdr) return gpg_error (GPG_ERR_BAD_BER); seqseq_len -= ti.nhdr; if (!ti.ndef && seqseq_len < ti.length) return gpg_error (GPG_ERR_BAD_BER); seqseq_len -= ti.length; } ndef = ti.ndef; len = ti.length; /* get the serial number */ err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_INTEGER && !ti.is_constructed) ) return gpg_error (GPG_ERR_INV_CRL_OBJ); if (!ndef) { if (len < ti.nhdr) return gpg_error (GPG_ERR_BAD_BER); len -= ti.nhdr; if (!ti.ndef && len < ti.length) return gpg_error (GPG_ERR_BAD_BER); len -= ti.length; } if (ti.nhdr + ti.length >= DIM(tmpbuf)) return gpg_error (GPG_ERR_TOO_LARGE); memcpy (tmpbuf, ti.buf, ti.nhdr); err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length); if (err) return err; HASH (tmpbuf, ti.nhdr+ti.length); xfree (crl->item.serial); sprintf (numbuf,"(%u:", (unsigned int)ti.length); numbuflen = strlen (numbuf); crl->item.serial = xtrymalloc (numbuflen + ti.length + 2); if (!crl->item.serial) return gpg_error (GPG_ERR_ENOMEM); strcpy (crl->item.serial, numbuf); memcpy (crl->item.serial+numbuflen, tmpbuf+ti.nhdr, ti.length); crl->item.serial[numbuflen + ti.length] = ')'; crl->item.serial[numbuflen + ti.length + 1] = 0; crl->item.reason = 0; /* get the revocation time */ err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; if ( !(ti.class == CLASS_UNIVERSAL && (ti.tag == TYPE_UTC_TIME || ti.tag == TYPE_GENERALIZED_TIME) && !ti.is_constructed) ) return gpg_error (GPG_ERR_INV_CRL_OBJ); if (!ndef) { if (len < ti.nhdr) return gpg_error (GPG_ERR_BAD_BER); len -= ti.nhdr; if (!ti.ndef && len < ti.length) return gpg_error (GPG_ERR_BAD_BER); len -= ti.length; } if (ti.nhdr + ti.length >= DIM(tmpbuf)) return gpg_error (GPG_ERR_TOO_LARGE); memcpy (tmpbuf, ti.buf, ti.nhdr); err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length); if (err) return err; HASH (tmpbuf, ti.nhdr+ti.length); _ksba_asntime_to_iso (tmpbuf+ti.nhdr, ti.length, ti.tag == TYPE_UTC_TIME, crl->item.revocation_date); /* if there is still space we must parse the optional entryExtensions */ if (ndef) return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); else if (len) { /* read the outer sequence */ err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE && ti.is_constructed) ) return gpg_error (GPG_ERR_INV_CRL_OBJ); if (ti.ndef) return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); HASH (ti.buf, ti.nhdr); if (len < ti.nhdr) return gpg_error (GPG_ERR_BAD_BER); len -= ti.nhdr; if (len < ti.length) return gpg_error (GPG_ERR_BAD_BER); /* now loop over the extensions */ while (len) { err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE && ti.is_constructed) ) return gpg_error (GPG_ERR_INV_CRL_OBJ); if (ti.ndef) return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); if (len < ti.nhdr) return gpg_error (GPG_ERR_BAD_BER); len -= ti.nhdr; if (len < ti.length) return gpg_error (GPG_ERR_BAD_BER); len -= ti.length; if (ti.nhdr + ti.length >= DIM(tmpbuf)) return gpg_error (GPG_ERR_TOO_LARGE); memcpy (tmpbuf, ti.buf, ti.nhdr); err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length); if (err) return err; HASH (tmpbuf, ti.nhdr+ti.length); err = store_one_entry_extension (crl, tmpbuf, ti.nhdr+ti.length); if (err) return err; } } /* read ahead */ err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; *got_entry = 1; /* Fixme: the seqseq length is not correct if any element was ndef'd */ crl->state.ti = ti; crl->state.seqseq_ndef = seqseq_ndef; crl->state.seqseq_len = seqseq_len; return 0; } /* This function is used when a [0] tag was encountered to read the crlExtensions */ static gpg_error_t parse_crl_extensions (ksba_crl_t crl) { gpg_error_t err; struct tag_info ti = crl->state.ti; unsigned long ext_len, len; unsigned char tmpbuf[4096]; /* for extensions */ /* if we do not have a tag [0] we are done with this */ if (!(ti.class == CLASS_CONTEXT && ti.tag == 0 && ti.is_constructed)) return 0; if (ti.ndef) return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); HASH (ti.buf, ti.nhdr); ext_len = ti.length; /* read the outer sequence */ err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE && ti.is_constructed) ) return gpg_error (GPG_ERR_INV_CRL_OBJ); if (ti.ndef) return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); HASH (ti.buf, ti.nhdr); if (ext_len < ti.nhdr) return gpg_error (GPG_ERR_BAD_BER); ext_len -= ti.nhdr; if (ext_len < ti.length) return gpg_error (GPG_ERR_BAD_BER); len = ti.length; /* now loop over the extensions */ while (len) { err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE && ti.is_constructed) ) return gpg_error (GPG_ERR_INV_CRL_OBJ); if (ti.ndef) return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); if (len < ti.nhdr) return gpg_error (GPG_ERR_BAD_BER); len -= ti.nhdr; if (len < ti.length) return gpg_error (GPG_ERR_BAD_BER); len -= ti.length; if (ti.nhdr + ti.length >= DIM(tmpbuf)) return gpg_error (GPG_ERR_TOO_LARGE); /* fixme use a larger buffer if the extension does not fit into tmpbuf */ memcpy (tmpbuf, ti.buf, ti.nhdr); err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length); if (err) return err; HASH (tmpbuf, ti.nhdr+ti.length); err = store_one_extension (crl, tmpbuf, ti.nhdr+ti.length); if (err) return err; } /* read ahead */ err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; crl->state.ti = ti; return 0; } /* Parse the signatureAlgorithm and the signature */ static gpg_error_t parse_signature (ksba_crl_t crl) { gpg_error_t err; struct tag_info ti = crl->state.ti; unsigned char tmpbuf[2048]; /* for the sig algo and bitstr */ size_t n, n2; /* We do read the stuff into a temporary buffer so that we can apply our parsing function for this structure */ /* read the algorithmIdentifier sequence */ if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_SEQUENCE && ti.is_constructed) ) return gpg_error (GPG_ERR_INV_CRL_OBJ); if (ti.ndef) return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); n = ti.nhdr + ti.length; if (n >= DIM(tmpbuf)) return gpg_error (GPG_ERR_TOO_LARGE); memcpy (tmpbuf, ti.buf, ti.nhdr); err = read_buffer (crl->reader, tmpbuf+ti.nhdr, ti.length); if (err) return err; /* and append the bit string */ err = _ksba_ber_read_tl (crl->reader, &ti); if (err) return err; if ( !(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_BIT_STRING && !ti.is_constructed) ) return gpg_error (GPG_ERR_INV_CRL_OBJ); n2 = ti.nhdr + ti.length; if (n + n2 >= DIM(tmpbuf)) return gpg_error (GPG_ERR_TOO_LARGE); memcpy (tmpbuf+n, ti.buf, ti.nhdr); err = read_buffer (crl->reader, tmpbuf+n+ti.nhdr, ti.length); if (err) return err; /* now parse it */ xfree (crl->sigval); crl->sigval = NULL; return _ksba_sigval_to_sexp (tmpbuf, n + n2, &crl->sigval); } /* The actual parser which should be used with a new CRL object and run in a loop until the the KSBA_SR_READY is encountered */ gpg_error_t ksba_crl_parse (ksba_crl_t crl, ksba_stop_reason_t *r_stopreason) { enum { sSTART, sCRLENTRY, sCRLEXT, sERROR } state = sERROR; ksba_stop_reason_t stop_reason; gpg_error_t err = 0; int got_entry = 0; if (!crl || !r_stopreason) return gpg_error (GPG_ERR_INV_VALUE); if (!crl->any_parse_done) { /* first time initialization of the stop reason */ *r_stopreason = 0; crl->any_parse_done = 1; } /* Calculate state from last reason */ stop_reason = *r_stopreason; *r_stopreason = KSBA_SR_RUNNING; switch (stop_reason) { case 0: state = sSTART; break; case KSBA_SR_BEGIN_ITEMS: case KSBA_SR_GOT_ITEM: state = sCRLENTRY; break; case KSBA_SR_END_ITEMS: state = sCRLEXT; break; case KSBA_SR_RUNNING: err = gpg_error (GPG_ERR_INV_STATE); break; default: err = gpg_error (GPG_ERR_BUG); break; } if (err) return err; /* Do the action */ switch (state) { case sSTART: err = parse_to_next_update (crl); break; case sCRLENTRY: err = parse_crl_entry (crl, &got_entry); break; case sCRLEXT: err = parse_crl_extensions (crl); if (!err) { if (crl->hash_fnc && crl->hashbuf.used) crl->hash_fnc (crl->hash_fnc_arg, crl->hashbuf.buffer, crl->hashbuf.used); crl->hashbuf.used = 0; err = parse_signature (crl); } break; default: err = gpg_error (GPG_ERR_INV_STATE); break; } if (err) return err; /* Calculate new stop reason */ switch (state) { case sSTART: stop_reason = KSBA_SR_BEGIN_ITEMS; break; case sCRLENTRY: stop_reason = got_entry? KSBA_SR_GOT_ITEM : KSBA_SR_END_ITEMS; break; case sCRLEXT: stop_reason = KSBA_SR_READY; break; default: break; } *r_stopreason = stop_reason; return 0; } diff --git a/tests/t-crl-parser.c b/tests/t-crl-parser.c index 4b9e980..bfba70a 100644 --- a/tests/t-crl-parser.c +++ b/tests/t-crl-parser.c @@ -1,340 +1,350 @@ /* t-crl-parser.c - basic test for the CRl parser. * Copyright (C) 2002, 2004, 2005 g10 Code GmbH * * This file is part of KSBA. * * KSBA is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * KSBA is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #undef ENABLE_HASH_LOGGING #ifdef ENABLE_HASH_LOGGING #define _GNU_SOURCE 1 #endif #include #include #include #include #include #include #include "../src/ksba.h" #include "t-common.h" #include "oidtranstbl.h" static void my_hasher (void *arg, const void *buffer, size_t length) { FILE *fp = arg; if (fp) { if ( fwrite (buffer, length, 1, fp) != 1 ) fail ("error writing to-be-hashed data"); } } /* Return the description for OID; if no description is available NULL is returned. */ static const char * get_oid_desc (const char *oid) { int i; if (oid) for (i=0; oidtranstbl[i].oid; i++) if (!strcmp (oidtranstbl[i].oid, oid)) return oidtranstbl[i].desc; return NULL; } static void print_names (int indent, ksba_name_t name) { int idx; const char *s; int indent_all; if ((indent_all = (indent < 0))) indent = - indent; if (!name) { fputs ("none\n", stdout); return; } for (idx=0; (s = ksba_name_enum (name, idx)); idx++) { char *p = ksba_name_get_uri (name, idx); printf ("%*s%s\n", idx||indent_all?indent:0, "", p?p:s); xfree (p); } } static void one_file (const char *fname) { gpg_error_t err; FILE *fp; ksba_reader_t r; ksba_crl_t crl; ksba_stop_reason_t stopreason; int count = 0; FILE *hashlog = NULL; #ifdef ENABLE_HASH_LOGGING { char *buf; if (asprintf (&buf, "%s.hash.log", fname) < 0) fail ("asprintf failed"); hashlog = fopen (buf, "wb"); if (!hashlog) fail ("can't create log file"); free (buf); } #endif printf ("*** checking `%s' ***\n", fname); fp = fopen (fname, "rb"); if (!fp) { fprintf (stderr, "%s:%d: can't open `%s': %s\n", __FILE__, __LINE__, fname, strerror (errno)); exit (1); } err = ksba_reader_new (&r); if (err) fail_if_err (err); err = ksba_reader_set_file (r, fp); fail_if_err (err); err = ksba_crl_new (&crl); if (err) fail_if_err (err); err = ksba_crl_set_reader (crl, r); fail_if_err (err); if (hashlog) ksba_crl_set_hash_function (crl, my_hasher, hashlog); do { err = ksba_crl_parse (crl, &stopreason); fail_if_err2 (fname, err); switch (stopreason) { case KSBA_SR_BEGIN_ITEMS: { const char *algoid; char *issuer; ksba_isotime_t this, next; algoid = ksba_crl_get_digest_algo (crl); printf ("digest algo: %s\n", algoid? algoid : "[none]"); + if (algoid && !strcmp (algoid, "1.2.840.113549.1.1.10")) + { + ksba_sexp_t pssparam; + + printf (" pss para: "); + pssparam = ksba_crl_get_sig_val (crl); + print_sexp (pssparam); + putchar ('\n'); + xfree (pssparam); + } err = ksba_crl_get_issuer (crl, &issuer); fail_if_err2 (fname, err); printf ("issuer: "); print_dn (issuer); xfree (issuer); putchar ('\n'); err = ksba_crl_get_update_times (crl, this, next); if (gpg_err_code (err) != GPG_ERR_INV_TIME) fail_if_err2 (fname, err); printf ("thisUpdate: "); print_time (this); putchar ('\n'); printf ("nextUpdate: "); print_time (next); putchar ('\n'); } break; case KSBA_SR_GOT_ITEM: { ksba_sexp_t serial; ksba_isotime_t rdate; ksba_crl_reason_t reason; err = ksba_crl_get_item (crl, &serial, rdate, &reason); fail_if_err2 (fname, err); printf ("CRL entry %d: s=", ++count); print_sexp_hex (serial); printf (", t="); print_time (rdate); printf (", r=%x\n", reason); xfree (serial); } break; case KSBA_SR_END_ITEMS: break; case KSBA_SR_READY: break; default: fail ("unknown stop reason"); } } while (stopreason != KSBA_SR_READY); if ( !ksba_crl_get_digest_algo (crl)) fail ("digest algorithm mismatch"); { ksba_name_t name1; ksba_sexp_t serial; ksba_sexp_t keyid; err = ksba_crl_get_auth_key_id (crl, &keyid, &name1, &serial); if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) { fputs ("AuthorityKeyIdentifier: ", stdout); if (gpg_err_code (err) == GPG_ERR_NO_DATA) fputs ("none\n", stdout); else { if (name1) { print_names (24, name1); ksba_name_release (name1); fputs (" serial: ", stdout); print_sexp_hex (serial); ksba_free (serial); } putchar ('\n'); if (keyid) { fputs (" keyIdentifier: ", stdout); print_sexp (keyid); ksba_free (keyid); putchar ('\n'); } } } else fail_if_err (err); } { ksba_sexp_t serial; err = ksba_crl_get_crl_number (crl, &serial); if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) { fputs ("crlNumber: ", stdout); if (gpg_err_code (err) == GPG_ERR_NO_DATA) fputs ("none", stdout); else { print_sexp (serial); ksba_free (serial); } putchar ('\n'); } else fail_if_err (err); } { int idx, crit; const char *oid; size_t derlen; for (idx=0; !(err=ksba_crl_get_extension (crl, idx, &oid, &crit, NULL, &derlen)); idx++) { const char *s = get_oid_desc (oid); printf ("%sExtn: %s%s%s%s (%lu octets)\n", crit? "Crit":"", s?" (":"", s?s:"", s?")":"", oid, (unsigned long)derlen); } if (err && gpg_err_code (err) != GPG_ERR_EOF && gpg_err_code (err) != GPG_ERR_NO_DATA ) fail_if_err (err); } { ksba_sexp_t sigval; sigval = ksba_crl_get_sig_val (crl); if (!sigval) fail ("signature value missing"); print_sexp (sigval); putchar ('\n'); xfree (sigval); } ksba_crl_release (crl); ksba_reader_release (r); fclose (fp); if (hashlog) fclose (hashlog); } int main (int argc, char **argv) { const char *srcdir = getenv ("srcdir"); if (!srcdir) srcdir = "."; if (argc > 1) { for (argc--, argv++; argc; argc--, argv++) one_file (*argv); } else { const char *files[] = { "crl_testpki_testpca.der", NULL }; int idx; for (idx=0; files[idx]; idx++) { char *fname; fname = xmalloc (strlen (srcdir) + 1 + strlen (files[idx]) + 1); strcpy (fname, srcdir); strcat (fname, "/"); strcat (fname, files[idx]); one_file (fname); xfree (fname); } } return 0; }