diff --git a/src/ocsp.c b/src/ocsp.c index e813166..21a203a 100644 --- a/src/ocsp.c +++ b/src/ocsp.c @@ -1,1703 +1,1710 @@ /* ocsp.c - OCSP (rfc2560) * Copyright (C) 2003, 2004, 2005, 2006, 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 "util.h" #include "cert.h" #include "convert.h" #include "keyinfo.h" #include "der-encoder.h" #include "ber-help.h" #include "ocsp.h" static const char oidstr_sha1[] = "1.3.14.3.2.26"; static const char oidstr_ocsp_basic[] = "1.3.6.1.5.5.7.48.1.1"; static const char oidstr_ocsp_nonce[] = "1.3.6.1.5.5.7.48.1.2"; #if 0 static void dump_hex (const unsigned char *p, size_t n) { if (!p) fputs ("none", stderr); else { for (; n; n--, p++) fprintf (stderr, " %02X", *p); } } #endif /* Create a new OCSP object and retrun it in R_OCSP. Return 0 on success or an error code. */ gpg_error_t ksba_ocsp_new (ksba_ocsp_t *r_ocsp) { *r_ocsp = xtrycalloc (1, sizeof **r_ocsp); if (!*r_ocsp) return gpg_error_from_syserror (); return 0; } static void release_ocsp_certlist (struct ocsp_certlist_s *cl) { while (cl) { struct ocsp_certlist_s *tmp = cl->next; ksba_cert_release (cl->cert); xfree (cl); cl = tmp; } } static void release_ocsp_extensions (struct ocsp_extension_s *ex) { while (ex) { struct ocsp_extension_s *tmp = ex->next; xfree (ex); ex = tmp; } } /* Release the OCSP object and all its resources. Passing NULL for OCSP is a valid nop. */ void ksba_ocsp_release (ksba_ocsp_t ocsp) { struct ocsp_reqitem_s *ri; if (!ocsp) return; xfree (ocsp->digest_oid); xfree (ocsp->request_buffer); for (; (ri=ocsp->requestlist); ri = ocsp->requestlist ) { ocsp->requestlist = ri->next; ksba_cert_release (ri->cert); ksba_cert_release (ri->issuer_cert); release_ocsp_extensions (ri->single_extensions); xfree (ri->serialno); } xfree (ocsp->sigval); xfree (ocsp->responder_id.name); xfree (ocsp->responder_id.keyid); release_ocsp_certlist (ocsp->received_certs); release_ocsp_extensions (ocsp->response_extensions); xfree (ocsp); } /* Set the hash algorithm to be used for signing the request to OID. Using this function will force the creation of a signed request. */ gpg_error_t ksba_ocsp_set_digest_algo (ksba_ocsp_t ocsp, const char *oid) { if (!ocsp || !oid || !*oid) return gpg_error (GPG_ERR_INV_VALUE); if (ocsp->digest_oid) xfree (ocsp->digest_oid); ocsp->digest_oid = xtrystrdup (oid); if (!ocsp->digest_oid) return gpg_error_from_syserror (); return 0; } gpg_error_t ksba_ocsp_set_requestor (ksba_ocsp_t ocsp, ksba_cert_t cert) { (void)ocsp; (void)cert; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); } /* Add the certificate CERT for which the status is to be requested and it's issuer certificate ISSUER_CERT to the context. This function may be called multiple time to create a list of targets to get combined into one actual request. */ gpg_error_t ksba_ocsp_add_target (ksba_ocsp_t ocsp, ksba_cert_t cert, ksba_cert_t issuer_cert) { struct ocsp_reqitem_s *ri; if (!ocsp || !cert || !issuer_cert) return gpg_error (GPG_ERR_INV_VALUE); ri = xtrycalloc (1, sizeof *ri); if (!ri) return gpg_error_from_syserror (); ksba_cert_ref (cert); ri->cert = cert; ksba_cert_ref (issuer_cert); ri->issuer_cert = issuer_cert; ri->next = ocsp->requestlist; ocsp->requestlist = ri; return 0; } /* Set the nonce to be used for the request to the content of the buffer NONCE of size NONCELEN. Libksba may have an upper limit of the allowed size of the nonce; if the supplied nonce is larger it will be truncated and the actual used length of the nonce returned. To detect the implementation limit (which should be considered as a good suggestion), the function may be called with NULL for NONCE, in which case the maximal usable noncelength is returned. The function returns the length of the nonce which will be used. */ size_t ksba_ocsp_set_nonce (ksba_ocsp_t ocsp, unsigned char *nonce, size_t noncelen) { if (!ocsp) return 0; if (!nonce) return sizeof ocsp->nonce; if (noncelen > sizeof ocsp->nonce) noncelen = sizeof ocsp->nonce; if (noncelen) { memcpy (ocsp->nonce, nonce, noncelen); } ocsp->noncelen = noncelen; return noncelen; } /* Compute the SHA-1 nameHash for the certificate CERT and put it in the buffer SHA1_BUFFER which must have been allocated to at least 20 bytes. */ static gpg_error_t issuer_name_hash (ksba_cert_t cert, unsigned char *sha1_buffer) { gpg_error_t err; const unsigned char *ptr; size_t length, dummy; err = _ksba_cert_get_subject_dn_ptr (cert, &ptr, &length); if (!err) { err = _ksba_hash_buffer (NULL, ptr, length, 20, sha1_buffer, &dummy); if (!err && dummy != 20) err = gpg_error (GPG_ERR_BUG); } return err; } /* Compute the SHA-1 hash of the public key of CERT and put it in teh buffer SHA1_BUFFER which must have been allocated with at least 20 bytes. */ static gpg_error_t issuer_key_hash (ksba_cert_t cert, unsigned char *sha1_buffer) { gpg_error_t err; const unsigned char *ptr; size_t length, dummy; err = _ksba_cert_get_public_key_ptr (cert, &ptr, &length); if (!err) { err = _ksba_hash_buffer (NULL, ptr, length, 20, sha1_buffer, &dummy); if (!err && dummy != 20) err = gpg_error (GPG_ERR_BUG); } return err; } /* Write the extensions for a request to WOUT. */ static gpg_error_t write_request_extensions (ksba_ocsp_t ocsp, ksba_writer_t wout) { gpg_error_t err; unsigned char *buf; size_t buflen; unsigned char *p; size_t derlen; ksba_writer_t w1 = NULL; ksba_writer_t w2 = NULL; if (!ocsp->noncelen) return 0; /* We do only support the nonce extension. */ /* Create writer objects for construction of the extension. */ err = ksba_writer_new (&w2); if (!err) err = ksba_writer_set_mem (w2, 256); if (!err) err = ksba_writer_new (&w1); if (!err) err = ksba_writer_set_mem (w1, 256); if (err) goto leave; - /* Write OID and and nonce. */ + /* Write OID and nonce. */ err = ksba_oid_from_str (oidstr_ocsp_nonce, &buf, &buflen); if (err) goto leave; err = _ksba_ber_write_tl (w1, TYPE_OBJECT_ID, CLASS_UNIVERSAL, 0, buflen); if (!err) err = ksba_writer_write (w1, buf, buflen); xfree (buf); buf = NULL; /* We known that the nonce is short enough to put the tag into 2 bytes, thus we write the encapsulating octet string directly with a fixed length. */ if (!err) err = _ksba_ber_write_tl (w1, TYPE_OCTET_STRING, CLASS_UNIVERSAL, 0, 2+ocsp->noncelen); if (!err) err = _ksba_ber_write_tl (w1, TYPE_OCTET_STRING, CLASS_UNIVERSAL, 0, ocsp->noncelen); if (!err) err = ksba_writer_write (w1, ocsp->nonce, ocsp->noncelen); /* Put a sequence around. */ p = ksba_writer_snatch_mem (w1, &derlen); if (!p) { err = ksba_writer_error (w1); goto leave; } err = _ksba_ber_write_tl (w2, TYPE_SEQUENCE, CLASS_UNIVERSAL, 1, derlen); if (!err) err = ksba_writer_write (w2, p, derlen); xfree (p); p = NULL; /* Put the sequence around all extensions. */ err = ksba_writer_set_mem (w1, 256); if (err) goto leave; p = ksba_writer_snatch_mem (w2, &derlen); if (!p) { err = ksba_writer_error (w2); goto leave; } err = _ksba_ber_write_tl (w1, TYPE_SEQUENCE, CLASS_UNIVERSAL, 1, derlen); if (!err) err = ksba_writer_write (w1, p, derlen); xfree (p); p = NULL; /* And put a context tag around everything. */ p = ksba_writer_snatch_mem (w1, &derlen); if (!p) { err = ksba_writer_error (w1); goto leave; } err = _ksba_ber_write_tl (wout, 2, CLASS_CONTEXT, 1, derlen); if (!err) err = ksba_writer_write (wout, p, derlen); xfree (p); p = NULL; leave: ksba_writer_release (w2); ksba_writer_release (w1); return err; } /* Build a request from the current context. The function checks that all necessary information have been set and stores the prepared request in the context. A subsequent ksba_ocsp_build_request may then be used to retrieve this request. Optional the requestmay be signed beofre calling ksba_ocsp_build_request. */ gpg_error_t ksba_ocsp_prepare_request (ksba_ocsp_t ocsp) { gpg_error_t err; struct ocsp_reqitem_s *ri; unsigned char *p; const unsigned char *der; size_t derlen; + struct tag_info ti; ksba_writer_t w1 = NULL; ksba_writer_t w2 = NULL; ksba_writer_t w3 = NULL; ksba_writer_t w4, w5, w6, w7; /* Used as aliases. */ if (!ocsp) return gpg_error (GPG_ERR_INV_VALUE); xfree (ocsp->request_buffer); ocsp->request_buffer = NULL; ocsp->request_buflen = 0; if (!ocsp->requestlist) return gpg_error (GPG_ERR_MISSING_ACTION); /* Create three writer objects for construction of the request. */ err = ksba_writer_new (&w3); if (!err) err = ksba_writer_set_mem (w3, 2048); if (!err) err = ksba_writer_new (&w2); if (!err) err = ksba_writer_new (&w1); if (err) goto leave; /* Loop over all single requests. */ for (ri=ocsp->requestlist; ri; ri = ri->next) { err = ksba_writer_set_mem (w2, 256); if (!err) err = ksba_writer_set_mem (w1, 256); if (err) goto leave; /* Write the AlgorithmIdentifier. */ err = _ksba_der_write_algorithm_identifier (w1, oidstr_sha1, NULL, 0); if (err) goto leave; /* Compute the issuerNameHash and write it into the CertID object. */ err = issuer_name_hash (ri->issuer_cert, ri->issuer_name_hash); if (!err) err = _ksba_ber_write_tl (w1, TYPE_OCTET_STRING, CLASS_UNIVERSAL, 0,20); if (!err) err = ksba_writer_write (w1, ri->issuer_name_hash, 20); if(err) goto leave; /* Compute the issuerKeyHash and write it. */ err = issuer_key_hash (ri->issuer_cert, ri->issuer_key_hash); if (!err) err = _ksba_ber_write_tl (w1, TYPE_OCTET_STRING, CLASS_UNIVERSAL, 0,20); if (!err) err = ksba_writer_write (w1, ri->issuer_key_hash, 20); if (err) goto leave; /* Write the serialNumber of the certificate to be checked. */ err = _ksba_cert_get_serial_ptr (ri->cert, &der, &derlen); if (!err) err = ksba_writer_write (w1, der, derlen); if (err) goto leave; - xfree (ri->serialno); - ri->serialno = xtrymalloc (derlen); - if (!ri->serialno) - { - err = gpg_error_from_syserror (); - goto leave; - } - memcpy (ri->serialno, der, derlen); - ri->serialnolen = derlen; + /* Store the integer value. */ + { + const unsigned char *tmpder = der; + size_t tmpderlen = derlen; + err = parse_integer (&tmpder, &tmpderlen, &ti); + if (err) + goto leave; + xfree (ri->serialno); + ri->serialno = xtrymalloc (tmpderlen); + if (!ri->serialno) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (ri->serialno, tmpder, tmpderlen); + ri->serialnolen = tmpderlen; + } /* Now write it out as a sequence to the outer certID object. */ p = ksba_writer_snatch_mem (w1, &derlen); if (!p) { err = ksba_writer_error (w1); goto leave; } err = _ksba_ber_write_tl (w2, TYPE_SEQUENCE, CLASS_UNIVERSAL, 1, derlen); if (!err) err = ksba_writer_write (w2, p, derlen); xfree (p); p = NULL; if (err) goto leave; /* Here we would write singleRequestExtensions. */ /* Now write it out as a sequence to the outer Request object. */ p = ksba_writer_snatch_mem (w2, &derlen); if (!p) { err = ksba_writer_error (w2); goto leave; } err = _ksba_ber_write_tl (w3, TYPE_SEQUENCE, CLASS_UNIVERSAL, 1, derlen); if (!err) err = ksba_writer_write (w3, p, derlen); xfree (p); p = NULL; if (err) goto leave; } /* End of looping over single requests. */ /* Reuse writers; for clarity, use new names. */ w4 = w1; w5 = w2; err = ksba_writer_set_mem (w4, 2048); if (!err) err = ksba_writer_set_mem (w5, 2048); if (err) goto leave; /* Put a sequence tag before the requestList. */ p = ksba_writer_snatch_mem (w3, &derlen); if (!p) { err = ksba_writer_error (w3); goto leave; } err = _ksba_ber_write_tl (w4, TYPE_SEQUENCE, CLASS_UNIVERSAL, 1, derlen); if (!err) err = ksba_writer_write (w4, p, derlen); xfree (p); p = NULL; if (err) goto leave; /* The requestExtensions go here. */ err = write_request_extensions (ocsp, w4); /* Write the tbsRequest. */ /* The version is default, thus we don't write it. */ /* The requesterName would go here. */ /* Write the requestList. */ p = ksba_writer_snatch_mem (w4, &derlen); if (!p) { err = ksba_writer_error (w4); goto leave; } err = _ksba_ber_write_tl (w5, TYPE_SEQUENCE, CLASS_UNIVERSAL, 1, derlen); if (!err) err = ksba_writer_write (w5, p, derlen); xfree (p); p = NULL; if (err) goto leave; /* Reuse writers; for clarity, use new names. */ w6 = w3; w7 = w4; err = ksba_writer_set_mem (w6, 2048); if (!err) err = ksba_writer_set_mem (w7, 2048); if (err) goto leave; /* Prepend a sequence tag. */ p = ksba_writer_snatch_mem (w5, &derlen); if (!p) { err = ksba_writer_error (w5); goto leave; } err = _ksba_ber_write_tl (w6, TYPE_SEQUENCE, CLASS_UNIVERSAL, 1, derlen); if (!err) err = ksba_writer_write (w6, p, derlen); xfree (p); p = NULL; if (err) goto leave; /* Write the ocspRequest. */ /* Note that we do not support the optional signature, because this saves us one writer object. */ /* Prepend a sequence tag. */ /* p = ksba_writer_snatch_mem (w6, &derlen); */ /* if (!p) */ /* { */ /* err = ksba_writer_error (w6); */ /* goto leave; */ /* } */ /* err = _ksba_ber_write_tl (w7, TYPE_SEQUENCE, CLASS_UNIVERSAL, */ /* 1, derlen); */ /* if (!err) */ /* err = ksba_writer_write (w7, p, derlen); */ /* xfree (p); p = NULL; */ /* if (err) */ /* goto leave; */ /* Read out the entire request. */ p = ksba_writer_snatch_mem (w6, &derlen); if (!p) { err = ksba_writer_error (w6); goto leave; } ocsp->request_buffer = p; ocsp->request_buflen = derlen; /* Ready. */ leave: ksba_writer_release (w3); ksba_writer_release (w2); ksba_writer_release (w1); return err; } gpg_error_t ksba_ocsp_hash_request (ksba_ocsp_t ocsp, void (*hasher)(void *, const void *, size_t length), void *hasher_arg) { (void)ocsp; (void)hasher; (void)hasher_arg; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); } gpg_error_t ksba_ocsp_set_sig_val (ksba_ocsp_t ocsp, ksba_const_sexp_t sigval) { (void)ocsp; (void)sigval; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); } gpg_error_t ksba_ocsp_add_cert (ksba_ocsp_t ocsp, ksba_cert_t cert) { (void)ocsp; (void)cert; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); } /* Build a request from the current context. The function checks that all necessary information have been set and then returns an allocated buffer with the resulting request. */ gpg_error_t ksba_ocsp_build_request (ksba_ocsp_t ocsp, unsigned char **r_buffer, size_t *r_buflen) { gpg_error_t err; if (!ocsp || !r_buffer || !r_buflen) return gpg_error (GPG_ERR_INV_VALUE); *r_buffer = NULL; *r_buflen = 0; if (!ocsp->requestlist) return gpg_error (GPG_ERR_MISSING_ACTION); if (!ocsp->request_buffer) { /* No prepare done, do it now. */ err = ksba_ocsp_prepare_request (ocsp); if (err) return err; assert (ocsp->request_buffer); } *r_buffer = ocsp->request_buffer; *r_buflen = ocsp->request_buflen; ocsp->request_buffer = NULL; ocsp->request_buflen = 0; return 0; } /* Parse the response extensions and store them aways. While doing this we also check the nonce extension. A typical data ASN.1 blob with only the nonce extension as passed to this function is: SEQUENCE { SEQUENCE { OBJECT IDENTIFIER ocspNonce (1 3 6 1 5 5 7 48 1 2) OCTET STRING, encapsulates { INTEGER 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 } } } */ static int parse_response_extensions (ksba_ocsp_t ocsp, const unsigned char *data, size_t datalen) { gpg_error_t err; struct tag_info ti; size_t length; char *oid = NULL; assert (!ocsp->response_extensions); err = parse_sequence (&data, &datalen, &ti); if (err) goto leave; length = ti.length; while (length) { struct ocsp_extension_s *ex; int is_crit; err = parse_sequence (&data, &datalen, &ti); if (err) goto leave; if (length < ti.nhdr + ti.length) { err = gpg_error (GPG_ERR_BAD_BER); goto leave; } length -= ti.nhdr + ti.length; xfree (oid); err = parse_object_id_into_str (&data, &datalen, &oid); if (err) goto leave; is_crit = 0; err = parse_optional_boolean (&data, &datalen, &is_crit); if (err) goto leave; err = parse_octet_string (&data, &datalen, &ti); if (err) goto leave; if (!strcmp (oid, oidstr_ocsp_nonce)) { err = parse_octet_string (&data, &datalen, &ti); if (err) goto leave; if (ocsp->noncelen != ti.length || memcmp (ocsp->nonce, data, ti.length)) ocsp->bad_nonce = 1; else ocsp->good_nonce = 1; } ex = xtrymalloc (sizeof *ex + strlen (oid) + ti.length); if (!ex) { err = gpg_error_from_syserror (); goto leave; } ex->crit = is_crit; strcpy (ex->data, oid); ex->data[strlen (oid)] = 0; ex->off = strlen (oid) + 1; ex->len = ti.length; memcpy (ex->data + ex->off, data, ti.length); ex->next = ocsp->response_extensions; ocsp->response_extensions = ex; parse_skip (&data, &datalen, &ti); /* Skip the octet string / integer. */ } leave: xfree (oid); return err; } /* Parse single extensions and store them away. */ static int parse_single_extensions (struct ocsp_reqitem_s *ri, const unsigned char *data, size_t datalen) { gpg_error_t err; struct tag_info ti; size_t length; char *oid = NULL; assert (ri && !ri->single_extensions); err = parse_sequence (&data, &datalen, &ti); if (err) goto leave; length = ti.length; while (length) { struct ocsp_extension_s *ex; int is_crit; err = parse_sequence (&data, &datalen, &ti); if (err) goto leave; if (length < ti.nhdr + ti.length) { err = gpg_error (GPG_ERR_BAD_BER); goto leave; } length -= ti.nhdr + ti.length; xfree (oid); err = parse_object_id_into_str (&data, &datalen, &oid); if (err) goto leave; is_crit = 0; err = parse_optional_boolean (&data, &datalen, &is_crit); if (err) goto leave; err = parse_octet_string (&data, &datalen, &ti); if (err) goto leave; ex = xtrymalloc (sizeof *ex + strlen (oid) + ti.length); if (!ex) { err = gpg_error_from_syserror (); goto leave; } ex->crit = is_crit; strcpy (ex->data, oid); ex->data[strlen (oid)] = 0; ex->off = strlen (oid) + 1; ex->len = ti.length; memcpy (ex->data + ex->off, data, ti.length); ex->next = ri->single_extensions; ri->single_extensions = ex; parse_skip (&data, &datalen, &ti); /* Skip the octet string / integer. */ } leave: xfree (oid); return err; } /* Parse the first part of a response: OCSPResponse ::= SEQUENCE { responseStatus OCSPResponseStatus, responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } OCSPResponseStatus ::= ENUMERATED { successful (0), --Response has valid confirmations malformedRequest (1), --Illegal confirmation request internalError (2), --Internal error in issuer tryLater (3), --Try again later --(4) is not used sigRequired (5), --Must sign the request unauthorized (6) --Request unauthorized } ResponseBytes ::= SEQUENCE { responseType OBJECT IDENTIFIER, response OCTET STRING } On success the RESPONSE_STATUS field of OCSP will be set to the response status and DATA will now point to the first byte in the octet string of the response; RLEN will be set to the length of this octet string. Note thate DATALEN is also updated but might point to a value larger than RLEN points to, if the provided data is a part of a larger image. */ static gpg_error_t parse_response_status (ksba_ocsp_t ocsp, unsigned char const **data, size_t *datalen, size_t *rlength) { gpg_error_t err; struct tag_info ti; char *oid; *rlength = 0; /* Parse the OCSPResponse sequence. */ err = parse_sequence (data, datalen, &ti); if (err) return err; /* Parse the OCSPResponseStatus. */ err = parse_enumerated (data, datalen, &ti, 1); if (err) return err; switch (**data) { case 0: ocsp->response_status = KSBA_OCSP_RSPSTATUS_SUCCESS; break; case 1: ocsp->response_status = KSBA_OCSP_RSPSTATUS_MALFORMED; break; case 2: ocsp->response_status = KSBA_OCSP_RSPSTATUS_INTERNAL; break; case 3: ocsp->response_status = KSBA_OCSP_RSPSTATUS_TRYLATER; break; case 5: ocsp->response_status = KSBA_OCSP_RSPSTATUS_SIGREQUIRED; break; case 6: ocsp->response_status = KSBA_OCSP_RSPSTATUS_UNAUTHORIZED; break; default: ocsp->response_status = KSBA_OCSP_RSPSTATUS_OTHER; break; } parse_skip (data, datalen, &ti); if (ocsp->response_status) return 0; /* This is an error reponse; we have to stop here. */ /* We have a successful reponse status, thus we check that ResponseBytes are actually available. */ err = parse_context_tag (data, datalen, &ti, 0); if (err) return err; err = parse_sequence (data, datalen, &ti); if (err) return err; err = parse_object_id_into_str (data, datalen, &oid); if (err) return err; if (strcmp (oid, oidstr_ocsp_basic)) { xfree (oid); return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL); } xfree (oid); /* Check that the next field is an octet string. */ err = parse_octet_string (data, datalen, &ti); if (err) return err; *rlength = ti.length; return 0; } /* Parse the object: SingleResponse ::= SEQUENCE { certID CertID, certStatus CertStatus, thisUpdate GeneralizedTime, nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, singleExtensions [1] EXPLICIT Extensions OPTIONAL } CertStatus ::= CHOICE { good [0] IMPLICIT NULL, revoked [1] IMPLICIT RevokedInfo, unknown [2] IMPLICIT UnknownInfo } RevokedInfo ::= SEQUENCE { revocationTime GeneralizedTime, revocationReason [0] EXPLICIT CRLReason OPTIONAL } UnknownInfo ::= NULL -- this can be replaced with an enumeration */ static gpg_error_t parse_single_response (ksba_ocsp_t ocsp, unsigned char const **data, size_t *datalen) { gpg_error_t err; struct tag_info ti; const unsigned char *savedata; const unsigned char *endptr; size_t savedatalen; size_t n; char *oid; ksba_isotime_t this_update, next_update, revocation_time; int look_for_request; const unsigned char *name_hash; const unsigned char *key_hash; const unsigned char *serialno; size_t serialnolen; struct ocsp_reqitem_s *request_item = NULL; /* The SingeResponse sequence. */ err = parse_sequence (data, datalen, &ti); if (err) return err; endptr = *data + ti.length; /* The CertID is SEQUENCE { hashAlgorithm AlgorithmIdentifier, issuerNameHash OCTET STRING, -- Hash of Issuer's DN issuerKeyHash OCTET STRING, -- Hash of Issuers public key serialNumber CertificateSerialNumber } */ err = parse_sequence (data, datalen, &ti); if (err) return err; err = _ksba_parse_algorithm_identifier (*data, *datalen, &n, &oid); if (err) return err; assert (n <= *datalen); *data += n; *datalen -= n; - /* fprintf (stderr, "algorithmIdentifier is `%s'\n", oid); */ + /* gpgrt_log_debug ("algorithmIdentifier is `%s'\n", oid); */ look_for_request = !strcmp (oid, oidstr_sha1); xfree (oid); err = parse_octet_string (data, datalen, &ti); if (err) return err; name_hash = *data; /* fprintf (stderr, "issuerNameHash="); */ /* dump_hex (*data, ti.length); */ /* putc ('\n', stderr); */ if (ti.length != 20) look_for_request = 0; /* Can't be a SHA-1 digest. */ parse_skip (data, datalen, &ti); err = parse_octet_string (data, datalen, &ti); if (err) return err; key_hash = *data; /* fprintf (stderr, "issuerKeyHash="); */ /* dump_hex (*data, ti.length); */ /* putc ('\n', stderr); */ if (ti.length != 20) look_for_request = 0; /* Can't be a SHA-1 digest. */ parse_skip (data, datalen, &ti); err= parse_integer (data, datalen, &ti); if (err) return err; serialno = *data; serialnolen = ti.length; /* fprintf (stderr, "serialNumber="); */ /* dump_hex (*data, ti.length); */ /* putc ('\n', stderr); */ parse_skip (data, datalen, &ti); if (look_for_request) { for (request_item = ocsp->requestlist; request_item; request_item = request_item->next) if (!memcmp (request_item->issuer_name_hash, name_hash, 20) - && !memcmp (request_item->issuer_key_hash, key_hash, 20) - && request_item->serialnolen == serialnolen + && !memcmp (request_item->issuer_key_hash, key_hash, 20) + && request_item->serialnolen == serialnolen && !memcmp (request_item->serialno, serialno, serialnolen)) - break; /* Got it. */ + break; /* Got it. */ } /* CertStatus ::= CHOICE { good [0] IMPLICIT NULL, revoked [1] IMPLICIT RevokedInfo, unknown [2] IMPLICIT UnknownInfo } */ *revocation_time = 0; err = _ksba_ber_parse_tl (data, datalen, &ti); if (err) return err; if (ti.length > *datalen) return gpg_error (GPG_ERR_BAD_BER); else if (ti.class == CLASS_CONTEXT && ti.tag == 0 && !ti.is_constructed) { /* good */ if (!ti.length) ; /* Cope with zero length objects. */ else if (*datalen && !**data) { /* Skip the NULL. */ (*datalen)--; (*data)++; } else return gpg_error (GPG_ERR_INV_OBJ); if (request_item) request_item->status = KSBA_STATUS_GOOD; } else if (ti.class == CLASS_CONTEXT && ti.tag == 1 && ti.is_constructed) { /* revoked */ ksba_crl_reason_t reason = KSBA_CRLREASON_UNSPECIFIED; err = parse_asntime_into_isotime (data, datalen, revocation_time); if (err) return err; /* fprintf (stderr, "revocationTime=%s\n", revocation_time); */ savedata = *data; savedatalen = *datalen; err = parse_context_tag (data, datalen, &ti, 0); if (err) { *data = savedata; *datalen = savedatalen; } else { /* Got a revocationReason. */ err = parse_enumerated (data, datalen, &ti, 1); if (err) return err; switch (**data) { case 0: reason = KSBA_CRLREASON_UNSPECIFIED; break; case 1: reason = KSBA_CRLREASON_KEY_COMPROMISE; break; case 2: reason = KSBA_CRLREASON_CA_COMPROMISE; break; case 3: reason = KSBA_CRLREASON_AFFILIATION_CHANGED; break; case 4: reason = KSBA_CRLREASON_SUPERSEDED; break; case 5: reason = KSBA_CRLREASON_CESSATION_OF_OPERATION; break; case 6: reason = KSBA_CRLREASON_CERTIFICATE_HOLD; break; case 8: reason = KSBA_CRLREASON_REMOVE_FROM_CRL; break; case 9: reason = KSBA_CRLREASON_PRIVILEGE_WITHDRAWN; break; case 10: reason = KSBA_CRLREASON_AA_COMPROMISE; break; default: reason = KSBA_CRLREASON_OTHER; break; } parse_skip (data, datalen, &ti); } /* fprintf (stderr, "revocationReason=%04x\n", reason); */ if (request_item) { request_item->status = KSBA_STATUS_REVOKED; _ksba_copy_time (request_item->revocation_time, revocation_time); request_item->revocation_reason = reason; } } else if (ti.class == CLASS_CONTEXT && ti.tag == 2 && !ti.is_constructed && *datalen) { /* unknown */ if (!ti.length) ; /* Cope with zero length objects. */ else if (!**data) { /* Skip the NULL. */ (*datalen)--; (*data)++; } else /* The comment indicates that an enumeration may come here. */ { err = parse_enumerated (data, datalen, &ti, 0); if (err) return err; fprintf (stderr, "libksba: unknownReason with an enum of " "length %u detected\n", (unsigned int)ti.length); parse_skip (data, datalen, &ti); } if (request_item) request_item->status = KSBA_STATUS_UNKNOWN; } else err = gpg_error (GPG_ERR_INV_OBJ); /* thisUpdate. */ err = parse_asntime_into_isotime (data, datalen, this_update); if (err) return err; -/* fprintf (stderr, "thisUpdate=%s\n", this_update); */ if (request_item) _ksba_copy_time (request_item->this_update, this_update); /* nextUpdate is optional. */ if (*data >= endptr) return 0; *next_update = 0; err = _ksba_ber_parse_tl (data, datalen, &ti); if (err) return err; if (ti.length > *datalen) return gpg_error (GPG_ERR_BAD_BER); else if (ti.class == CLASS_CONTEXT && ti.tag == 0 && ti.is_constructed) { /* have nextUpdate */ err = parse_asntime_into_isotime (data, datalen, next_update); if (err) return err; -/* fprintf (stderr, "nextUpdate=%s\n", next_update); */ if (request_item) _ksba_copy_time (request_item->next_update, next_update); } else if (ti.class == CLASS_CONTEXT && ti.tag == 1 && ti.is_constructed) { /* Undo that read. */ *data -= ti.nhdr; *datalen += ti.nhdr; } else err = gpg_error (GPG_ERR_INV_OBJ); /* singleExtensions is optional */ if (*data >= endptr) return 0; err = _ksba_ber_parse_tl (data, datalen, &ti); if (err) return err; if (ti.length > *datalen) return gpg_error (GPG_ERR_BAD_BER); if (ti.class == CLASS_CONTEXT && ti.tag == 1 && ti.is_constructed) { if (request_item) { err = parse_single_extensions (request_item, *data, ti.length); if (err) return err; } parse_skip (data, datalen, &ti); } else err = gpg_error (GPG_ERR_INV_OBJ); return 0; } /* Parse the object: ResponseData ::= SEQUENCE { version [0] EXPLICIT Version DEFAULT v1, responderID ResponderID, producedAt GeneralizedTime, responses SEQUENCE OF SingleResponse, responseExtensions [1] EXPLICIT Extensions OPTIONAL } ResponderID ::= CHOICE { byName [1] Name, byKey [2] KeyHash } */ static gpg_error_t parse_response_data (ksba_ocsp_t ocsp, unsigned char const **data, size_t *datalen) { gpg_error_t err; struct tag_info ti; const unsigned char *savedata; size_t savedatalen; size_t responses_length; /* The out er sequence. */ err = parse_sequence (data, datalen, &ti); if (err) return err; /* The optional version field. */ savedata = *data; savedatalen = *datalen; err = parse_context_tag (data, datalen, &ti, 0); if (err) { *data = savedata; *datalen = savedatalen; } else { /* FIXME: check that the version matches. */ parse_skip (data, datalen, &ti); } /* The responderID field. */ assert (!ocsp->responder_id.name); assert (!ocsp->responder_id.keyid); err = _ksba_ber_parse_tl (data, datalen, &ti); if (err) return err; if (ti.length > *datalen) return gpg_error (GPG_ERR_BAD_BER); else if (ti.class == CLASS_CONTEXT && ti.tag == 1 && ti.is_constructed) { /* byName. */ err = _ksba_derdn_to_str (*data, ti.length, &ocsp->responder_id.name); if (err) return err; parse_skip (data, datalen, &ti); } else if (ti.class == CLASS_CONTEXT && ti.tag == 2 && ti.is_constructed) { /* byKey. */ err = parse_octet_string (data, datalen, &ti); if (err) return err; if (!ti.length) return gpg_error (GPG_ERR_INV_OBJ); /* Zero length key id. */ ocsp->responder_id.keyid = xtrymalloc (ti.length); if (!ocsp->responder_id.keyid) return gpg_error_from_syserror (); memcpy (ocsp->responder_id.keyid, *data, ti.length); ocsp->responder_id.keyidlen = ti.length; parse_skip (data, datalen, &ti); } else err = gpg_error (GPG_ERR_INV_OBJ); /* The producedAt field. */ err = parse_asntime_into_isotime (data, datalen, ocsp->produced_at); if (err) return err; /* The responses field set. */ err = parse_sequence (data, datalen, &ti); if (err ) return err; responses_length = ti.length; while (responses_length) { savedatalen = *datalen; err = parse_single_response (ocsp, data, datalen); if (err) return err; assert (responses_length >= savedatalen - *datalen); responses_length -= savedatalen - *datalen; } /* The optional responseExtensions set. */ savedata = *data; savedatalen = *datalen; err = parse_context_tag (data, datalen, &ti, 1); if (!err) { err = parse_response_extensions (ocsp, *data, ti.length); if (err) return err; parse_skip (data, datalen, &ti); } else if (gpg_err_code (err) == GPG_ERR_INV_OBJ) { *data = savedata; *datalen = savedatalen; } else return err; return 0; } /* Parse the entire response message pointed to by MSG of length MSGLEN. */ static gpg_error_t parse_response (ksba_ocsp_t ocsp, const unsigned char *msg, size_t msglen) { gpg_error_t err; struct tag_info ti; const unsigned char *msgstart; const unsigned char *endptr; const char *s; size_t len; msgstart = msg; err = parse_response_status (ocsp, &msg, &msglen, &len); if (err) return err; msglen = len; /* We don't care about any extra bytes provided to us. */ if (ocsp->response_status) { /* fprintf (stderr,"response status found to be %d - stop\n", */ /* ocsp->response_status); */ return 0; } /* Now that we are sure that it is a BasicOCSPResponse, we can parse the really important things: BasicOCSPResponse ::= SEQUENCE { tbsResponseData ResponseData, signatureAlgorithm AlgorithmIdentifier, signature BIT STRING, certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } */ err = parse_sequence (&msg, &msglen, &ti); if (err) return err; endptr = msg + ti.length; ocsp->hash_offset = msg - msgstart; err = parse_response_data (ocsp, &msg, &msglen); if (err) return err; ocsp->hash_length = msg - msgstart - ocsp->hash_offset; /* The signatureAlgorithm and the signature. We only need to get the length of both objects and let a specialized function do the actual parsing. */ s = msg; len = msglen; err = parse_sequence (&msg, &msglen, &ti); if (err) return err; parse_skip (&msg, &msglen, &ti); err= _ksba_ber_parse_tl (&msg, &msglen, &ti); if (err) return err; if (!(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_BIT_STRING && !ti.is_constructed) ) err = gpg_error (GPG_ERR_INV_OBJ); else if (!ti.length) err = gpg_error (GPG_ERR_TOO_SHORT); else if (ti.length > msglen) err = gpg_error (GPG_ERR_BAD_BER); parse_skip (&msg, &msglen, &ti); len = len - msglen; xfree (ocsp->sigval); ocsp->sigval = NULL; err = _ksba_sigval_to_sexp (s, len, &ocsp->sigval); if (err) return err; /* Parse the optional sequence of certificates. */ if (msg >= endptr) return 0; /* It's optional, so stop now. */ err = parse_context_tag (&msg, &msglen, &ti, 0); if (gpg_err_code (err) == GPG_ERR_INV_OBJ) return 0; /* Not the right tag. Stop here. */ if (err) return err; err = parse_sequence (&msg, &msglen, &ti); if (err) return err; if (ti.ndef) return gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); { ksba_cert_t cert; struct ocsp_certlist_s *cl, **cl_tail; assert (!ocsp->received_certs); cl_tail = &ocsp->received_certs; endptr = msg + ti.length; while (msg < endptr) { /* Find the length of the certificate. */ s = msg; err = parse_sequence (&msg, &msglen, &ti); if (err) return err; err = ksba_cert_new (&cert); if (err) return err; err = ksba_cert_init_from_mem (cert, msg - ti.nhdr, ti.nhdr + ti.length); if (err) { ksba_cert_release (cert); return err; } parse_skip (&msg, &msglen, &ti); cl = xtrycalloc (1, sizeof *cl); if (!cl) { err = gpg_error_from_syserror (); ksba_cert_release (cert); return err; } cl->cert = cert; *cl_tail = cl; cl_tail = &cl->next; } } return 0; } /* Given the OCSP context and a binary reponse message of MSGLEN bytes in MSG, this fucntion parses the response and prepares it for signature verification. The status from the server is returned in RESPONSE_STATUS and must be checked even if the function returns without an error. */ gpg_error_t ksba_ocsp_parse_response (ksba_ocsp_t ocsp, const unsigned char *msg, size_t msglen, ksba_ocsp_response_status_t *response_status) { gpg_error_t err; struct ocsp_reqitem_s *ri; if (!ocsp || !msg || !msglen || !response_status) return gpg_error (GPG_ERR_INV_VALUE); if (!ocsp->requestlist) return gpg_error (GPG_ERR_MISSING_ACTION); /* Reset the fields used to track the response. This is so that we can use the parse function a second time for the same request. This is useful in case of a TryLater response status. */ ocsp->response_status = KSBA_OCSP_RSPSTATUS_NONE; release_ocsp_certlist (ocsp->received_certs); release_ocsp_extensions (ocsp->response_extensions); ocsp->received_certs = NULL; ocsp->hash_length = 0; ocsp->bad_nonce = 0; ocsp->good_nonce = 0; xfree (ocsp->responder_id.name); ocsp->responder_id.name = NULL; xfree (ocsp->responder_id.keyid); ocsp->responder_id.keyid = NULL; for (ri=ocsp->requestlist; ri; ri = ri->next) { ri->status = KSBA_STATUS_NONE; *ri->this_update = 0; *ri->next_update = 0; *ri->revocation_time = 0; ri->revocation_reason = 0; release_ocsp_extensions (ri->single_extensions); } /* Run the actual parser. */ err = parse_response (ocsp, msg, msglen); *response_status = ocsp->response_status; /* FIXME: find duplicates in the request list and set them to the same status. */ if (*response_status == KSBA_OCSP_RSPSTATUS_SUCCESS) if (ocsp->bad_nonce || (ocsp->noncelen && !ocsp->good_nonce)) *response_status = KSBA_OCSP_RSPSTATUS_REPLAYED; return err; } /* Return the digest algorithm to be used for the signature or NULL in case of an error. The returned pointer is valid as long as the context is valid and no other ksba_ocsp_parse_response or ksba_ocsp_build_request has been used. */ const char * ksba_ocsp_get_digest_algo (ksba_ocsp_t ocsp) { return ocsp? ocsp->digest_oid : NULL; } /* Hash the data of the response using the hash function HASHER which will be passed HASHER_ARG as its first argument and a pointer and a length of the data to be hashed. This hash function might be called several times and should update the hash context. The algorithm to be used for the hashing can be retrieved using ksba_ocsp_get_digest_algo. Note that MSG and MSGLEN should be indentical to the values passed to ksba_ocsp_parse_response. */ gpg_error_t ksba_ocsp_hash_response (ksba_ocsp_t ocsp, const unsigned char *msg, size_t msglen, void (*hasher)(void *, const void *, size_t length), void *hasher_arg) { if (!ocsp || !msg || !hasher) return gpg_error (GPG_ERR_INV_VALUE); if (!ocsp->hash_length) return gpg_error (GPG_ERR_MISSING_ACTION); if (ocsp->hash_offset + ocsp->hash_length >= msglen) return gpg_error (GPG_ERR_CONFLICT); hasher (hasher_arg, msg + ocsp->hash_offset, ocsp->hash_length); return 0; } /* 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 and that function may be called only once after a successful ksba_ocsp_parse_response. Returns NULL for an invalid handle or if no signature is available. If PRODUCED_AT is not NULL, it will receive the time the response was signed. */ ksba_sexp_t ksba_ocsp_get_sig_val (ksba_ocsp_t ocsp, ksba_isotime_t produced_at) { ksba_sexp_t p; if (produced_at) *produced_at = 0; if (!ocsp || !ocsp->sigval ) return NULL; if (produced_at) _ksba_copy_time (produced_at, ocsp->produced_at); p = ocsp->sigval; ocsp->sigval = NULL; return p; } /* Return the responder ID for the current response into R_NAME or into R_KEYID. On sucess either R_NAME or R_KEYID will receive an allocated object. If R_NAME or R_KEYID has been passed as NULL but a value is available the errorcode GPG_ERR_NO_DATA is returned. Caller must release the values stored at R_NAME or R_KEYID; the function stores NULL tehre in case of an error. */ gpg_error_t ksba_ocsp_get_responder_id (ksba_ocsp_t ocsp, char **r_name, ksba_sexp_t *r_keyid) { if (r_name) *r_name = NULL; if (r_keyid) *r_keyid = NULL; if (!ocsp) return gpg_error (GPG_ERR_INV_VALUE); if (ocsp->responder_id.name && r_name) { *r_name = xtrystrdup (ocsp->responder_id.name); if (!*r_name) return gpg_error_from_syserror (); } else if (ocsp->responder_id.keyid && r_keyid) { char numbuf[50]; size_t numbuflen; sprintf (numbuf,"(%lu:", (unsigned long)ocsp->responder_id.keyidlen); numbuflen = strlen (numbuf); *r_keyid = xtrymalloc (numbuflen + ocsp->responder_id.keyidlen + 2); if (!*r_keyid) return gpg_error_from_syserror (); strcpy (*r_keyid, numbuf); memcpy (*r_keyid+numbuflen, ocsp->responder_id.keyid, ocsp->responder_id.keyidlen); (*r_keyid)[numbuflen + ocsp->responder_id.keyidlen] = ')'; (*r_keyid)[numbuflen + ocsp->responder_id.keyidlen + 1] = 0; } else return gpg_error (GPG_ERR_NO_DATA); return 0; } /* Get optional certificates out of a response. The caller may use * this in a loop to get all certificates. The returned certificate * is a shallow copy of the original one; the caller must still use * ksba_cert_release() to free it. Returns: A certificate object or * NULL for end of list or error. */ ksba_cert_t ksba_ocsp_get_cert (ksba_ocsp_t ocsp, int idx) { struct ocsp_certlist_s *cl; if (!ocsp || idx < 0) return NULL; for (cl=ocsp->received_certs; cl && idx; cl = cl->next, idx--) ; if (!cl) return NULL; ksba_cert_ref (cl->cert); return cl->cert; } /* Return the status of the certificate CERT for the last response done on the context OCSP. CERT must be the same certificate as used for the request; only a shallow compare is done (i.e. the pointers are compared). R_STATUS returns the status value, R_THIS_UPDATE and R_NEXT_UPDATE are the corresponding OCSP response values, R_REVOCATION_TIME is only set to the revocation time if the indicated status is revoked, R_REASON will be set to the reason given for a revocation. All the R_* arguments may be given as NULL if the value is not required. The function return 0 on success, GPG_ERR_NOT_FOUND if CERT was not used in the request or any other error code. Note that the caller should have checked the signature of the entire reponse to be good before using the stati retruned by this function. */ gpg_error_t ksba_ocsp_get_status (ksba_ocsp_t ocsp, ksba_cert_t cert, ksba_status_t *r_status, ksba_isotime_t r_this_update, ksba_isotime_t r_next_update, ksba_isotime_t r_revocation_time, ksba_crl_reason_t *r_reason) { struct ocsp_reqitem_s *ri; if (!ocsp || !cert || !r_status) return gpg_error (GPG_ERR_INV_VALUE); if (!ocsp->requestlist) return gpg_error (GPG_ERR_MISSING_ACTION); /* Find the certificate. We don't care about the issuer certificate and stop at the first match. The implementation may be optimized by keeping track of the last certificate found to start with the next one then. Given that a usual request consists only of a few certificates, this does not make much sense in reality. */ for (ri=ocsp->requestlist; ri; ri = ri->next) if (ri->cert == cert) break; if (!ri) return gpg_error (GPG_ERR_NOT_FOUND); if (r_status) *r_status = ri->status; if (r_this_update) _ksba_copy_time (r_this_update, ri->this_update); if (r_next_update) _ksba_copy_time (r_next_update, ri->next_update); if (r_revocation_time) _ksba_copy_time (r_revocation_time, ri->revocation_time); if (r_reason) *r_reason = ri->revocation_reason; return 0; } /* WARNING: The returned values ares only valid as long as no other ocsp function is called on the same context. */ gpg_error_t ksba_ocsp_get_extension (ksba_ocsp_t ocsp, ksba_cert_t cert, int idx, char const **r_oid, int *r_crit, unsigned char const **r_der, size_t *r_derlen) { struct ocsp_extension_s *ex; if (!ocsp) return gpg_error (GPG_ERR_INV_VALUE); if (!ocsp->requestlist) return gpg_error (GPG_ERR_MISSING_ACTION); if (idx < 0) return gpg_error (GPG_ERR_INV_INDEX); if (cert) { /* Return extensions for the certificate (singleExtensions). */ struct ocsp_reqitem_s *ri; for (ri=ocsp->requestlist; ri; ri = ri->next) if (ri->cert == cert) break; if (!ri) return gpg_error (GPG_ERR_NOT_FOUND); for (ex=ri->single_extensions; ex && idx; ex = ex->next, idx--) ; if (!ex) return gpg_error (GPG_ERR_EOF); /* No more extensions. */ } else { /* Return extensions for the response (responseExtensions). */ for (ex=ocsp->response_extensions; ex && idx; ex = ex->next, idx--) ; if (!ex) return gpg_error (GPG_ERR_EOF); /* No more extensions. */ } if (r_oid) *r_oid = ex->data; if (r_crit) *r_crit = ex->crit; if (r_der) *r_der = ex->data + ex->off; if (r_derlen) *r_derlen = ex->len; return 0; }