diff --git a/src/Makefile.am b/src/Makefile.am index 0286451..0f1fe72 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,141 +1,141 @@ # Makefile.am - for the KSBA ASN.1 and X.509 library # Copyright (C) 2001, 2002, 2004, 2005, 2007 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 . ## Process this file with automake to produce Makefile.in pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = ksba.pc asn1_modules = tmttv2.asn cms.asn buildtool_src = asn1-gentables.c gen-help.c gen-help.h EXTRA_DIST = ksba.h.in ksba-config.in $(asn1_modules) ksba.m4 \ libksba.vers libksba.def \ asn1-parse.c asn1-tables.c $(buildtool_src) ksba.pc.in BUILT_SOURCES = asn1-parse.c asn1-tables.c bin_SCRIPTS = ksba-config nodist_include_HEADERS = ksba.h lib_LTLIBRARIES = libksba.la noinst_PROGRAMS = ber-dump m4datadir = $(datadir)/aclocal m4data_DATA = ksba.m4 CLEANFILES = asn1-gentables DISTCLEANFILES = asn1-tables.c AM_CPPFLAGS = -I$(top_builddir)/gl -I$(top_srcdir)/gl AM_CFLAGS = $(GPG_ERROR_CFLAGS) $(COVERAGE_CFLAGS) if HAVE_LD_VERSION_SCRIPT libksba_version_script_cmd = -Wl,--version-script=$(srcdir)/libksba.vers else libksba_version_script_cmd = endif if HAVE_W32_SYSTEM RCCOMPILE = $(RC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ $(libksba_la_CPPFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) LTRCCOMPILE = $(LIBTOOL) --mode=compile --tag=RC $(RCCOMPILE) SUFFIXES = .rc .lo .rc.lo: $(LTRCCOMPILE) -i "$<" -o "$@" ksba_res = versioninfo.lo no_undefined = -no-undefined export_symbols = -export-symbols $(srcdir)/libksba.def extra_ltoptions = -XCClinker -static-libgcc install-def-file: $(INSTALL) $(srcdir)/libksba.def $(DESTDIR)$(libdir)/libksba.def uninstall-def-file: -rm $(DESTDIR)$(libdir)/libksba.def ksba_deps = $(ksba_res) libksba.def else !HAVE_W32_SYSTEM ksba_res = no_undefined = export_symbols = extra_ltoptions = ksba_deps = install-def-file: uninstall-def-file: endif !HAVE_W32_SYSTEM libksba_la_LDFLAGS = $(no_undefined) $(export_symbols) $(extra_ltoptions) \ $(libksba_version_script_cmd) -version-info \ @LIBKSBA_LT_CURRENT@:@LIBKSBA_LT_REVISION@:@LIBKSBA_LT_AGE@ \ $(COVERAGE_LDFLAGS) libksba_la_INCLUDES = -I$(top_srcdir)/lib libksba_la_DEPENDENCIES = $(srcdir)/libksba.vers $(ksba_deps) libksba_la_LIBADD = $(ksba_res) @LTLIBOBJS@ @GPG_ERROR_LIBS@ libksba_la_SOURCES = \ ksba.h \ visibility.c visibility.h \ reader.c reader.h \ writer.c writer.h \ asn1-parse.y \ asn1-func.c asn1-func2.c asn1-func.h \ ber-help.c ber-help.h \ ber-decoder.c ber-decoder.h \ der-encoder.c der-encoder.h \ cert.c cert.h \ cms.c cms.h cms-parser.c \ crl.c crl.h \ certreq.c certreq.h \ ocsp.c ocsp.h \ keyinfo.c keyinfo.h \ oid.c name.c dn.c time.c convert.h \ version.c util.c util.h shared.h \ sexp-parse.h \ asn1-tables.c ber_dump_SOURCES = ber-dump.c \ ber-decoder.c ber-help.c reader.c writer.c asn1-parse.c \ - asn1-func.c oid.c util.c + asn1-func.c oid.c time.c util.c ber_dump_LDADD = $(GPG_ERROR_LIBS) ../gl/libgnu.la ber_dump_CFLAGS = $(AM_CFLAGS) asn1-parse.c : asn1-func.h gen-help.h asn1-gentables: asn1-gentables.c asn1-parse.c asn1-func.c gen-help.c gen-help.h $(CC_FOR_BUILD) $(CFLAGS_FOR_BUILD) $(LDFLAGS_FOR_BUILD) \ $(CPPFLAGS_FOR_BUILD) -I$(srcdir) -DBUILD_GENTOOLS -o $@ \ $(srcdir)/asn1-gentables.c \ `test -f 'asn1-parse.c' || echo '$(srcdir)/'`asn1-parse.c \ $(srcdir)/asn1-func.c \ $(srcdir)/gen-help.c asn1-tables.c : $(asn1_modules) asn1-gentables @set -e; list=""; \ for file in $(asn1_modules); do list="$$list $(srcdir)/$$file";done;\ ./asn1-gentables $$list > asn1-tables.c install-data-local: install-def-file uninstall-local: uninstall-def-file diff --git a/src/ber-help.c b/src/ber-help.c index 87109f3..958aab5 100644 --- a/src/ber-help.c +++ b/src/ber-help.c @@ -1,477 +1,669 @@ /* ber-help.c - BER herlper functions * Copyright (C) 2001, 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 "asn1-func.h" /* need some constants */ +#include "convert.h" #include "ber-help.h" /* Fixme: The parser functions should check that primitive types don't have the constructed bit set (which is not allowed). This saves us some work when using these parsers */ 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; } static int premature_eof (struct tag_info *ti) { - /* Note: We do an strcmp on this string at othyer places. */ + /* Note: We do an strcmp on this string at other places. */ ti->err_string = "premature EOF"; return gpg_error (GPG_ERR_BAD_BER); } static gpg_error_t eof_or_error (ksba_reader_t reader, struct tag_info *ti, int premature) { gpg_error_t err; err = ksba_reader_error (reader); if (err) { ti->err_string = "read error"; return err; } if (premature) return premature_eof (ti); return gpg_error (GPG_ERR_EOF); } /* Read the tag and the length part from the TLV triplet. */ gpg_error_t _ksba_ber_read_tl (ksba_reader_t reader, struct tag_info *ti) { int c; unsigned long tag; ti->length = 0; ti->ndef = 0; ti->nhdr = 0; ti->err_string = NULL; ti->non_der = 0; /* Get the tag */ c = read_byte (reader); if (c==-1) return eof_or_error (reader, ti, 0); ti->buf[ti->nhdr++] = c; ti->class = (c & 0xc0) >> 6; ti->is_constructed = !!(c & 0x20); tag = c & 0x1f; if (tag == 0x1f) { tag = 0; do { /* We silently ignore an overflow in the tag value. It is not worth checking for it. */ tag <<= 7; c = read_byte (reader); if (c == -1) return eof_or_error (reader, ti, 1); if (ti->nhdr >= DIM (ti->buf)) { ti->err_string = "tag+length header too large"; return gpg_error (GPG_ERR_BAD_BER); } ti->buf[ti->nhdr++] = c; tag |= c & 0x7f; } while (c & 0x80); } ti->tag = tag; /* Get the length */ c = read_byte (reader); if (c == -1) return eof_or_error (reader, ti, 1); if (ti->nhdr >= DIM (ti->buf)) { ti->err_string = "tag+length header too large"; return gpg_error (GPG_ERR_BAD_BER); } ti->buf[ti->nhdr++] = c; if ( !(c & 0x80) ) ti->length = c; else if (c == 0x80) { ti->ndef = 1; ti->non_der = 1; } else if (c == 0xff) { ti->err_string = "forbidden length value"; return gpg_error (GPG_ERR_BAD_BER); } else { unsigned long len = 0; int count = c & 0x7f; if (count > sizeof (len) || count > sizeof (size_t)) return gpg_error (GPG_ERR_BAD_BER); for (; count; count--) { len <<= 8; c = read_byte (reader); if (c == -1) return eof_or_error (reader, ti, 1); if (ti->nhdr >= DIM (ti->buf)) { ti->err_string = "tag+length header too large"; return gpg_error (GPG_ERR_BAD_BER); } ti->buf[ti->nhdr++] = c; len |= c & 0xff; } ti->length = len; } /* Without this kludge some example certs can't be parsed */ if (ti->class == CLASS_UNIVERSAL && !ti->tag) ti->length = 0; return 0; } /* Parse the buffer at the address BUFFER which of SIZE and return the tag and the length part from the TLV triplet. Update BUFFER and SIZE on success. */ gpg_error_t _ksba_ber_parse_tl (unsigned char const **buffer, size_t *size, struct tag_info *ti) { int c; unsigned long tag; const unsigned char *buf = *buffer; size_t length = *size; ti->length = 0; ti->ndef = 0; ti->nhdr = 0; ti->err_string = NULL; ti->non_der = 0; /* Get the tag */ if (!length) return premature_eof (ti); c = *buf++; length--; ti->buf[ti->nhdr++] = c; ti->class = (c & 0xc0) >> 6; ti->is_constructed = !!(c & 0x20); tag = c & 0x1f; if (tag == 0x1f) { tag = 0; do { /* We silently ignore an overflow in the tag value. It is not worth checking for it. */ tag <<= 7; if (!length) return premature_eof (ti); c = *buf++; length--; if (ti->nhdr >= DIM (ti->buf)) { ti->err_string = "tag+length header too large"; return gpg_error (GPG_ERR_BAD_BER); } ti->buf[ti->nhdr++] = c; tag |= c & 0x7f; } while (c & 0x80); } ti->tag = tag; /* Get the length */ if (!length) return premature_eof (ti); c = *buf++; length--; if (ti->nhdr >= DIM (ti->buf)) { ti->err_string = "tag+length header too large"; return gpg_error (GPG_ERR_BAD_BER); } ti->buf[ti->nhdr++] = c; if ( !(c & 0x80) ) ti->length = c; else if (c == 0x80) { ti->ndef = 1; ti->non_der = 1; } else if (c == 0xff) { ti->err_string = "forbidden length value"; return gpg_error (GPG_ERR_BAD_BER); } else { unsigned long len = 0; int count = c & 0x7f; if (count > sizeof (len) || count > sizeof (size_t)) return gpg_error (GPG_ERR_BAD_BER); for (; count; count--) { len <<= 8; if (!length) return premature_eof (ti); c = *buf++; length--; if (ti->nhdr >= DIM (ti->buf)) { ti->err_string = "tag+length header too large"; return gpg_error (GPG_ERR_BAD_BER); } ti->buf[ti->nhdr++] = c; len |= c & 0xff; } /* Sanity check for the length: This is done so that we can take * the value for malloc plus some additional bytes without * risking an overflow. */ if (len > (1 << 30)) return gpg_error (GPG_ERR_BAD_BER); ti->length = len; } /* Without this kludge some example certs can't be parsed */ if (ti->class == CLASS_UNIVERSAL && !ti->tag) ti->length = 0; *buffer = buf; *size = length; return 0; } /* Write TAG of CLASS to WRITER. constructed is a flag telling whether the value is a constructed one. length gives the length of the value, if it is 0 undefinite length is assumed. length is ignored for the NULL tag. */ gpg_error_t _ksba_ber_write_tl (ksba_writer_t writer, unsigned long tag, enum tag_class class, int constructed, unsigned long length) { unsigned char buf[50]; int buflen = 0; if (tag < 0x1f) { *buf = (class << 6) | tag; if (constructed) *buf |= 0x20; buflen++; } else { return gpg_error (GPG_ERR_NOT_IMPLEMENTED); } if (!tag && !class) buf[buflen++] = 0; /* end tag */ else if (tag == TYPE_NULL && !class) buf[buflen++] = 0; /* NULL tag */ else if (!length) buf[buflen++] = 0x80; /* indefinite length */ else if (length < 128) buf[buflen++] = length; else { int i; /* fixme: if we know the sizeof an ulong we could support larger objects - however this is pretty ridiculous */ i = (length <= 0xff ? 1: length <= 0xffff ? 2: length <= 0xffffff ? 3: 4); buf[buflen++] = (0x80 | i); if (i > 3) buf[buflen++] = length >> 24; if (i > 2) buf[buflen++] = length >> 16; if (i > 1) buf[buflen++] = length >> 8; buf[buflen++] = length; } return ksba_writer_write (writer, buf, buflen); } /* Encode TAG of CLASS in BUFFER. CONSTRUCTED is a flag telling whether the value is a constructed one. LENGTH gives the length of the value, if it is 0 undefinite length is assumed. LENGTH is ignored for the NULL tag. It is assumed that the provide buffer is large enough for storing the result - this is usually achieved by using _ksba_ber_count_tl() in advance. Returns 0 in case of an error or the length of the encoding.*/ size_t _ksba_ber_encode_tl (unsigned char *buffer, unsigned long tag, enum tag_class class, int constructed, unsigned long length) { unsigned char *buf = buffer; if (tag < 0x1f) { *buf = (class << 6) | tag; if (constructed) *buf |= 0x20; buf++; } else { return 0; /*Not implemented*/ } if (!tag && !class) *buf++ = 0; /* end tag */ else if (tag == TYPE_NULL && !class) *buf++ = 0; /* NULL tag */ else if (!length) *buf++ = 0x80; /* indefinite length */ else if (length < 128) *buf++ = length; else { int i; /* fixme: if we know the sizeof an ulong we could support larger objetcs - however this is pretty ridiculous */ i = (length <= 0xff ? 1: length <= 0xffff ? 2: length <= 0xffffff ? 3: 4); *buf++ = (0x80 | i); if (i > 3) *buf++ = length >> 24; if (i > 2) *buf++ = length >> 16; if (i > 1) *buf++ = length >> 8; *buf++ = length; } return buf - buffer; } /* Calculate the length of the TL needed to encode a TAG of CLASS. CONSTRUCTED is a flag telling whether the value is a constructed one. LENGTH gives the length of the value; if it is 0 an indefinite length is assumed. LENGTH is ignored for the NULL tag. */ size_t _ksba_ber_count_tl (unsigned long tag, enum tag_class class, int constructed, unsigned long length) { int buflen = 0; (void)constructed; /* Not used, but passed for uniformity of such calls. */ if (tag < 0x1f) { buflen++; } else { buflen++; /* assume one and let the actual write function bail out */ } if (!tag && !class) buflen++; /* end tag */ else if (tag == TYPE_NULL && !class) buflen++; /* NULL tag */ else if (!length) buflen++; /* indefinite length */ else if (length < 128) buflen++; else { int i; /* fixme: if we know the sizeof an ulong we could support larger objetcs - however this is pretty ridiculous */ i = (length <= 0xff ? 1: length <= 0xffff ? 2: length <= 0xffffff ? 3: 4); buflen++; if (i > 3) buflen++; if (i > 2) buflen++; if (i > 1) buflen++; buflen++; } return buflen; } + + +gpg_error_t +_ksba_parse_sequence (unsigned char const **buf, size_t *len, + struct tag_info *ti) +{ + gpg_error_t err; + + err = _ksba_ber_parse_tl (buf, len, ti); + if (err) + ; + else if (!(ti->class == CLASS_UNIVERSAL && ti->tag == TYPE_SEQUENCE + && ti->is_constructed) ) + err = gpg_error (GPG_ERR_INV_OBJ); + else if (ti->length > *len) + err = gpg_error (GPG_ERR_BAD_BER); + return err; +} + + +gpg_error_t +_ksba_parse_context_tag (unsigned char const **buf, size_t *len, + struct tag_info *ti, int tag) +{ + gpg_error_t err; + + err = _ksba_ber_parse_tl (buf, len, ti); + if (err) + ; + else if (!(ti->class == CLASS_CONTEXT && ti->tag == tag + && ti->is_constructed) ) + err = gpg_error (GPG_ERR_INV_OBJ); + else if (ti->length > *len) + err = gpg_error (GPG_ERR_BAD_BER); + + return err; +} + + +gpg_error_t +_ksba_parse_enumerated (unsigned char const **buf, size_t *len, + struct tag_info *ti, size_t maxlen) +{ + gpg_error_t err; + + err = _ksba_ber_parse_tl (buf, len, ti); + if (err) + ; + else if (!(ti->class == CLASS_UNIVERSAL && ti->tag == TYPE_ENUMERATED + && !ti->is_constructed) ) + err = gpg_error (GPG_ERR_INV_OBJ); + else if (!ti->length) + err = gpg_error (GPG_ERR_TOO_SHORT); + else if (maxlen && ti->length > maxlen) + err = gpg_error (GPG_ERR_TOO_LARGE); + else if (ti->length > *len) + err = gpg_error (GPG_ERR_BAD_BER); + + return err; +} + + +gpg_error_t +_ksba_parse_integer (unsigned char const **buf, size_t *len, + struct tag_info *ti) +{ + gpg_error_t err; + + err = _ksba_ber_parse_tl (buf, len, ti); + if (err) + ; + else if (!(ti->class == CLASS_UNIVERSAL && ti->tag == TYPE_INTEGER + && !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 > *len) + err = gpg_error (GPG_ERR_BAD_BER); + + return err; +} + + +gpg_error_t +_ksba_parse_octet_string (unsigned char const **buf, size_t *len, + struct tag_info *ti) +{ + gpg_error_t err; + + err= _ksba_ber_parse_tl (buf, len, ti); + if (err) + ; + else if (!(ti->class == CLASS_UNIVERSAL && ti->tag == TYPE_OCTET_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 > *len) + err = gpg_error (GPG_ERR_BAD_BER); + + return err; +} + + +/* Note that R_BOOL will only be set if a value has been given. Thus + the caller should set it to the default value prior to calling this + function. Obviously no call to parse_skip is required after + calling this function. */ +gpg_error_t +_ksba_parse_optional_boolean (unsigned char const **buf, size_t *len, + int *r_bool) +{ + gpg_error_t err; + struct tag_info ti; + + err = _ksba_ber_parse_tl (buf, len, &ti); + if (err) + ; + else if (!ti.length) + err = gpg_error (GPG_ERR_TOO_SHORT); + else if (ti.length > *len) + err = gpg_error (GPG_ERR_BAD_BER); + else if (ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_BOOLEAN + && !ti.is_constructed) + { + if (ti.length != 1) + err = gpg_error (GPG_ERR_BAD_BER); + *r_bool = !!**buf; + parse_skip (buf, len, &ti); + } + else + { /* Undo the read. */ + *buf -= ti.nhdr; + *len += ti.nhdr; + } + + return err; +} + + +gpg_error_t +_ksba_parse_object_id_into_str (unsigned char const **buf, size_t *len, + char **oid) +{ + struct tag_info ti; + gpg_error_t err; + + *oid = NULL; + err = _ksba_ber_parse_tl (buf, len, &ti); + if (err) + ; + else if (!(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_OBJECT_ID + && !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 > *len) + err = gpg_error (GPG_ERR_BAD_BER); + else if (!(*oid = ksba_oid_to_str (*buf, ti.length))) + err = gpg_error_from_syserror (); + else + { + *buf += ti.length; + *len -= ti.length; + } + return err; +} + + +gpg_error_t +_ksba_parse_asntime_into_isotime (unsigned char const **buf, size_t *len, + ksba_isotime_t isotime) +{ + struct tag_info ti; + gpg_error_t err; + + err = _ksba_ber_parse_tl (buf, len, &ti); + if (err) + ; + else if ( !(ti.class == CLASS_UNIVERSAL + && (ti.tag == TYPE_UTC_TIME || ti.tag == TYPE_GENERALIZED_TIME) + && !ti.is_constructed) ) + err = gpg_error (GPG_ERR_INV_OBJ); + else if (ti.length > *len) + err = gpg_error (GPG_ERR_INV_BER); + else if (!(err = _ksba_asntime_to_iso (*buf, ti.length, + ti.tag == TYPE_UTC_TIME, isotime))) + parse_skip (buf, len, &ti); + + return err; +} diff --git a/src/ber-help.h b/src/ber-help.h index 286347d..20d39e8 100644 --- a/src/ber-help.h +++ b/src/ber-help.h @@ -1,67 +1,122 @@ /* ber-help.h - Basic Encoding Rules helpers * Copyright (C) 2001, 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 . */ #ifndef BER_HELP_H #define BER_HELP_H 1 struct tag_info { enum tag_class class; int is_constructed; unsigned long tag; unsigned long length; /* length part of the TLV */ int ndef; /* It is an indefinite length */ size_t nhdr; /* number of bytes in the TL */ unsigned char buf[10]; /* buffer for the TL */ const char *err_string; int non_der; }; gpg_error_t _ksba_ber_read_tl (ksba_reader_t reader, struct tag_info *ti); gpg_error_t _ksba_ber_parse_tl (unsigned char const **buffer, size_t *size, struct tag_info *ti); gpg_error_t _ksba_ber_write_tl (ksba_writer_t writer, unsigned long tag, enum tag_class class, int constructed, unsigned long length); size_t _ksba_ber_encode_tl (unsigned char *buffer, unsigned long tag, enum tag_class class, int constructed, unsigned long length); size_t _ksba_ber_count_tl (unsigned long tag, enum tag_class class, int constructed, unsigned long length); +static inline void +parse_skip (unsigned char const **buf, size_t *len, struct tag_info *ti) +{ + if (ti->length) + { + assert (ti->length <= *len); + *len -= ti->length; + *buf += ti->length; + } +} + +gpg_error_t _ksba_parse_sequence (unsigned char const **buf, size_t *len, + struct tag_info *ti); +#define parse_sequence(buf,len,ti) \ + _ksba_parse_sequence ((buf),(len),(ti)) + +gpg_error_t _ksba_parse_context_tag (unsigned char const **buf, size_t *len, + struct tag_info *ti, int tag); +#define parse_context_tag(buf,len,ti,tag) \ + _ksba_parse_context_tag ((buf),(len),(ti),(tag)) + +gpg_error_t _ksba_parse_enumerated (unsigned char const **buf, size_t *len, + struct tag_info *ti, size_t maxlen); +#define parse_enumerated(buf,len,ti,maxlen) \ + _ksba_parse_enumerated ((buf),(len),(ti),(maxlen)) + +gpg_error_t _ksba_parse_integer (unsigned char const **buf, size_t *len, + struct tag_info *ti); +#define parse_integer(buf,len,ti) \ + _ksba_parse_integer ((buf),(len),(ti)) + +gpg_error_t _ksba_parse_octet_string (unsigned char const **buf, size_t *len, + struct tag_info *ti); +#define parse_octet_string(buf,len,ti) \ + _ksba_parse_octet_string ((buf),(len),(ti)) + +gpg_error_t _ksba_parse_optional_boolean (unsigned char const **buf, + size_t *len, int *r_bool); +#define parse_optional_boolean(buf,len,r_bool) \ + _ksba_parse_optional_boolean ((buf),(len),(r_bool)) + +gpg_error_t _ksba_parse_object_id_into_str (unsigned char const **buf, + size_t *len, char **oid); +#define parse_object_id_into_str(buf,len,r_oid) \ + _ksba_parse_object_id_into_str ((buf),(len),(r_oid)) + + +gpg_error_t _ksba_parse_asntime_into_isotime (unsigned char const **buf, + size_t *len, + ksba_isotime_t isotime); +#define parse_asntime_into_isotime(buf,len,isotime) \ + _ksba_parse_asntime_into_isotime ((buf),(len),(isotime)) + + + #endif /*BER_HELP_H*/ diff --git a/src/crl.c b/src/crl.c index daeb222..b5a9208 100644 --- a/src/crl.c +++ b/src/crl.c @@ -1,1546 +1,1426 @@ /* 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" 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)) - -static void -parse_skip (unsigned char const **buf, size_t *len, struct tag_info *ti) -{ - if (ti->length) - { - assert (ti->length <= *len); - *len -= ti->length; - *buf += ti->length; - } -} - -static gpg_error_t -parse_sequence (unsigned char const **buf, size_t *len, struct tag_info *ti) -{ - gpg_error_t err; - - err = _ksba_ber_parse_tl (buf, len, ti); - if (err) - ; - else if (!(ti->class == CLASS_UNIVERSAL && ti->tag == TYPE_SEQUENCE - && ti->is_constructed) ) - err = gpg_error (GPG_ERR_INV_OBJ); - else if (ti->length > *len) - err = gpg_error (GPG_ERR_BAD_BER); - return err; -} - -static gpg_error_t -parse_integer (unsigned char const **buf, size_t *len, struct tag_info *ti) -{ - gpg_error_t err; - - err = _ksba_ber_parse_tl (buf, len, ti); - if (err) - ; - else if (!(ti->class == CLASS_UNIVERSAL && ti->tag == TYPE_INTEGER - && !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 > *len) - err = gpg_error (GPG_ERR_BAD_BER); - - return err; -} - -static gpg_error_t -parse_octet_string (unsigned char const **buf, size_t *len, struct tag_info *ti) -{ - gpg_error_t err; - - err= _ksba_ber_parse_tl (buf, len, ti); - if (err) - ; - else if (!(ti->class == CLASS_UNIVERSAL && ti->tag == TYPE_OCTET_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 > *len) - err = gpg_error (GPG_ERR_BAD_BER); - - return err; -} - -static gpg_error_t -parse_object_id_into_str (unsigned char const **buf, size_t *len, char **oid) -{ - struct tag_info ti; - gpg_error_t err; - - *oid = NULL; - err = _ksba_ber_parse_tl (buf, len, &ti); - if (err) - ; - else if (!(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_OBJECT_ID - && !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 > *len) - err = gpg_error (GPG_ERR_BAD_BER); - else if (!(*oid = ksba_oid_to_str (*buf, ti.length))) - err = gpg_error_from_errno (errno); - else - { - *buf += ti.length; - *len -= ti.length; - } - return err; -} - - - /** * 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. * * 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) 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; } - -/* Parse an enumerated value. Note that this code is duplication of - the one at ocsp.c. */ -static gpg_error_t -parse_enumerated (unsigned char const **buf, size_t *len, struct tag_info *ti, - size_t maxlen) -{ - gpg_error_t err; - - err = _ksba_ber_parse_tl (buf, len, ti); - if (err) - ; - else if (!(ti->class == CLASS_UNIVERSAL && ti->tag == TYPE_ENUMERATED - && !ti->is_constructed) ) - err = gpg_error (GPG_ERR_INV_OBJ); - else if (!ti->length) - err = gpg_error (GPG_ERR_TOO_SHORT); - else if (maxlen && ti->length > maxlen) - err = gpg_error (GPG_ERR_TOO_LARGE); - else if (ti->length > *len) - err = gpg_error (GPG_ERR_BAD_BER); - - return err; -} - - + /* 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/src/ocsp.c b/src/ocsp.c index 4b26f8d..b5517cf 100644 --- a/src/ocsp.c +++ b/src/ocsp.c @@ -1,1901 +1,1705 @@ /* 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 - -static void -parse_skip (unsigned char const **buf, size_t *len, struct tag_info *ti) -{ - if (ti->length) - { - assert (ti->length <= *len); - *len -= ti->length; - *buf += ti->length; - } -} - -static gpg_error_t -parse_sequence (unsigned char const **buf, size_t *len, struct tag_info *ti) -{ - gpg_error_t err; - - err = _ksba_ber_parse_tl (buf, len, ti); - if (err) - ; - else if (!(ti->class == CLASS_UNIVERSAL && ti->tag == TYPE_SEQUENCE - && ti->is_constructed) ) - err = gpg_error (GPG_ERR_INV_OBJ); - else if (ti->length > *len) - err = gpg_error (GPG_ERR_BAD_BER); - return err; -} - -static gpg_error_t -parse_enumerated (unsigned char const **buf, size_t *len, struct tag_info *ti, - size_t maxlen) -{ - gpg_error_t err; - - err = _ksba_ber_parse_tl (buf, len, ti); - if (err) - ; - else if (!(ti->class == CLASS_UNIVERSAL && ti->tag == TYPE_ENUMERATED - && !ti->is_constructed) ) - err = gpg_error (GPG_ERR_INV_OBJ); - else if (!ti->length) - err = gpg_error (GPG_ERR_TOO_SHORT); - else if (maxlen && ti->length > maxlen) - err = gpg_error (GPG_ERR_TOO_LARGE); - else if (ti->length > *len) - err = gpg_error (GPG_ERR_BAD_BER); - - return err; -} - -static gpg_error_t -parse_integer (unsigned char const **buf, size_t *len, struct tag_info *ti) -{ - gpg_error_t err; - - err = _ksba_ber_parse_tl (buf, len, ti); - if (err) - ; - else if (!(ti->class == CLASS_UNIVERSAL && ti->tag == TYPE_INTEGER - && !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 > *len) - err = gpg_error (GPG_ERR_BAD_BER); - - return err; -} - -static gpg_error_t -parse_octet_string (unsigned char const **buf, size_t *len, struct tag_info *ti) -{ - gpg_error_t err; - - err= _ksba_ber_parse_tl (buf, len, ti); - if (err) - ; - else if (!(ti->class == CLASS_UNIVERSAL && ti->tag == TYPE_OCTET_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 > *len) - err = gpg_error (GPG_ERR_BAD_BER); - - return err; -} - - -/* Note that R_BOOL will only be set if a value has been given. Thus - the caller should set it to the default value prior to calling this - function. Obviously no call to parse_skip is required after - calling this function. */ -static gpg_error_t -parse_optional_boolean (unsigned char const **buf, size_t *len, int *r_bool) -{ - gpg_error_t err; - struct tag_info ti; - - err = _ksba_ber_parse_tl (buf, len, &ti); - if (err) - ; - else if (!ti.length) - err = gpg_error (GPG_ERR_TOO_SHORT); - else if (ti.length > *len) - err = gpg_error (GPG_ERR_BAD_BER); - else if (ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_BOOLEAN - && !ti.is_constructed) - { - if (ti.length != 1) - err = gpg_error (GPG_ERR_BAD_BER); - *r_bool = !!**buf; - parse_skip (buf, len, &ti); - } - else - { /* Undo the read. */ - *buf -= ti.nhdr; - *len += ti.nhdr; - } - - return err; -} - - - -static gpg_error_t -parse_object_id_into_str (unsigned char const **buf, size_t *len, char **oid) -{ - struct tag_info ti; - gpg_error_t err; - - *oid = NULL; - err = _ksba_ber_parse_tl (buf, len, &ti); - if (err) - ; - else if (!(ti.class == CLASS_UNIVERSAL && ti.tag == TYPE_OBJECT_ID - && !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 > *len) - err = gpg_error (GPG_ERR_BAD_BER); - else if (!(*oid = ksba_oid_to_str (*buf, ti.length))) - err = gpg_error_from_syserror (); - else - { - *buf += ti.length; - *len -= ti.length; - } - return err; -} - - -static gpg_error_t -parse_asntime_into_isotime (unsigned char const **buf, size_t *len, - ksba_isotime_t isotime) -{ - struct tag_info ti; - gpg_error_t err; - - err = _ksba_ber_parse_tl (buf, len, &ti); - if (err) - ; - else if ( !(ti.class == CLASS_UNIVERSAL - && (ti.tag == TYPE_UTC_TIME || ti.tag == TYPE_GENERALIZED_TIME) - && !ti.is_constructed) ) - err = gpg_error (GPG_ERR_INV_OBJ); - else if (ti.length > *len) - err = gpg_error (GPG_ERR_INV_BER); - else if (!(err = _ksba_asntime_to_iso (*buf, ti.length, - ti.tag == TYPE_UTC_TIME, isotime))) - parse_skip (buf, len, &ti); - - return err; -} - - -static gpg_error_t -parse_context_tag (unsigned char const **buf, size_t *len, struct tag_info *ti, - int tag) -{ - gpg_error_t err; - - err = _ksba_ber_parse_tl (buf, len, ti); - if (err) - ; - else if (!(ti->class == CLASS_CONTEXT && ti->tag == tag - && ti->is_constructed) ) - err = gpg_error (GPG_ERR_INV_OBJ); - else if (ti->length > *len) - err = gpg_error (GPG_ERR_BAD_BER); - - return err; -} - - /* 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. */ 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; 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_ber_write_tl (w1, TYPE_INTEGER, CLASS_UNIVERSAL, 0, 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; /* 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); */ 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->serialno, serialno, serialnolen)) 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; }