Initial checkin of Quick DER a.k.a. Quick `n' Easy DER.
authorRick van Rein <rick@openfortress.nl>
Sun, 21 Feb 2016 22:30:26 +0000 (22:30 +0000)
committerRick van Rein <rick@openfortress.nl>
Sun, 21 Feb 2016 22:30:26 +0000 (22:30 +0000)
* Tested to correctly der_unpack() then der_pack() a PKIX Certificate
* Created logo, Makefiles, stub configure
* Documented the library to some degree in MarkDown files in the root

32 files changed:
.gitmodules [new file with mode: 0644]
INSTALL.MD [new file with mode: 0644]
LICENSE.MD [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.MD [new file with mode: 0644]
USING.MD [new file with mode: 0644]
WHEN-SIZE-MATTERS.MD [new file with mode: 0644]
configure [new file with mode: 0755]
img/hinkelbaan-klein.png [new file with mode: 0644]
img/hinkelbaan-klein.xcf [new file with mode: 0644]
img/hinkelbaan-voeten.png [new file with mode: 0644]
img/hinkelbaan-voeten.xcf [new file with mode: 0644]
img/photo/LICENSE.MD [new file with mode: 0644]
img/photo/Metropole_Zlicin_-_skakaci_panak_2.jpg [new file with mode: 0644]
include/quick-der/api.h [new file with mode: 0644]
lib/Makefile [new file with mode: 0644]
lib/der_header.c [new file with mode: 0644]
lib/der_iterate.c [new file with mode: 0644]
lib/der_pack.c [new file with mode: 0644]
lib/der_prepack.c [new file with mode: 0644]
lib/der_skipenter.c [new file with mode: 0644]
lib/der_unpack.c [new file with mode: 0644]
lib/der_walk.c [new file with mode: 0644]
lib/quick-der.c [new file with mode: 0644]
quick-der-logo.png [new file with mode: 0644]
test/Makefile [new file with mode: 0644]
test/certio.c [new file with mode: 0644]
test/krb5ticket.c [new file with mode: 0644]
test/kxover.c [new file with mode: 0644]
test/verisign.der [new file with mode: 0644]
tool/Makefile [new file with mode: 0644]
tool/hexio [new submodule]

diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..c60559d
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "tool/hexio"]
+       path = tool/hexio
+       url = https://github.com/vanrein/hexio
diff --git a/INSTALL.MD b/INSTALL.MD
new file mode 100644 (file)
index 0000000..a6af6cb
--- /dev/null
@@ -0,0 +1,7 @@
+# Installing Quick DER
+
+    ./configure
+    make
+    make install
+
+Other build targets include `clean`, `uninstall`.
diff --git a/LICENSE.MD b/LICENSE.MD
new file mode 100644 (file)
index 0000000..91c10fe
--- /dev/null
@@ -0,0 +1,26 @@
+# Quick DER licensing terms
+
+Copyright (c) 2016, Rick van Rein, OpenFortress.nl
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+  1. Redistributions of source code must retain the above copyright notice,
+     this list of conditions and the following disclaimer.
+
+  2. Redistributions in binary form must reproduce the above copyright notice,
+     this list of conditions and the following disclaimer in the documentation
+     and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..16fd691
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+# SUBDIRS = lib asn2qder rfc test
+SUBDIRS = lib tool test
+
+all:
+       for d in $(SUBDIRS); do make -C "$$d" all ; done
+
+install:
+       for d in $(SUBDIRS); do make -C "$$d" install ; done
+
+uninstall:
+       for d in $(SUBDIRS); do make -C "$$d" uninstall ; done
+
+clean:
+       for d in $(SUBDIRS); do make -C "$$d" clean ; done
diff --git a/README.MD b/README.MD
new file mode 100644 (file)
index 0000000..a3d7f71
--- /dev/null
+++ b/README.MD
@@ -0,0 +1,125 @@
+# Quick (and Easy) DER, a Library for parsing ASN.1
+
+![Quick DER logo](quick-der-logo.png)
+
+> *Quick DER, or if you like, "Quick and Easy DER", is a library for handling
+> DER, which is a widely used binary representation of ASN.1 syntax in binary
+> formats.  The library describes ASN.1 syntax in a parser table that can be
+> fed into library routines, resulting in pointer/length descriptors for the
+> individual data fragments.*
+
+## Basic Usage
+
+The basic approach of using this library is translating the ASN.1 syntax into
+a parser table.  This is done when building your software, as a phase preceding
+the compilation of your Quick DER using program.  The translation from ASN.1
+to C code can be done manually and **TODO** will in the future be automated with
+an ASN.1 parser.
+
+The resulting *path walks* are used in calls like `der_unpack()` to transform
+DER into C-style structures, and `der_pack()` to make the opposite transformation.
+The C-style structures are derived from the ASN.1 syntax, and permits access to
+the information with no further need of understanding the depths of processing
+DER.
+
+Since you are handling binary data (rather than character strings), all data
+is described in so-called `dercursor` structures; each containing a pointer
+and a size of the data pointed at.
+
+
+## Basic Code Structure
+
+The output from mapping ASN.1 to a parser table is an include file for the
+C programming language.  This defines the various *path walks* that can be
+used to unpack and pack DER data.  The output and input of these routines
+takes the form of an array of `dercursor` values.
+
+To simplify use of the unpacked data, there are overlay structures for the
+`dercursor` array.  These overlay structures use the same labels that are
+used in the ASN.1 syntax, so it is possible to walk around in the structures.
+
+Some parts of the syntax indicate `OPTIONAL` elements.  Such elements result
+in the respective `dercursor` variables to be NULL values; specifically, the
+function `der_isnull()` returns a true value for these elements.
+
+
+## Extra Code Facilities
+
+There are routines `der_iterate_first()` and `der_iterate_next()` routines
+to manually iterate over a DER structure's components.  This can be used to
+analyse structures that have not been unpacked (yet).  The `der_countelements()`
+routine can be used to predict the number of iterations.
+
+There are also routines to manually walk through packaged DER structures,
+namely `der_enter()` and `der_skip()` to enter into a nested structure and to
+find the next element in a concatenation of such elements.
+
+A much more advanced form of such walks through a DER structure exists in the
+form of `der_walk()`, which is fed another kind of walk, a sequence of enter/skip
+statements with tags that will be validated.
+
+
+## Validation of DER
+
+To validate the structures written in DER, both `der_unpack()` and `der_walk()`
+can be used.  Most other routines are coded for flexibility and should not be
+assumed to validate DER in more detail than strictly required.
+
+The `der_unpack()` routine runs through the entire structure, and validates
+the tags it runs by, as well as the complete nesting structure it encounters.
+It is a complete validation of the structures.
+
+The `der_walk()` routine performs *lazy validation*, meaning that it will
+carefully check tags and nesting inasfar as it is needed to get from its
+starting point to its end point.  Anything but the paths explored will be
+accepted without question.
+
+### Relation to BER
+
+*This is for ASN.1 experts; others can safely skip this subsection.*
+
+The Quick DER library is designed to process DER, although it will also accept
+some of the more general BER format.  If a length or value is written in more
+than the minimal space, the library is still likely to accept it.  Note that
+the `BIT STRING` type is somewhat likely to run into overflow problems, so
+there the full restrictiveness of DER is applied.
+
+
+## No Memory Allocation
+
+The entire library has been designed to operate without dynamic memory allocation.
+This means that there will never be a memory leak as a result of using Quick DER.
+
+When DER information is unpacked, it is assumed to be loaded into a memory buffer
+by the calling program, and the `dercursor` structures point to portions of that
+buffer.  The data is stored in `dercursor` arrays which the user program may
+overlay with meaningful, ASN.1-labelled structures.  In many applications, such
+structures can be allocated on the stack.
+
+Some portions of the data may be dynamically sized, notably the `SEQUENCE OF`
+and `SET OF` structures, which indicate that the structural description following
+it may be repeated in the binary data.  Such data portions will be stored and
+not yet unpacked by `der_unpack()`.  Based on the stored DER data in a `dercursor`,
+the calling application can choose to use iterators, `der_walk()` and so on to
+avoid actually unpacking it; or it may allocate memory dynamically, and use that
+to repeatedly call `der_unpack()` to find the individual entries.
+
+In short, the Quick DER library *never* needs to perform memory allocation, and
+it provides the calling program with a lot of control to avoid it too.  This is
+ideal for embedded applications, but is also beneficial for a secure programming
+style.
+
+
+## Future Plans
+
+There are a few things that this library can use:
+
+  * **Proper tests.** The current `test` directory is far too small; we can take a PKIX certificate apart and re-compose it, so we're definately doing something good, but this is nowhere near thorough testing.  If you run into a problem case, then *please* share it so we can solve it and extend our test base.
+  * **ASN.1 compilation.** The compiler for [libtasn1](http://git.savannah.gnu.org/cgit/libtasn1.git/tree/lib) may be an interesting piece of software to modify to create the `derwalk[]` structures that we currently create by hand, together with the overlay structures with syntax-derived labels.
+  * **RFC Library.** A collection of most/all standards that use DER today, in a pre-compiled form that can simply be included as `<quick-der/rfc5280.h>` and then permits processing of the structures defined in it (in this case, PKIX Certificates).
+
+And of course, there are many useful things we may do with this library:
+
+  * **Kerberos in PKIX.** [Certificates wrapping Kerberos Tickets](http://github.com/arpa2/kerberos2pkix) for use with [TLS-KDH](https://tools.ietf.org/html/draft-vanrein-tls-kdh)
+  * **Miniature LDAP services.** These can help you centralise your data storage under own control; for instance, your PGP key ring or your vCard collection are good canidadates for sharing locally.
+
diff --git a/USING.MD b/USING.MD
new file mode 100644 (file)
index 0000000..e1d31cf
--- /dev/null
+++ b/USING.MD
@@ -0,0 +1,160 @@
+# Quick DER parsing support
+
+> *Quick DER parsing aims to get you started with the parsing of DER (and most
+> BER) encoded ASN.1 data really quickly.  It also aims to makes quick parsers,
+> by using shared data structures whenever possible.*
+
+**Note: This is a preview of features to come; for now, it is no promise, but merely a rough sketch of what is to come.**
+
+## Outline of using this library
+
+Working with the quick DER library is really quick to learn.
+
+### Preparing your build environment
+
+The first thing you do, is parse an ASN.1 specification that you may have gotten
+from any source -- for example, from an RFC.  You map it to a header file and a
+parser specification file:
+
+    qder_from_asn1 myspec.asn1 myderparser.c myderparser.h
+
+Your source code dealing with DER should read the entire block, and pass it to
+the DER parser.  Initially, it would include:
+
+    #include <der/qparse.h>
+    #include "myderparser.h"
+
+and builing should include:
+
+    gcc -o myparser.o myparser.c
+    gcc ... myderparser.o -lqder
+
+You may be lucky, and find your favourite spec precompiled.  In that case, you
+can limit yourself to things like:
+
+    #define RFC5280_PREFIX pkix_
+    #include <der/qparse.h>
+    #include <der/rfc5280.h>
+
+and link with simply:
+
+    gcc ... -lqder
+
+
+### Parsing DER structures
+
+Before you get to parse DER-encoded structures that match the ASN.1 syntax,
+you should read the entire data into memory.  The parser output will not
+clone bits and pieces of data, but instead point into it with cursors; these
+are little structures with a pointer and a length.  Note that this means that
+strings are not NUL-terminated; printing them may be different than what you
+are accustomed to:
+
+    printf ("%s\n", derelem->ptr);                  // Unbounded string
+    printf ("%.*s\n", derelem->len, derelem->ptr);  // Perfect printing
+
+Now, to invoke the parser, you setup a cursor describing the entire content,
+
+   dercursor_t thelot;
+   thelot.derptr = ...pointer-to-data...;
+   thelot.derlen = ...length-of-data...;
+
+then you invoke the parser, providing it with storage space and the
+precompiled structure to follow while parsing:
+
+   struct pkix_Certificate crt;
+   int prsok = der_unpack (&thelot, asn1_pkix_Certificate, &crt, 1);
+
+This will parse the DER-encoded data in `thelot` and store the various fields
+in `crt`, so it becomes available as individual cursor structures such as
+`crt.tbsCertificate.validity.notAfter`.  This follows the structure of the
+ASN.1 syntax, and uses field names defined in it, to gradually move into
+the structure.  The header file defines those names as part of the
+`asn1_pkix_Certificate`.
+
+Something else that can now be done, is switch behaviour based on the the
+various fields that contain an `OBJECT IDENTIFIER` for that purposes.  These
+can usually be treated as binary settings to be compared as binaries.  The
+`dercmp()` utility does this by looking at the length as well as binary
+contents of such fields, as in
+
+    if (dercmp (&crt.signatureAlgorith.algorithm, RSA_WITH_SHA1) == 0) {
+       ...the OIDs matched...
+    } else ...other cases...
+
+
+## Iterating over repeating structures
+
+Many structures in ASN.1 are variable in the sizes of primitive data types, but
+have a fixed composition structure.  And `OPTIONAL` parts can be parsed and their
+respective structure fields set to NULL when they are absent.  This also happens
+to values setup with a `DEFAULT` value in ASN.1 (note that their default value
+is not filled in by the parser).
+
+But some structures are not parsed immediately, because they might have a
+repeated structure, and thus won't fit into a structure; not if the assumption
+is that dynamic allocation should not be done by the parser.  For such
+repeating structure, there are two options, namely iterating over their
+contents or allocating memory and having them filled by the parser.
+
+To iterate over a repeated structure, simply use the two routines setup for
+that in quick DER, as in:
+
+    if (der_iterate_first (&crt.tbsCertificate.extensions.packed, &iter)) do {
+       ...treat extension pointed to by iter...
+    } while (der_iterate_next (&crt.tbsCertificate.extensions.packed, &iter));
+
+This requires no dynamic allocation, and simply handles each of the extensions
+in a certificate one by one.
+
+## Allocating space for a repeating structure
+
+The structures that repeat are limited to the ASN.1 constructs
+`SEQUENCE OF` and `SET OF`.  When these occur, the parser will not unfold
+the contained structure, but simply store the whole structure.  We will
+refer to that as "packed" representation, meaning the binary DER format.
+
+It is possible to replace packed notation by unpacked, by assigning to it
+an array of suitable size to contain the required number of elements,
+and then unfold the repeated structure into it:
+
+    size_t count = der_countelements (&crt.tbsCertificate.extensions.packed);
+    pkix_Extensions *exts = calloc (count, sizeof (pkcix_Extensions));
+    if (exts === NULL) {
+       ...handle error...
+    }
+    prsok = der_unpack (&crt.tbsCertificate.extensions.packed, asn1_pkix_Extension, exts, count);
+
+This will unpack `count` times the structure described by `asn1_pkix_Extension` and place the output in `count` structures in the array `exts`; note that the earlier
+call the `der_unpack` had a parameter `1` in the position of `count`.
+
+When successful, the `der_unpack()` routine replaces the `extensions.packed`
+structure, which is a plain `dercursor_t`, with an unpacked structure
+`unpacked` which has elements `derray` pointing to an array of cursors and
+an element `dercnt` with the number of cursors in that array.  When this
+is setup, the `.packed` version of the data is destroyed; the `.packed` and
+`.unpacked` versions are in fact labels of a union.
+
+Note that structures such as `crt` may hold a lot of useful naming, but they
+are just a cleverly constructed overlay form for an array of `dercursor_t` fields,
+which is exactly how `der_unpack` treats them.  The ASN.1 parsing instructions
+are matched to the structures so that no data will be sticking out of these
+array-like structures.
+
+## Composing DER output
+
+The composition of DER output uses the same ASN.1 structural descriptions as
+the unpacking process.  It is possible to use `.packed` structures, but once
+they are unpacked it becomes necessary to prepare repeating structures for
+repackaging.  This uses the `der_prepack()` function:
+
+    int prsok = der_prepack (TODO);
+
+This sets up a third flavour of the repeated structure, namely `.prepacked`.
+In this form, the `derlen` value has been set to the eventual length of
+the to-be-formed DER structure, but the `derray` value still points to the
+array of `dercursor_t` holding the to-be-filled data.  This `derlen` field
+can subsequently be used during the future packing process.
+
+TODO: How to distinguish packed, unpacked and prepacked lengths?  Tag or size bits?
+
diff --git a/WHEN-SIZE-MATTERS.MD b/WHEN-SIZE-MATTERS.MD
new file mode 100644 (file)
index 0000000..fd2f1c6
--- /dev/null
@@ -0,0 +1,123 @@
+# Quick DER sizes: Sometimes small is bettar
+
+> *This is a decription of the sizes of structures used by Quick DER.  As this
+> demonstrates, the library is quite useful for embedded purposes.*
+
+Using Quick DER, an include file is derived from an ASN.1 syntax specification.
+The include files contain fixed parsers for the various structure that can be
+assigned to constant byte arrays, and then applied to a dowloaded sequence of
+bytes in DER format.
+
+Data is generally stored in the form of a dercursor.  This is a combination
+of a pointer to DER data with a size of the data from that point.  We measure
+memory consumption for data storage in such structures; they point the the
+already-allocated memory that holds to-be-analysed DER input.  The length of
+the actual data handled is therefore not included below, since it can vary
+virtually without bounds.
+
+
+## Code size
+
+The following data stems from analysis of the `.text` segment of version TODO
+of Quick DER:
+
+  * 261 bytes for `der_header()`
+  * 079 bytes for `der_iterate_first()`, `der_iterate_next()` and `der_iterate_next()`
+  * 141 bytes for `der_skip()` and `der_enter()`
+  * 761 bytes for `der_unpack()`
+  * 008 bytes for `der_prepack()`
+  * 788 bytes for `der_pack()`
+  * 376 bytes for `der_walk()`
+
+Note that `der_header()` is the only dependency for most other modules.
+Some modules will write into `errno` as well.
+
+The total size of all these modules is 2405 bytes.  Note that this includes
+alternatives; it is possible to use just `der_walk()` to walk into structures,
+or one might do it with `der_unpack()`.  The choice is there but there is no
+need to mix the styles if you are pressed for space.
+
+
+## Data storage
+
+This is expressed in the number of dercursor structures needed to address
+portions of already-allocated DER code space.  Note that Quick DER never
+allocates dynamic memory for itself, so you are free()d from memory management
+for these routines.
+
+Moreover, the routines help you to avoid allocating dynamic memory too.  The
+walker construct, together with basic movements inside DER structures, makes
+it easy to setup a cursor and have it traverse paths.  And there are iterators
+to walk through SEQUENCE OF and SET OF structures.
+
+The only reason you might have to allocate memory is when you intend to fully
+der_unpack() into SEQUENCE OF and SET OF structures.  In this case, the output
+in the form of an dercursor array can have any size due to the variable size
+of the repeated structure.  You may be forced to use this if your aim is to
+der_pack() the structure later, because it may otherwise end up as a Primitive
+structure, which may not be what you intended to deliver.  But even then, it
+is a matter of dercursor structures, not the full-blown packaged data size.
+After taking ample size precautiouns, this might easily be done on a stack.
+
+The other components of variability do not require explicit allocation, because
+the space for all their variants can be unfolded and separately stored.
+This applies to OPTIONAL / DEFAULT as well as to CHOICE.  Entries that were
+not parsed are simply set to NULL dercursor values, so it is detectable that
+they were not present in the input (and should not appear in the output).
+The ANY element is parsed by providing the entire structure at its position,
+including its header (and tag).  You can use der_enter() or der_header() to
+skip or analyse the header, and/or you could start a new der_unpack() or
+der_walk() to get into the structure, based on the applicable syntax.
+
+The number of dercursor structures created depends on the parser prescription.
+Here is the count for der_unpack()ing a few standard structures:
+
+  * 16 dercursor values for a PKIX Certificate
+  * 03 dercursor values for a PKIX Certificate Extension
+  * 07 dercursor values for a Kerberos5 Ticket's unencrypted part
+  * 14 dercursor values for a Kerberos5 Ticket's EncTicketPart
+
+Quite humble, isn't it?
+
+
+## Stack size
+
+The storage needed on the stack depends solely on routine nesting depth.
+The nesting depth depends on the ASN.1 syntax descriptions being processed;
+since it does not depend on the data, there is no risk of a stack overflow
+caused by too large data.  (You should of course still be careful when you
+allocate dynamically sized, that is, input-determined, data on your stack.)
+
+With der_walk() there is never any nesting.  For der_pack() and der_unpack()
+the nesting is determined by the number of ENTER / LEAVE pairs.
+In addition, der_pack() will nest for components prepared with der_prepack()
+which might also be prepared in a nested fashion.  In addition, der_unpack()
+will nest for CHOICE sequences.
+
+The following indications provide some information on the maximum reached
+nesting depth while running der_unpack() over some standard structures:
+
+  * 4 nesting levels for a PKIX Certificate
+  * 1 nesting levels for a PKIX Certificate Extension
+  * 4 nesting levels for a Kerberos5 Ticket's unencrypted part
+  * 5 nesting levels for a Kerberos5 Ticket's EncTicketPart
+
+The reason that Kerberos is using a few more nesting levels is that it uses
+explicit tagging very liberally.  It's a modest cost for such a bright use
+of ASN.1 and especially to achieve extensibility in the data formats.
+
+
+## Benchmarking Environment
+
+The aforementioned figures were obtained using:
+
+  * `gcc` 4.7.2
+  * `CFLAGS=-Os -ggdb3 -c`
+  * `objdump -h -j .text *.o`
+  * The first GIT checkin of the source code (may be updated later on)
+
+The choice for `-Os` was based on the intention to demonstrate suitability
+for embedded environments.  It saved about 42% code size compared to `-O0`
+(no optimisation) and about 25% compared to `-O2` (often used in projects as
+their default optimisation).
+
diff --git a/configure b/configure
new file mode 100755 (executable)
index 0000000..9b83baf
--- /dev/null
+++ b/configure
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+echo
+echo My apologies\; I have not enjoyed my previous encounter with autotools.
+echo
+echo For a package supposed to overcome platform differences, it had too many
+echo version dependencies of its own.  Also, I failed to grasp the logic or
+echo structure of the autotools, making it seem like an endless series of
+echo fixes without clarity of why any of them was needed, and where.
+echo
+echo Please accept my humble offering of Makefiles instead.
+echo
diff --git a/img/hinkelbaan-klein.png b/img/hinkelbaan-klein.png
new file mode 100644 (file)
index 0000000..65d0a9b
Binary files /dev/null and b/img/hinkelbaan-klein.png differ
diff --git a/img/hinkelbaan-klein.xcf b/img/hinkelbaan-klein.xcf
new file mode 100644 (file)
index 0000000..7211954
Binary files /dev/null and b/img/hinkelbaan-klein.xcf differ
diff --git a/img/hinkelbaan-voeten.png b/img/hinkelbaan-voeten.png
new file mode 100644 (file)
index 0000000..2469dd2
Binary files /dev/null and b/img/hinkelbaan-voeten.png differ
diff --git a/img/hinkelbaan-voeten.xcf b/img/hinkelbaan-voeten.xcf
new file mode 100644 (file)
index 0000000..e63f6f1
Binary files /dev/null and b/img/hinkelbaan-voeten.xcf differ
diff --git a/img/photo/LICENSE.MD b/img/photo/LICENSE.MD
new file mode 100644 (file)
index 0000000..c5df59d
--- /dev/null
@@ -0,0 +1,31 @@
+# Photo license
+
+Description
+       Català: Xarranca
+       Čeština: Nebe, peklo, ráj / Skákání panáka
+       Deutsch: Hickelkasten
+       English: Hopscotch
+       Español: Rayuela
+       Français : Marelle
+       Nederlands: Hinkelbaan
+       Português: Amarelinha
+       Русский: Классики
+       Svenska: Hoppa hage
+
+Souce
+       https://commons.wikimedia.org/wiki/File:Metropole_Zlicin_-_skakaci_panak_2.jpg
+
+Date
+       22 September 2008
+
+Source
+       Own work
+
+Author
+       Matěj Baťha
+
+Permission
+       https://commons.wikimedia.org/wiki/Commons:Reusing_content_outside_Wikimedia
+       Creative Commons Attribution ShareAlike 3.0
+       https://creativecommons.org/licenses/by-sa/3.0/deed.en
+
diff --git a/img/photo/Metropole_Zlicin_-_skakaci_panak_2.jpg b/img/photo/Metropole_Zlicin_-_skakaci_panak_2.jpg
new file mode 100644 (file)
index 0000000..0aa4ed2
Binary files /dev/null and b/img/photo/Metropole_Zlicin_-_skakaci_panak_2.jpg differ
diff --git a/include/quick-der/api.h b/include/quick-der/api.h
new file mode 100644 (file)
index 0000000..78e4a28
--- /dev/null
@@ -0,0 +1,549 @@
+
+#ifndef QUICK_DER_H
+#define QUICK_DER_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+
+
+#ifdef DEBUG
+#  include <stdio.h>
+#  define DPRINTF printf
+#else
+#  define DPRINTF
+#endif
+
+
+/* Most of BER is included with these routines as well, but not the
+ * indefinate-length method.  Also, there is no support for application,
+ * contextual and private tags [31] and up.  What this means in practice,
+ * is that you should be able to process PKIX certificates, in spite of
+ * their outer layer being BER and only the tbsCertificate being DER.
+ */
+
+
+/* Cursors describe the ASN.1 buffer in DER coding, and can be walked into
+ * structures when they follow a path.  It is possible to make copies of this
+ * structure between partial traversals, so as to efficiently run into a
+ * structure.
+ */
+typedef struct dercursor {
+       uint8_t *derptr;
+       size_t   derlen;
+} dercursor;
+
+
+/* Unpacked DER structures take the same shape as a dercursor, and usually
+ * overlay the same space; it is assumed that the programmer is aware of
+ * the phase his program is in, and addresses the right variation.  Most
+ * often, this type will not be used, but a properly typed structure with
+ * the same names.  The derray points to an array of dercursors, which is
+ * then overlayed with a structure with dercursors named after fields in
+ * the ASN.1 syntax; the dercnt value indicates how many elements dercursors
+ * exist in the DER data.
+ *
+ * This variation is useful for dynamically-sized data, notably from unpacking
+ * SEQUENCE OF and SET OF substructures.
+ */
+
+typedef struct derarray {
+       dercursor *derray;
+       size_t dercnt;
+} derarray;
+
+
+/* Prepacked DER structures finally, also overlay the dercursor and/or the
+ * derarray structures, to cover yet another phase of the process.  In this
+ * case, there still is a pointer to a derarray, but it has been marked with
+ * a variation on the length, namely with the most significant bit set.  This
+ * makes the variation recognisable during the packing process, without
+ * really affecting the potential of an in-memory stored data structure.
+ */
+
+#define DER_DERLEN_FLAG_CONSTRUCTED ((~(size_t)0)^((~(size_t)0)>>1))
+#define DER_DERLEN_ERROR ((~(size_t)0)>>1)
+
+typedef struct derprep {
+       dercursor *derray;
+       size_t derlen_msb;
+} derprep;
+
+
+/* The above structures may be overlaid in a union, but since the derray is
+ * usually replaced with another component, a variation is used by generated
+ * code.  Especially the "info" variation is then usually replaced by an
+ * overlay structure with names taken from the ASN.1 syntax.  For home-brewn
+ * parsers however, we do include a generic union below.
+ *
+ * The variation "wire" refers to the format on the wire, so plain DER.
+ * The variation "info" refers to an unpacked array of dercursors.
+ * The variation "prep" refers to a form that is prepared for sending.
+ */
+typedef union dernode {
+       dercursor wire;
+       derarray info;
+       derarray prep;
+} dernode;
+
+
+
+/* WRITING GOOD DER TARVERSAL PATHS.
+ *
+ * A path constitutes a “lazy” parser going through a DER structure.  It will
+ * trigger faults when it runs into them, but will otherwise be quite permissive.
+ * The lazy thing is that it simply advances a cursor to move through the ASN.1
+ * specification.
+ *
+ * To write an ASN.1 path expression, know the place where your cursor starts,
+ * which usually is the entire structure you stored in a buffer, and know where
+ * it should end.  To get to the end point, you will need to do a series of
+ * actions, namely skipping entries and entering other entries.  A path is
+ * basically a series of such steps.
+ *
+ * The ASN.1 syntax assures parseability with only one tag of lookahead.  This
+ * means that at any choice point in the syntax, you can safely skip ahead to the
+ * one you meant to find.  So, ignore a CHOICE and skip any OPTIONAL bits
+ * unless they are actually part of your path.
+ *
+ * The path traversal mechanism is a lazy syntax check, meaning it will check
+ * precisely those parts that it needs to traverse the path, but accept anything
+ * else without giving it a second look.  In other words, the code is sufficiently
+ * secure to not accept badly formatted data, but only inasfar as you actually
+ * need that data.  To this end, an OPTIONAL tag may be skipped by prefixing
+ * it with a special tag, and a CHOICE may be skipped regardless of the actual
+ * choice it made (if you want to enter either form, you should simply indicate
+ * the desired tag and the parser may return that it stopped being able to parse
+ * the structure from that point in your code, which tells you to try another path
+ * from the cursor position).  And yes, you can have an optional choice by
+ * first using DER_WALK_OPTIONAL, DER_WALK_CHOICE, DER_TAG_…
+ *
+ * Path components are specified by their tags, for which definitions follow; in
+ * addition, a flag DER_WALK_SKIP or DER_WALK_ENTER
+ * should be added to signal skip or enter.  Note that the
+ * first component is not being matched; you should compare its tag by
+ * looking what the cursor points at.  You should first ensure that you did not
+ * end up in an empty data structure though.  Paths are stored in derwalk[]
+ * that end in DER_WALK_END.
+ *
+ * There is no support for the long-form tag; that is, tags 31 with continued
+ * tag bytes; it is assumed that a tag always fits in one byte and otherwise
+ * ENOTIMPL is reported.
+ *
+ * An example path, with made-up ASN.1 markup in comments, is:
+ *
+ *     derwalk [] = {
+ *             DER_WALK_ENTER | DER_TAG_SEQUENCE,      // SEQUENCE OF
+ *             DER_WALK_ENTER | DER_TAG_CONTEXT (0),   // [0]
+ *             DER_WALK_OPTIONAL,
+ *             DER_WALK_SKIP  | DER_TAG_BOOLEAN,       // amActive BOOLEAN OPTIONAL
+ *             DER_WALK_CHOICE   ,                     // myMood CHOICE { …. }
+ *             DER_WALK_SKIP  | DER_TAG_OCTET_STRING,  // myNAME OCTET STRING
+ *             DER_WALK_ENTER | DER_TAG_SEQUENCE,      // options SEQUENCE OF
+ *             DER_WALK_END
+ *     };
+ *
+ * Having found the sequence we’re interested in, we could iterate over its
+ * elements.
+ *
+ * There certainly is room for a developer’s tool that creates such paths from a
+ * textual description and the ASN.1 syntax description.  Perhaps the formats
+ * used by libtasn1 could help doing this.  Compared to libtasn1, the advantage
+ * of this approach is that the code is simpler, memory management is simplified
+ * by using shared data, and that ought to improve efficiency rather dramatically.
+ */
+
+typedef const uint8_t derwalk;
+
+/* Special markers for instructions on a walking path */
+#define DER_WALK_END 0x00
+#define DER_WALK_OPTIONAL 0x3f
+#define DER_WALK_CHOICE 0x1f
+#define DER_WALK_ANY 0x1f
+
+/* Special markers for instructions for (un)packing syntax */
+#define DER_PACK_LEAVE 0x00
+#define DER_PACK_END 0x00
+#define DER_PACK_OPTIONAL 0x3f
+#define DER_PACK_CHOICE_BEGIN 0x1f
+#define DER_PACK_CHOICE_END 0x1f
+#define DER_PACK_ANY 0xdf
+
+/* Flags to add to tags to indicate entering or skipping them */
+#define DER_WALK_ENTER 0x20
+#define DER_WALK_SKIP  0x00
+#define DER_WALK_MATCHBITS (~(DER_WALK_ENTER | DER_WALK_SKIP))
+
+/* Flags to add to tags to indicate entering or storing them while (un)packing */
+#define DER_PACK_ENTER 0x20
+#define DER_PACK_STORE 0x00
+#define DER_PACK_MATCHBITS (~(DER_PACK_ENTER | DER_PACK_STORE))
+
+/* Universal tags and macros for application, contextual, private tags */
+#define DER_TAG_BOOLEAN 0x01
+#define DER_TAG_INTEGER 0x02
+#define DER_TAG_BITSTRING 0x03
+#define DER_TAG_BIT_STRING 0x03
+#define DER_TAG_OCTETSTRING 0x04
+#define DER_TAG_OCTET_STRING 0x04
+#define DER_TAG_NULL 0x05
+#define DER_TAG_OBJECTIDENTIFIER 0x06
+#define DER_TAG_OBJECT_IDENTIFIER 0x06
+#define DER_TAG_OID 0x06
+#define DER_TAG_OBJECT_DESCRIPTOR 0x07
+#define DER_TAG_EXTERNAL 0x08
+#define DER_TAG_REAL 0x09
+#define DER_TAG_ENUMERATED 0x0a
+#define DER_TAG_EMBEDDEDPDV 0x0b
+#define DER_TAG_EMBEDDED_PDV 0x0b
+#define DER_TAG_UTF8STRING 0x0c
+#define DER_TAG_RELATIVEOID 0x0d
+#define DER_TAG_RELATIVE_OID 0x0d
+#define DER_TAG_SEQUENCE 0x10
+#define DER_TAG_SEQUENCEOF 0x10
+#define DER_TAG_SEQUENCE_OF 0x10
+#define DER_TAG_SET 0x11
+#define DER_TAG_SETOF 0x11
+#define DER_TAG_SET_OF 0x11
+#define DER_TAG_NUMERICSTRING 0x12
+#define DER_TAG_PRINTABLESTRING 0x13
+#define DER_TAG_T61STRING 0x14
+#define DER_TAG_VIDEOTEXSTRING 0x15
+#define DER_TAG_IA5SRING 0x16
+#define DER_TAG_UTCTIME 0x17
+#define DER_TAG_GENERALIZEDTIME 0x18
+#define DER_TAG_GRAPHICSTRING 0x19
+#define DER_TAG_VISIBLESTRING 0x1a
+#define DER_TAG_GENERALSTRING 0x1b
+#define DER_TAG_UNIVERSALSTRING 0x1c
+#define DER_TAG_CHARACTERSTRING 0x1d
+#define DER_TAG_CHARACTER_STRING 0x1d
+#define DER_TAG_BMPSTRING 0x1e
+
+#define DER_TAG_APPLICATION(n) (0x40 | (n))
+#define DER_TAG_CONTEXT(n) (0x80 | (n))
+#define DER_TAG_PRIVATE(n) (0xc0 | (n))
+
+
+/* PARSING AN ENTIRE STRUCTURE
+ *
+ * Although it is useful to be able to construct paths that walk through
+ * DER-encoded ASN.1 data, it is often more useful to parse a structure
+ * and put its values into a general data structure.  This can be done
+ * by describing the entire syntax, in much the same fashion as a walking
+ * path that meanders through the entire structure.  Ideally of course,
+ * this is automatically derived from an ASN.1 syntax description.
+ *
+ * The idea of parsing an entire structure is like a depth-first walk
+ * through the entire structure, and store all the individual components
+ * into a cursor of their own.  The combined cursors can be overlayed
+ * by a structure type that has field names to match the ASN.1 syntax,
+ * so as to simplify locating entries to use in a program.
+ *
+ * Where we used symbols starting with DER_WALK_ to describe walking paths,
+ * we will use symbols starting with DER_PACK_ to describe the syntax
+ * descriptions, and we will use them both for packing and unpacking
+ * DER syntax.
+ *
+ * The flags DER_PACK_ENTER and DER_PACK_STORE indicate what to do with
+ * the current item being walked past; _ENTER unfolds a structures and
+ * dives into it, with the intention of backing out later using a
+ * DER_PACK_LEAVE.  It is normal for the first instruction to have a
+ * DER_PACK_ENTER flag.  The alternative flag is DER_PACK_STORE, which
+ * indicates that the present tag should not be processed, but instead
+ * be stored as a dercursor in the provided output array.
+ *
+ * Entries immediately following DER_PACK_OPTIONAL (including those
+ * structures marked in ASN.1 with a DEFAULT value, which by the way is
+ * not installed while unpacking a DER structure) will be skipped when
+ * they do not match; in case of DER_PACK_STORE flags, the respective
+ * dercursor structures will be set to derptr NULL and derlen 0.
+ *
+ * The ASN.1 pattern of a CHOICE between alternative substructures is
+ * marked between DER_PACK_CHOICE_BEGIN and DER_PACK_CHOICE_END, and may
+ * be surrounded by DER_PACK_OPTIONAL_ markers.  Each of the possible
+ * substructures is tried on the upcoming DER element, and all that do
+ * not match fill a dercursor structure with derptr NULL and derlen 0.
+ * As soon as one has matched, it is handled as specified and the
+ * remaining choice options are all set to derptr NULL and derlen 0.
+ * When encountering DER_PACK_CHOICE_END, it is tested whether one of
+ * the choices was fulfilled, except when DER_PACK_OPTIONAL_ markers
+ * surround the choice section.
+ *
+ * Not all elements have a predictable number of dercursors, in which
+ * case they must be parses them in a special manner.  This is true
+ * for the SEQUENCE OF and SET OF constructs; their total content needs
+ * to be _STOREd in a first pass, and separately parsed into an array
+ * of dercursors (or an overlayed structure type).  The caller allocates
+ * the space where it wants --on the stack or form a heap-- and also
+ * has the option of running through the contents with iterators based
+ * in the cursor that was _STOREd.
+ *
+ * Having done all this, we now have a structure that holds the various
+ * pieces of our DER structure, with field names that derive from the
+ * ASN.1 syntax.  In fact, using the same structural descriptions that
+ * also derive from the ASN syntax, we should be able to reproduce the
+ * DER information from the parser output structures.
+ * (NOTE: Variations in Primitive-or-Constructed?
+ */
+
+
+/* Test if the cursor points to a Constructed type.  Return 1 for yes, 0 for no.
+ * Note that too-short structures return 0, so this is not quite the inverse
+ * of der_isptimitive().
+ */
+static inline int der_isconstructed (const dercursor *crs) {
+       return (crs->derlen >= 2)? (((*crs->derptr) >> 5) & 0x01): 0;
+}
+
+/* Test if the cursor points to a Primitive type.  Return 1 for yes, 0 for no.
+ * Note that too-short structures return 0, so this is not quite the inverse
+ * of der_isconstructed().
+ */
+static inline int der_isprimitive (const dercursor *crs) {
+       return (crs->derlen >= 2)? (((~ *crs->derptr) >> 5) & 0x01) : 0;
+}
+
+
+/* Ensure that the length available to the cursor is non-empty.  This is sort of
+ * a DER-equivalent to a NULL pointer; it normally occurs only during
+ * iteration, where it can be used to test whether more data is available.
+ * Note that this does not actually read the DER-data, but instead the cursor.
+ * This functions return 1 for non-empty, or 0 for empty cursor lengths.
+ */
+static inline int der_isnonempty (const dercursor *crs) {
+       return (crs->derlen == 0)? 0: 1;
+}
+
+
+/* Test whether a cursor is set to a NULL value; this is the case when the
+ * data pointed to is actually NULL.
+ */
+static inline int der_isnull (const dercursor *crs) {
+       return (crs->derptr == NULL);
+}
+
+
+/* Analyse the header of a DER structure.  Pass back its tag, len and the
+ * total header length.  Analysis starts at crs, which will move past the
+ * header by updating both its derptr and derlen components.  This function
+ * returns 0 on success, or -1 on error (in which case it sets errno).
+ *
+ * For BIT STRINGS, this routine validates that remainder bits are cleared.
+ * Note that this is a difference between BER and DER; DER requires that
+ * the bits are 0 whereas BER welcomes arbitrary values.  In the interest
+ * of security (bit buffer overflows) and reproducability of signatures on
+ * data, this routine rejects non-zero remainder bits with an error.  For
+ * your program, this may mean that the number of remainder bits do not
+ * need to be checked if zero bits are acceptable without overflow risk.
+ */
+int der_header (dercursor *crs, uint8_t *tagp, size_t *lenp, uint8_t *hlenp);
+
+
+/* Update a cursor expression by walking into a DER-encoded ASN.1 structure.
+ * The return value is -1 on error, and errno will be set accordinly, and the
+ * cursor will not have been updated.  Otherwise, the return value is the number
+ * of unprocessed bytes on the path, so 0 when the entire path was processed.
+ * The count as non-error returns, so the cursor is updated.  Values higher than
+ * 0 indicate where in the path a tag could not be found; this may be helpful in
+ * learning about the structure that was being parsed, for example that an
+ * OPTIONAL or CHOICE part was absent from the DER bytes.
+ *
+ * Paths are sequence of one-byte choices to be made.  These choices are tags,
+ * because these are used by ASN.1 to decide on parsing choices to be made.
+ * The one difference is the interpretation of the Primitive/Constructed bit: when
+ * this is set to Primitive, the value will be skipped (even if it is Constructed)
+ * and when set to Constructed, the value will be entered and interpreted as ASN.1
+ * (even when it is setup as Primitive).
+ *
+ * In all the places where ASN.1 defines choices, such as CHOICE or OPTIONAL,
+ * it enforces distinct tags from the various choices.  This can be used in a path
+ * to skip such unknown parts in the encoding.
+ *
+ * When entering a BIT STRING, special treatment is implemented; the remaining
+ * bits will have to be zero, and these are then skipped while entering the
+ * remainder.  Note that this ensures that the byte-aligned DER structures are
+ * properly packed into a bit-aligned BIT STRING container.
+ */
+int der_walk (dercursor *crs, const derwalk *path);
+
+
+/* Skip the current value under the cursor.  Return an empty cursor value
+ * if nothing more is to be had.
+ * This function returns -1 on error and sets errno; 0 on success.
+ */
+int der_skip (dercursor *crs);
+
+
+/* Enter the current value under the cursor.  Return an empty cursor value
+ * if nothing more is to be had.  Some special handling is done for BIT STRING
+ * entrance; for them, the number of remainder bits is required to be 0 and
+ * that initial byte is skipped.
+ *
+ * This function returns -1 on error and sets errno; 0 on success.
+ */
+int der_enter (dercursor *crs);
+
+
+/* Unpack a structure, or possibly a sequence of structures.  The output
+ * is stored in subsequent entries of outarray, whose size should be
+ * precomputed to sufficient length.  The outarray will often be an
+ * overlay for a structure composed of dercursor elements with labels
+ * and nesting derived from ASN.1 syntax, and matching an (un)packing walk.
+ *
+ * The syntax is supplied without a length; proper formatting of the syntax
+ * is assumed, that is the number of DER_PACK_ENTER bits should be followed
+ * by an equal amount of DER_PACK_LEAVE instructions, and the choice
+ * markers DER_PACK_CHOICE_BEGIN ... DER_PACK_CHOICE_END must be properly
+ * nested with those instructions and with each other.  There is no
+ * protection for foolish specifications (and they will often be generated
+ * anyway).  This method additionally requires the first element in the
+ * syntax to be flagged with DER_PACK_ENTER.  (TODO: Permit non-looped use?)
+ *
+ * The cursor will be updated by this call to point to the position
+ * where unpacking stopped.  Refer to the return value to see if this is
+ * an error position.  The function returns 0 on success and -1 on failure.
+ * Upon failure, errno is also set, namely to EBADMSG for syntax problems
+ * or ERANGE for lengths or tags that are out of the supported range of
+ * this implementation.
+ */
+int der_unpack (dercursor *crs, const derwalk *syntax,
+                       dercursor *outarray, int repeats);
+
+
+/* Given an iterator, setup a second iterator to run over its contained
+ * components.  While iterating, the initial iterator must continue to be
+ * supplied, without modification to it.
+ *
+ * This function returns 1 upon success.  In case of failure, it
+ * returns 0; in addition, it sets the nested iterator for zero
+ * iterations.  A special case of error is when the container cursor is
+ * not pointing to a Constructed element; in this case an error is returned
+ * but the cursor will run over the contained elements when using the iterator.
+ *
+ * To be sensitive to errors, use this as follows:
+ *
+ *     if (der_iterate_first (cnt, &iter)) do {
+ *             ...process entry...
+ *     } while (der_iterate_next (&iter));
+ *
+ */
+int der_iterate_first (const dercursor *container, dercursor *iterator);
+
+/* Step forward with an iterator.  This assumes an iterator that was
+ * setup by der_iterate_first() and has since then not been modified.
+ */
+int der_iterate_next (dercursor *iterator);
+
+
+/* Count the number of elements available after entering the component
+ * under the cursor.  This is useful to know how many elements exist inside
+ * a SEQUENCE OF or SET OF, but may be used for other purposes as well.
+ */
+int der_countelements (dercursor *container);
+
+
+/* COMPOSING DER STRUCTURES FOR TRANSMISSION
+ *
+ * While working with DER data, the various dercursor structures can be passed
+ * around freely, because they are mere <pointer,length> tuples that reference
+ * independently management memory -- usually an input buffer.  As long as the
+ * input buffer is not cleared, the tuples can be carried around and copied
+ * and destroyed at will.
+ *
+ * Just like overlay structures are filled with dercursor fields labelled
+ * according to the ASN.1 syntax during der_unpack(), it is also possible to
+ * build up such structures for output with der_pack().  These structures
+ * must once more refer to independently allocated memory, which may be any
+ * mixture of sources: input buffers, stack portions, heap structures.  As
+ * long as these memory regions are kept around during der_pack().  After
+ * der_pack(), a new output area has been filled and the fragments can be
+ * cleared, deallocated and so on.
+ *
+ * The der_pack() routine is based on the same DER_PACK_ syntax descriptions
+ * used by der_unpack(), so it is straightforward to unpack a structure, make
+ * some modifications and pack its new incarnation.  
+ *
+ * One point of concern is the DER_STORE_ structure.  This can both be used
+ * for Primitive types and for Constructed types such as SEQUENCE OF and
+ * SET OF that either need an additional call to der_unpack() to be parsed,
+ * or iteration, or der_walk().  Reproducing those pieces literally is not
+ * a problem, and leads to the original data as a Primitive type.  But when
+ * the data is in fact Constructed, or perhaps modified, something else is
+ * required.  To accommodate that, you may need to der_prepack() such partial
+ * structures that are to be dealt with a Constructed types; most commonly
+ * however, is that this is necessary for SEQUENCE OF and SET OF, which never
+ * occur as Primitive types, and are therefore always treated correctly.
+ *
+ * The general idea of der_prepack() is that you supply a dercursor array
+ * and set it up in the original structure stored by DER_STORE_.  This
+ * leads to a different representation that will be recognised by
+ * der_pack() as a reference to such an array, which it will insert as
+ * a Constructed subtype, with the tag that it originally had during
+ * parsing.  Specifically, the highest bit of derlen, identified by the
+ * symbol DER_DERLEN_FLAG_CONSTRUCTED, will be set to indicate that the
+ * remainder of the derlen is to be interpreted as a number of dercursors,
+ * to be found at the derptr.  This differs from the usual meaning, which
+ * is a literal series of bytes pointed to by derptr and whose length is
+ * stored in derlen.  TODO: Do we really want to do it this way?!?
+ *
+ * Note that the special ASN.1 constructs "ANY" and "ANY DEFINED BY" are
+ * not a problem.  They are stored with the tag and length included, and
+ * are therefore trivial to reproduce.  Also, the CHOICE and OPTIONAL
+ * versions are trivially handled by having NULL dercursors.  Do avoid
+ * calling der_pack() with multiple options in one CHOICE set though.
+ *
+ * Based on all this, the der_pack() routine can scan over the syntax,
+ * look at the data at hand and determine what needs to go where.  It
+ * is often good to know the storage space before writing, and
+ * der_packlen() can be used to determine that.
+ *
+ * Since der_pack() assumes it is provided with the properly sized
+ * memory buffer to write to, it will start at the end and work its
+ * way back.  This means that lengths are known at their time of
+ * insertion into the buffer.  The idea stems from MIT Kerberos5,
+ * but unlike that implementation we do use a "forward" description
+ * and merely traverse it from the end to the beginning.  Note that
+ * the DER_PACK_ descriptions lend themselves well for doing this.
+ */
+
+
+/* Pack a memory buffer following the indicated syntax, and using the elements
+ * stored in the derray.  Enough memory is assumed to be available _before_
+ * outbuf_end_opt; to find how large this buffer needs to be, it is possible to
+ * call this function with outbuf_end_opt set to NULL.
+ *
+ * The return value is the same, regardless of outbuf_end_opt being NULL or not;
+ * it is the length of the required output buffer.  When an error occurs, the
+ * value 0 is returned, but that cannot happen on a second run on the same data
+ * with only the outbuf_end_opt set to non-NULL.
+ *
+ * Please note once more that outbuf_end_opt, when non-NULL, points to the
+ * first byte that is _not_ filled with the output DER data.  The value will
+ * be decremented in this function for the bytes written.  This is quite
+ * simply a more optimal strategy for DER production than anything else.
+ * And yes, this is funny in an API, but you have the information and we would
+ * otherwise ask you to pass it in, need to check it, you would then need to
+ * check for extra error returns, ... so this is in fact simpler.
+ *
+ * Any parts of this structure that need to be prepacked are assumed to have
+ * been prepared with der_prepack().  If your packaged structures show up as
+ * Primitive where they should have been Constructed, then this is where to
+ * look.
+ */
+size_t der_pack (const derwalk *syntax, const dercursor *derray,
+                                       uint8_t *outbuf_end_opt);
+
+
+/* Pre-package the given DER array into the dercursor that is also provided.
+ * This operation modifies the information stored in the destination field,
+ * in a way that stops it from being interpreted properly in the usualy
+ * manner, but it _does_ prepare it for der_pack() in a way that will include
+ * the array of dercursor as a consecutive sequence, without any additional
+ * headers, markers or other frills.
+ */
+void der_prepack (dercursor *derray, size_t arraycount, derarray *target);
+
+
+#endif /* QUICK_DER_H */
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644 (file)
index 0000000..3b41bb4
--- /dev/null
@@ -0,0 +1,45 @@
+OBJS = der_pack.o der_prepack.o der_unpack.o der_iterate.o der_walk.o der_skipenter.o der_header.o
+
+TARGETS = libquickder.a libquickder.so
+
+PREFIX = /usr/local
+
+all: $(TARGETS)
+
+install: $(TARGETS)
+       install $(TARGETS) "$(PREFIX)/lib"
+       mkdir -p "$(PREFIX)/include/quick-der"
+       install ../include/quick-der/api.h "$(PREFIX)/include/quick-der"
+
+uninstall:
+       for f in $(TARGETS); do rm -f "$(PREFIX)/lib/$$f" ; done
+       rm -f "$(PREFIX)/include/quick-der/api.h"
+       rmdir --ignore-fail-on-non-empty "$(PREFIX)/include/quick-der"
+
+clean:
+       rm -f $(OBJS) $(TARGETS)
+
+.c.o:
+       gcc -fPIC -Os -c -I../include -o "$@" "$<"
+
+libquickder.a: $(OBJS)
+       rm -rf "$@"
+       ar rc "$@" $(OBJS)
+
+libquickder.so: $(OBJS)
+       gcc -I../include --shared -Os -o "$@" $(OBJS)
+
+# test-kxover: test-kxover.c quick-der.c libquickder.a
+#      gcc -I../include -ggdb3 -o $@ test-kxover.c libquickder.a -lc
+# 
+# test-cert-1: test-cert-1.c quick-der.c libquickder.a
+#      gcc -I../include -ggdb3 -o $@ test-cert-1.c libquickder.a -lc
+# 
+# test-cert-2: test-cert-2.c quick-der.c libquickder.a
+#      gcc -I../include -ggdb3 -DDEBUG -o $@ test-cert-2.c libquickder.a -lc
+# 
+# test-cert-3: test-cert-3.c quick-der.c libquickder.a
+#      gcc -I../include -ggdb3 -o $@ test-cert-3.c libquickder.a -lc
+
+stats: $(OBJS)
+       @for o in *.o ; do objdump -h -j .text $$o | sed -e '/\.text/!d' -e 's/^.*\.text[ \t]*\([^ \t]*\).*/\1'" $${o%.o}/" ;done | while read sz fun ; do printf '%5d %s\n' 0x$$sz $$fun ; done
diff --git a/lib/der_header.c b/lib/der_header.c
new file mode 100644 (file)
index 0000000..0e6a5bc
--- /dev/null
@@ -0,0 +1,73 @@
+#include <quick-der/api.h>
+
+
+/* Analyse the header of a DER structure.  Pass back its tag, len and the
+ * total header length.  Analysis starts at crs, which will move past the
+ * header by updating both its derptr and derlen components.  This function
+ * returns 0 on success, or -1 on error (in which case it sets errno).
+ *
+ * For BIT STRINGS, this routine validates that remainder bits are cleared.
+ * Note that this is a difference between BER and DER; DER requires that
+ * the bits are 0 whereas BER welcomes arbitrary values.  In the interest
+ * of security (bit buffer overflows) and reproducability of signatures on
+ * data, this routine rejects non-zero remainder bits with an error.  For
+ * your program, this may mean that the number of remainder bits do not
+ * need to be checked if zero bits are acceptable without overflow risk.
+ */
+int der_header (dercursor *crs, uint8_t *tagp, size_t *lenp, uint8_t *hlenp) {
+       uint8_t tag;
+       uint8_t lenlen;
+       uint8_t rembits;
+       uint8_t rembyte;
+       size_t len;
+       *tagp = tag = *crs->derptr++;
+       crs->derlen--;
+       if ((tag & 0x1f) == 0x1f) {
+               // No support for long tags
+               errno = ERANGE;
+               return -1;
+       }
+       len = *crs->derptr++;
+       crs->derlen--;
+       if (len & 0x80) {
+               lenlen = len & 0x7f;
+               *hlenp = 2 + lenlen;
+               if (lenlen > crs->derlen) {
+                       errno = EBADMSG;
+                       return -1;
+               }
+               if (lenlen > sizeof (size_t)) {
+                       // No support for such long sizes
+                       errno = ERANGE;
+                       return -1;
+               }
+               crs->derlen -= lenlen;
+               len = 0;
+               while (lenlen-- > 0) {
+                       len <<= 8;
+                       len |= *crs->derptr++;
+               }
+       } else {
+               *hlenp = 2;
+       }
+       if (len & DER_DERLEN_FLAG_CONSTRUCTED) {
+               errno = ERANGE;
+               return -1;
+       }
+       if (len > crs->derlen) {
+               errno = EBADMSG;
+               return -1;
+       }
+       // Special treatment for BIT STRING (one additional header byte)
+       if (tag == DER_TAG_BITSTRING) {
+               rembits = *crs->derptr;
+               rembyte = crs->derptr [len-1] & (0xff >> (8 - rembits));
+               if ((len == 0) || (*crs->derptr > 7) || (rembyte != 0x00)) {
+                       errno = EBADMSG;
+                       return -1;
+               }
+       }
+       *lenp = len;
+DPRINTF ("DEBUG: Header analysis: tag 0x%02x, hdrlen %d, len %d, rest %d\n", *tagp, *hlenp, *lenp, crs->derlen);
+       return 0;
+}
diff --git a/lib/der_iterate.c b/lib/der_iterate.c
new file mode 100644 (file)
index 0000000..b0635e6
--- /dev/null
@@ -0,0 +1,58 @@
+#include <quick-der/api.h>
+
+
+/* Given a dercursor, setup an iterator to run over its contained components.
+ * While iterating, the initial iterator must continue to be supplied, without
+ * modification to it.
+ *
+ * This function returns 1 upon success.  In case of failure, it
+ * returns 0; in addition, it sets the nested iterator for zero
+ * iterations.  A special case of error is when the container cursor is
+ * not pointing to a Constructed element; in this case an error is returned
+ * but the cursor will run over the contained elements when using the iterator.
+ *
+ * To be sensitive to errors, use this as follows:
+ *
+ *     if (der_iterate_first (cnt, &iter)) do {
+ *             ...process entry...
+ *     } while (der_iterate_next (&iter));
+ *
+ */
+int der_iterate_first (const dercursor *container, dercursor *iterator) {
+#if 0  /* Old code */
+       *iterator = *container;
+       der_enter (iterator);
+       if (!der_isnonempty (iterator)) {
+               return 0;
+       }
+       if (der_isconstructed (container)) {
+               return 1;
+       } else {
+               return 0;
+       }
+#else
+       *iterator = *container;
+#endif
+}
+
+/* Step forward with an iterator.  This assumes an iterator that was
+ * setup by der_iterate_first() and has since then not been modified.
+ */
+int der_iterate_next (dercursor *iterator) {
+       der_skip (iterator);
+       return (iterator->derlen >= 2);
+}
+
+
+/* Count the number of elements available after entering the component
+ * under the cursor.  This is useful to know how many elements exist inside
+ * a SEQUENCE OF or SET OF, but may be used for other purposes as well.
+ */
+int der_countelements (dercursor *container) {
+       int retval = 0;
+       dercursor iter;
+       if (der_iterate_first (container, &iter)) do {
+               retval++;
+       } while (der_iterate_next (&iter));
+       return retval;
+}
diff --git a/lib/der_pack.c b/lib/der_pack.c
new file mode 100644 (file)
index 0000000..dd03750
--- /dev/null
@@ -0,0 +1,231 @@
+#include <quick-der/api.h>
+
+
+/* Backward-insert the bytes for the individual entries of the given derprep
+ * structure; the entry is assumed to be setup with prepack, and so to contain
+ * a derray pointing to an array of dercursor, and the derlen_msb is supposed
+ * to be the element count with the highest bit set for the
+ * DER_DERLEN_FLAG_CONSTRUCTED flagging.
+ *
+ * This function does not return failure under the assumption that a
+ * properly-sized buffer is available for it.  The return value is
+ * still the size, because it is needed when processing the data.
+ *
+ * If bufend is NULL, this function can be used to measure the size of the
+ * total insertion.  In this case, the function may return DER_DERLEN_ERROR
+ * to indicate an error.
+ */
+size_t der_pack_prepack (const derprep *derp, uint8_t **bufend) {
+       size_t totlen = 0;
+       size_t elmlen;
+       size_t cnt = derp->derlen_msb & ~DER_DERLEN_FLAG_CONSTRUCTED;
+       dercursor *crs = derp->derray;
+       uint8_t *buf;
+       while (cnt-- > 0) {
+               if (crs->derlen & DER_DERLEN_FLAG_CONSTRUCTED) {
+                       elmlen = der_pack_prepack ((const derprep *) crs+cnt, bufend);
+                       if (elmlen == DER_DERLEN_ERROR) {
+                               return DER_DERLEN_ERROR;
+                       }
+               } else {
+                       if (bufend) {
+                               buf = *bufend;
+                               buf -= elmlen;
+                               memcpy (buf, crs [cnt].derptr, elmlen);
+DPRINTF ("DEBUG: Wrote %4d bytes to 0x%016llx\n", elmlen, buf);
+                               *bufend = buf;
+                       }
+                       elmlen = crs->derlen;
+               }
+               totlen += elmlen;
+               if ((totlen | elmlen) & DER_DERLEN_FLAG_CONSTRUCTED) {
+                       return DER_DERLEN_ERROR;
+               }
+       }
+       return totlen;
+}
+
+
+/* Backward-insert the bytes for der_pack() for the given syntax, using the
+ * DER array for elementary values.  Special handling is provided when a
+ * BIT STRING is entered; this encapsulates byte-aligned DER codes into a
+ * bit-aligned BIT STRING, so we can insert the remainder bits set to 0.
+ * Also handled specially is DER_PACK_ANY, which causes the entire structure
+ * to be stored or returned, including its DER header.
+ *
+ * The routine returns 0 if it encounters an error, or otherwise the number
+ * of bytes filled.  When it is called with a non-NULL bufend and an output
+ * buffer of the right size, it will not return an error.
+ */
+static size_t der_pack_rec (const derwalk *syntax, int *stxlen,
+                               uint8_t **bufend,
+                               const dercursor *derray, size_t *offsetp) {
+       size_t totlen = 0;
+       size_t elmlen = 0;
+       size_t tmplen;
+       bool addhdr;
+       bool bitstr;
+       uint8_t cmd;
+       uint8_t tag;
+       uint8_t *buf;
+       uint8_t lenlen;
+       const dercursor *dernext;
+DPRINTF ("DEBUG: Entered recursive call der_pack_rec() with bufend=0x%016llx\n", bufend? *bufend: 0);
+       do {
+               // deref stxend; decrease the stored pointer; deref that pointer:
+               tag = cmd = syntax [-- *stxlen];
+               bitstr = (cmd == (DER_PACK_ENTER | DER_TAG_BITSTRING));;
+DPRINTF ("DEBUG: Command to pack_rec() is 0x%02x, collected length is %zd, offset is %zd\n", cmd, totlen, *offsetp);
+               // Note: DER_PACK_ANY ends up under DER_PACK_STORE below
+               if ((cmd == DER_PACK_CHOICE_BEGIN)
+                                       || (cmd == DER_PACK_CHOICE_END)
+                                       || (cmd == DER_PACK_OPTIONAL)) {
+                       // Skip, and rely on consistent NULL dercursor entries
+DPRINTF ("DEBUG: Choice|Optional command has no data\n");
+                       cmd = 0x00;  // Avoid falling out (OPTIONAL & ENTER)
+                       continue;
+               } else if (cmd & DER_PACK_ENTER) {
+                       // Ends current (recursive) der_pack_rec() for sub-part
+                       // Continue below, where the <tag,elmlen> header is added
+                       addhdr = 1;
+                       elmlen = totlen;
+                       totlen = bitstr? 1: 0;
+DPRINTF ("DEBUG: Post-enter element, moved totlen %zd to element length\n", elmlen);
+               } else if (cmd == DER_PACK_LEAVE) {
+                       // Make a recursive call for what precedes DER_PACK_LEAVE
+                       elmlen = der_pack_rec (syntax, stxlen, bufend, derray, offsetp);
+                       if (elmlen == DER_DERLEN_ERROR) {
+                               return DER_DERLEN_ERROR;
+                       }
+                       addhdr = 0;
+DPRINTF ("DEBUG: Recursive element length set to %zd\n", elmlen);
+               } else {
+                       // We have hit upon a DER_PACK_STORE value (includes ANY)
+                       addhdr = (cmd != DER_PACK_ANY);
+                       // Consume one array element, even if it will be NULL
+                       (*offsetp)--;
+                       dernext = derray + *offsetp;    // offset may have changed
+DPRINTF ("DEBUG: Updated offset to %zd, pointer is 0x%016llx\n", *offsetp, dernext);
+                       if (der_isnull (dernext)) {
+                               // Do not pack this entry, DEFAULT or CHOICE
+                               elmlen = 0;
+                               addhdr = 0;
+DPRINTF ("DEBUG: Consumed NULL entry at %zd, so elmlen set to 0\n", *offsetp);
+                       } else if (derray->derlen & DER_DERLEN_FLAG_CONSTRUCTED) {
+                               // Prepacked Constructed, non-NULL entry
+                               elmlen = der_pack_prepack ((const derprep *) dernext, bufend);
+                               if (elmlen == DER_DERLEN_ERROR) {
+                                       return DER_DERLEN_ERROR;
+                               }
+DPRINTF ("DEBUG: Fetched length from constructed, recursive element\n");
+                       } else {
+                               // Primitive, non-NULL entry
+                               elmlen = dernext->derlen;
+                               if ((elmlen > 0) && (bufend != NULL)) {
+                                       buf = *bufend;
+                                       buf -= elmlen;
+                                       memcpy (buf, dernext->derptr, elmlen);
+DPRINTF ("DEBUG: Wrote %4d bytes to 0x%016llx\n", elmlen, buf);
+                                       *bufend = buf;
+                               }
+                               if ((tag == 0x08) || (tag == 0x0b)
+                                               || (tag == 0x10) || (tag == 0x11)) {
+                                       tag |= 0x20;    // Constructed, even STORED
+                               }
+DPRINTF ("DEBUG: Fetched length from primitive element\n");
+                       }
+DPRINTF ("DEBUG: Stored element length set to %zd at offset %zd\n", elmlen, *offsetp);
+               }
+               if (addhdr) {
+                       if (bufend) {
+                               buf = *bufend;
+                               if (bitstr) {
+                                       * --buf = 0x00;
+                               }
+                       }
+                       lenlen = 0;
+                       if (elmlen >= 0x80) {
+                               tmplen = elmlen;
+                               while (tmplen > 0) {
+                                       if (bufend) {
+                                               * -- buf = (tmplen & 0xff);
+                                       }
+                                       tmplen >>= 8;
+                                       lenlen++;
+                               }
+                       }
+                       if (bufend) {
+                               * -- buf = (elmlen >= 0x80)? (lenlen|0x80): elmlen;
+                               * -- buf = tag;
+                               * bufend = buf;
+DPRINTF ("DEBUG: Wrote %4d bytes to 0x%016llx\n", 2 + lenlen, buf);
+                       }
+                       elmlen += 2 + lenlen;
+               }
+DPRINTF ("DEBUG: Adding %zd to length %zd, collected length is %zd\n", elmlen, totlen, elmlen + totlen);
+               totlen += elmlen;
+               if ((elmlen | totlen) & DER_DERLEN_FLAG_CONSTRUCTED) {
+                       return DER_DERLEN_ERROR;
+               }
+       // Special cases: DER_PACK_OPTIONAL has had the cmd value reset to 0x00
+       // Note: The while loop terminates *after* processing the ENTER cmd
+       } while (((cmd & DER_PACK_ENTER) == 0x00) && (*stxlen > 0));
+DPRINTF ("DEBUG: Leaving recursive call der_pack_rec() with bufend=0x%016llx\n", bufend? *bufend: 0);
+       return totlen;
+}
+
+
+/* Pack a memory buffer following the indicated syntax, and using the elements
+ * stored in the derray.  Enough memory is assumed to be available _before_
+ * outbuf_end_opt; to find how large this buffer needs to be, it is possible to
+ * call this function with outbuf_end_opt set to NULL.
+ *
+ * The return value is the same, regardless of outbuf_end_opt being NULL or not;
+ * it is the length of the required output buffer.  When an error occurs, the
+ * value 0 is returned, but that cannot happen on a second run on the same data
+ * with only the outbuf_end_opt set to non-NULL.
+ *
+ * Please note once more that outbuf_end_opt, when non-NULL, points to the
+ * first byte that is _not_ filled with the output DER data.  The value will
+ * be decremented in this function for the bytes written.  This is quite
+ * simply a more optimal strategy for DER production than anything else.
+ * And yes, this is funny in an API, but you have the information and we would
+ * otherwise ask you to pass it in, need to check it, you would then need to
+ * check for extra error returns, ... so this is in fact simpler.
+ *
+ * Any parts of this structure that need to be prepacked are assumed to have
+ * been prepared with der_prepack().  If your packaged structures show up as
+ * Primitive where they should have been Constructed, then this is where to
+ * look.
+ */
+size_t der_pack (const derwalk *syntax, const dercursor *derray,
+                                       uint8_t *outbuf_end_opt) {
+       int entered = 0;
+       uint8_t cmd;
+       int stxlen = 0;
+       size_t derraylen = 0;
+       size_t totlen = 0;
+       while (cmd = syntax [stxlen], (entered > 0) || (cmd != DER_PACK_END)) {
+               stxlen++;
+               if (cmd & DER_PACK_ENTER) {
+                       if (cmd != DER_PACK_OPTIONAL) {
+                               entered++;
+                       }
+               } else {
+                       if (cmd == DER_PACK_LEAVE) {
+                               entered--;
+                       } else if ((cmd != DER_PACK_CHOICE_BEGIN) && (cmd != DER_PACK_CHOICE_END)) {
+                               // Remaining commands store data (including ANY)
+                               derraylen++;
+                       }
+               }
+       }
+DPRINTF ("DEBUG: Skipping %d syntax bytes, ending in %02x %02x %02x %02x | %02x\n", stxlen, syntax [-4], syntax [-3], syntax [-2], syntax [-1], syntax [0]);
+       while (stxlen > 0) {
+               totlen += der_pack_rec (syntax, &stxlen,
+                               outbuf_end_opt? &outbuf_end_opt: NULL,
+                               derray, &derraylen);
+       }
+       // One could assert() on derraylen == NULL, and syntax back to initial
+       return totlen;
+}
diff --git a/lib/der_prepack.c b/lib/der_prepack.c
new file mode 100644 (file)
index 0000000..20c96f3
--- /dev/null
@@ -0,0 +1,15 @@
+#include <quick-der/api.h>
+
+
+/* Pre-package the given DER array into the dercursor that is also provided.
+ * This operation modifies the information stored in the destination field,
+ * in a way that stops it from being interpreted properly in the usualy
+ * manner, but it _does_ prepare it for der_pack() in a way that will include
+ * the array of dercursor as a consecutive sequence, without any additional
+ * headers, markers or other frills.
+ */
+void der_prepack (dercursor *derray, size_t arraycount, derarray *target) {
+       target->derray = derray;
+       target->dercnt = arraycount;
+}
+
diff --git a/lib/der_skipenter.c b/lib/der_skipenter.c
new file mode 100644 (file)
index 0000000..ca2d4c9
--- /dev/null
@@ -0,0 +1,46 @@
+#include <quick-der/api.h>
+
+
+/* Skip the current value under the cursor.  Return an empty cursor value
+ * if nothing more is to be had.
+ * This function returns -1 on error and sets errno; 0 on success.
+ */
+int der_skip (dercursor *crs) {
+       uint8_t tag;
+       uint8_t hlen;
+       size_t len;
+       if (der_header (crs, &tag, &len, &hlen)) {
+               crs->derptr = NULL;
+               crs->derlen = 0;
+               return -1;
+       } else {
+               crs->derptr += len;
+               crs->derlen -= len;
+               return 0;
+       }
+}
+
+/* Enter the current value under the cursor.  Return an empty cursor value
+ * if nothing more is to be had.  Some special handling is done for BIT STRING
+ * entrance; for them, the number of remainder bits is required to be 0 and
+ * that initial byte is skipped.
+ *
+ * This function returns -1 on error and sets errno; 0 on success.
+ */
+int der_enter (dercursor *crs) {
+       uint8_t tag;
+       uint8_t hlen;
+       size_t len;
+       if (der_header (crs, &tag, &len, &hlen)) {
+               crs->derptr = NULL;
+               crs->derlen = 0;
+               return -1;
+       } else {
+               crs->derlen = len;
+               return 0;
+       }
+       if (tag == DER_TAG_BITSTRING) {
+               crs->derlen--;
+               crs->derptr++;
+       }
+}
diff --git a/lib/der_unpack.c b/lib/der_unpack.c
new file mode 100644 (file)
index 0000000..f6650e8
--- /dev/null
@@ -0,0 +1,306 @@
+#include <quick-der/api.h>
+
+
+/* Unpack a DER structure based on its ASN.1 description, mapped to DER_PACK_
+ * instructions.  This includes handling of OPTIONAL/DEFAULT and CHOICE syntax.
+ * It also includes a DER_PACK_ENTER flag and DER_PACK_LEAVE instruction to
+ * dive into a structure, as well as a DER_PACK_STORE flag to store the outcome
+ * of a construct.
+ *
+ * When an error is encountered, this function returns NULL and sets errno to
+ * a suitable error code.  In case of success, the return value gives the
+ * new position from which the walk should continue.  It also updates the
+ * number of elements in the output array in outctr.
+ *
+ * This routine takes a dercursor that it will move forward, up to a point
+ * where parsing failed or was simply done.  In addition, it uses an
+ * outarray and an outctr that is incremented while entries (NULL or other)
+ * are filled in.
+ *
+ * This recursive routine processes a number of flags that modify its action:
+ *  - choice implements a CHOICE of alternatives (possibly OPTIONAL) and
+ *    will normally require precisely one of these alternatives to match;
+ *  - optional indicates that the first DER element does not need to match
+ *    because the syntax marks the *walk as OPTIONAL or having a DEFAULT;
+ *  - optout indicates that the entries should be parsed and skipped, but
+ *    only produce NULL entries due to CHOICE or OPTIONAL semantics.
+ * Note that optout applies to any length of DER elements.  It is used to
+ * support incrementing outctr while setting NULL values where otherwise
+ * there might have been actual values, but the syntax context blocks that.
+ */
+static const derwalk *der_unpack_rec (dercursor *crs, const derwalk *walk,
+                               dercursor *outarray,
+                               int *outctr,
+                               bool choice,
+                               bool optional,
+                               bool optout) {
+       uint8_t tag;
+       uint8_t hlen;
+       uint8_t terminal;
+       uint8_t cmd;
+       size_t len;
+       dercursor newcrs;
+       dercursor hdrcrs;
+       bool chosen = 0;
+       bool optoutsub = optout;
+DPRINTF ("DEBUG: Entering der_unpack_rec() at 0x%08llx\n", (intptr_t) walk);
+       //
+       // Decide on the terminal code and parse until that value
+       if (choice) {
+               terminal = DER_PACK_CHOICE_END;
+       } else {
+               terminal = DER_PACK_LEAVE;
+       }
+DPRINTF ("DEBUG: Start looping around for 0x%02x\n", terminal);
+       while (*walk != terminal) {
+DPRINTF ("DEBUG: Entering loop with choice=%d, optional=%d, optout=%d\n", choice, optional, optout);
+               //
+               // First detect the more complex structures for CHOICE and OPTIONAL
+               if (*walk == DER_PACK_OPTIONAL) {
+                       //
+                       // Parse the prefix command for OPTION / DEFAULT
+DPRINTF ("DEBUG: Encountered OPTIONAL\n");
+                       if (optional || choice) {
+                               // Nested OPTION, that can't be good
+                               // OPTION within CHOICE also signifies trouble
+                               errno = EBADMSG;
+                               return NULL;
+                       }
+                       optional = 1; // for the one next entry (may be ENTER)
+                       walk++;
+               }
+               if (*walk == DER_PACK_CHOICE_BEGIN) {
+                       //
+                       // Parse for a choice, leaving the OPTIONAL flag as is
+DPRINTF ("DEBUG: Encountered CHOICE\n");
+                       if (choice) {
+                               // Nested CHOCIE, that can't be good
+                               errno = EBADMSG;
+                               return NULL;
+                       }
+DPRINTF ("DEBUG: Making recursive call because of CHOICE_BEGIN\n");
+                       walk = der_unpack_rec (crs, walk + 1,
+                                       outarray, outctr,
+                                       1, optional, optout);
+                       if (walk == NULL) {
+                               // Pass on inner error
+                               return NULL;
+                       }
+DPRINTF ("DEBUG: Ended recursive call because of CHOICE_END\n");
+                       optional = 0; // any 1 was used up by recursive CHOICE
+DPRINTF ("DEBUG: Next command up is 0x%02x with %d left\n", *walk, crs->derlen);
+                       continue;
+               }
+               //
+               // Check if we have anything left to process at the DER cursor
+               if (crs->derlen < 2) {
+                       if ((crs->derlen == 0) && optional) {
+                               // Empty value is acceptable, skip ahead
+                               if (*walk & DER_PACK_STORE) {
+                                       memset (outarray + (*outctr)++,
+                                                       0,
+                                                       sizeof (dercursor));
+                                       walk++;
+                                       continue;
+                               }
+                       } else {
+DPRINTF ("ERROR: Message size is only %d\n", crs->derlen);
+                               errno = EBADMSG;
+                               return NULL;
+                       }
+               }
+               //
+               // Pickup the tag and check its sanity
+               hdrcrs = newcrs = *crs;
+               if (der_header (&hdrcrs, &tag, &len, &hlen)) {
+                       return NULL;
+               }
+               //
+               // Now decide how to handle the element.  If the OPTIONAL flag
+               // is active, then a mismatch in the first attempted match is
+               // accepted, but that will clear the OPTIONAL flag.  If the
+               // OPTOUT flag is active, then everything happes as it would
+               // normally happen, except that _STORE always stores NULL
+               // values.  If the CHOICE flag is active, then a match is
+               // processed and OPTOUT parsing is applied to all remaining
+               // elements in the CHOICE (and the preceding OPTIONAL flag
+               // applies to the whole CHOICE instead of a single element).
+               // This assumes OPTION cannot occur immediately inside CHOICE.
+               cmd = *walk++;
+DPRINTF ("DEBUG: Instruction 0x%02x decodes 0x%02x size %d of %d\n", cmd, tag, len, hlen + len);
+               if (chosen) {
+DPRINTF ("DEBUG: CHOICE was already made\n");
+                       // Already matched CHOICE, so don't try matching anymore;
+                       // we chase on with optout && optoutsub
+                       optoutsub = 1;
+               } else if ((cmd == DER_PACK_ANY) || ((tag ^ cmd) & DER_PACK_MATCHBITS) == 0x00) {
+DPRINTF ("DEBUG: Found a match\n");
+                       // We found a match
+                       optoutsub = optout;     // Hopefully store the value
+                       newcrs.derptr += hlen + len;    // Skip over match
+                       newcrs.derlen -= hlen + len;
+                       if (cmd == (DER_PACK_ENTER | DER_TAG_BITSTRING)) {
+                               // Check the remainder bits
+                               if (*hdrcrs.derptr != 0x00) {
+                                       errno = EBADMSG;
+                                       return NULL;
+                               }
+                               // Skip the remainder bits
+                               hdrcrs.derptr++;
+                               hdrcrs.derlen--;
+                       }
+                       if (choice) {
+                               // We matched a choice, so skip other choices
+DPRINTF ("DEBUG: Moreover, found a matching choice\n");
+                               optoutsub = optout; // For the current element
+                               optout = 1;         // For   following elements
+                               chosen = 1;         // Bypass match of following
+                       }
+               } else if (choice) {
+DPRINTF ("DEBUG: Found a non-matching choice\n");
+                       // No match, but CHOICE flag permits that while choosing
+                       optoutsub = 1;  // suppress value copy for current elem
+               } else if (optional) {
+DPRINTF ("DEBUG: Mismatch forgiven because we're doing optional recognition\n");
+                       // No match, but OPTIONAL flag permits that once
+                       optoutsub = 1;  // suppress value copy for current elem
+               } else {
+DPRINTF ("ERROR: Mismatch in neither CHOICE nor OPTIONAL decoding parts\n");
+                       // No match and nothing helped to make that acceptable
+                       errno = EBADMSG;
+                       return NULL;
+               }
+               //
+               // Now see if we need to ENTER a substructure.  If so, we will
+               // use optoutsub for optout.  We never pass CHOICE because we
+               // are past the choosing tag, and we also do not pass OPTIONAL
+               // because that applied to the present tag.  The cursor passed
+               // is newcrs, which is then also updated and later copied to crs.
+               if (cmd & DER_PACK_ENTER) {
+                       newcrs = hdrcrs;
+                       if (cmd == (DER_PACK_ENTER | DER_TAG_BITSTRING)) {
+                               if (*newcrs.derptr++ != 0x00) {
+                                       errno = EBADMSG;
+                                       return NULL;
+                               }
+                               newcrs.derlen--;
+                       }
+DPRINTF ("DEBUG: Making recursive call because of ENTER bit with rest %d\n", newcrs.derlen);
+                       walk = der_unpack_rec (&newcrs, walk,
+                                       outarray, outctr,
+                                       0, 0, optoutsub);
+                       if (walk == NULL) {
+                               return NULL;
+                       }
+DPRINTF ("DEBUG: Ended recursive call because of ENTER bit\n");
+               //
+               // The alternative to _ENTER is to _STORE the current value.
+               // Whether we actually do that, or store a NULL value instead,
+               // is determined by the opt-out choice for the current element,
+               // so by optoutsub.
+               //
+               // Even when we retry the same DER code to another walking step,
+               // then we want to store the walk-guided syntax component.
+               } else if (optoutsub) {
+                       // We opt out on this elem, so we store a NULL cursor
+DPRINTF ("DEBUG: Opting out of output value #%d, setting it to NULL cursor\n", *outctr);
+                       memset (outarray + (*outctr)++,
+                                       0,
+                                       sizeof (dercursor));
+               } else {
+                       // We store the DER value found
+DPRINTF ("DEBUG: Storing output value #%d with %d bytes 0x%02x, 0x%02x, 0x%02x, ...\n", *outctr, crs->derlen, crs->derptr [0], crs->derptr [1], crs->derptr [2]);
+                       //TODO:COUNTDOWNLENGTHS// outarray [ (*outctr)++ ] = *crs;
+                       if (cmd == DER_PACK_ANY) {
+                               outarray [ (*outctr) ].derptr = crs->derptr;
+                               outarray [ (*outctr) ].derlen = hlen + len;
+                       } else {
+                               outarray [ (*outctr) ].derptr = hdrcrs.derptr;
+                               outarray [ (*outctr) ].derlen = len;
+                       }
+                       (*outctr)++;
+               }
+               //
+               // If this is not a CHOICE, then any OPTIONAL flag is cleared;
+               // the prefix serves at most one element, although that can be
+               // extended with the _ENTER flag to a range of elements.  But
+               // at this point, the use of the OPTIONAL bit has played out.
+               if (!choice) {
+                       optional = 0;
+               }
+               //
+               // Update the visible DER cursor, making it either go back to
+               // what we just tried to match or advance to the next DER element
+               *crs = newcrs;
+DPRINTF ("DEBUG: Considering another loop-around for 0x%02x on 0x%02x with %d left\n", terminal, *walk, crs->derlen);
+       }
+DPRINTF ("DEBUG: Ended looping around for 0x%02x with %d left\n", terminal, crs->derlen);
+       //
+       // Skip past the detected terminal on the walk
+       walk++;
+       //
+       // If this is a CHOICE and it is not OPTIONAL, then failure if all
+       // attempted matches failed.  Note that OPTOUT is another matter; it
+       // details surrounding OPTIONALs, which are not of influence on
+       // the CHOICE being subjected to a local OPTIONAL prefix.
+       // Note that the choice flag is cleared as soon as it matches.
+       if (choice && (!chosen) && (!optional)) {
+DPRINTF ("ERROR: Ended a CHOICE without choosing, even though it is not OPTIONAL\n");
+               errno = EBADMSG;
+               return NULL;
+       }
+       //
+       // It is also an error if we were looping until DER_PACK_LEAVE but
+       // we did not actually run into the end of the DER encoding.
+#if 0
+       //TODO// This is not working because there may be surroundings continuing
+       if ((terminal == DER_PACK_LEAVE) && (crs->derlen != 0)) {
+               errno = EBADMSG;
+               return NULL;
+       }
+#endif
+       //
+       // Properly ended with DER_PACK_LEAVE, so report success
+DPRINTF ("DEBUG: Leaving  der_unpack_rec() at 0x%08llx\n", (intptr_t) walk);
+       return walk;
+}
+
+
+/* Unpack a structure, or possibly a sequence of structures.  The output
+ * is stored in subsequent entries of outarray, whose size should be
+ * precomputed to sufficient length.  The outarray will often be an
+ * overlay for a structure composed of dercursor elements with labels
+ * and nesting derived from ASN.1 syntax, and matching an (un)packing walk.
+ *
+ * The syntax is supplied without a length; proper formatting of the syntax
+ * is assumed, that is the number of DER_PACK_ENTER bits should be followed
+ * by an equal amount of DER_PACK_LEAVE instructions, and the choice
+ * markers DER_PACK_CHOICE_BEGIN ... DER_PACK_CHOICE_END must be properly
+ * nested with those instructions and with each other.  There is no
+ * protection for foolish specifications (and they will often be generated
+ * anyway).  This method additionally requires the first element in the
+ * syntax to be flagged with DER_PACK_ENTER.  (TODO: Permit non-looped use?)
+ *
+ * The cursor will be updated by this call to point to the position
+ * where unpacking stopped.  Refer to the return value to see if this is
+ * an error position.  The function returns 0 on success and -1 on failure.
+ * Upon failure, errno is also set, namely to EBADMSG for syntax problems
+ * or ERANGE for lengths or tags that are out of the supported range of
+ * this implementation.
+ */
+int der_unpack (dercursor *crs, const derwalk *syntax,
+                       dercursor *outarray, int repeats) {
+       int outctr = 0;
+       if ((*syntax & DER_PACK_ENTER) == 0x00) {
+               errno = EBADMSG;
+               return -1;
+       }
+       while (repeats-- > 0) {
+               if (der_unpack_rec (crs, syntax,
+                               outarray, &outctr,
+                               0, 0, 0) == NULL) {
+                       return -1;
+               }
+       }
+       return 0;
+}
diff --git a/lib/der_walk.c b/lib/der_walk.c
new file mode 100644 (file)
index 0000000..a7870d3
--- /dev/null
@@ -0,0 +1,139 @@
+#include <quick-der/api.h>
+
+
+/* Update a cursor expression by walking into a DER-encoded ASN.1 structure.
+ * The return value is -1 on error, and errno will be set accordinly, and the
+ * cursor will not have been updated.  Otherwise, the return value is the number
+ * of unprocessed bytes on the path, so 0 when the entire path was processed.
+ * The count as non-error returns, so the cursor is updated.  Values higher than
+ * 0 indicate where in the path a tag could not be found; this may be helpful in
+ * learning about the structure that was being parsed, for example that an
+ * OPTIONAL or CHOICE part was absent from the DER bytes.
+ *
+ * Paths are sequence of one-byte choices to be made.  These choices are tags,
+ * because these are used by ASN.1 to decide on parsing choices to be made.
+ * The one difference is the interpretation of the Primitive/Constructed bit: when
+ * this is set to Primitive, the value will be skipped (even if it is Constructed)
+ * and when set to Constructed, the value will be entered and interpreted as ASN.1
+ * (even when it is setup as Primitive).
+ *
+ * In all the places where ASN.1 defines choices, such as CHOICE or OPTIONAL,
+ * it enforces distinct tags from the various choices.  This can be used in a path
+ * to skip such unknown parts in the encoding.
+ *
+ * When entering a BIT STRING, special treatment is implemented; the remaining
+ * bits will have to be zero, and these are then skipped while entering the
+ * remainder.  Note that this ensures that the byte-aligned DER structures are
+ * properly packed into a bit-aligned BIT STRING container.
+ */
+int der_walk (dercursor *crs, const derwalk *path) {
+       size_t len;
+       uint8_t hlen;
+       uint8_t tag;
+       dercursor intcrs = *crs;
+       int retval;
+       int optional = 0;
+       int choice = 0;
+       while (*path != DER_WALK_END) {
+               // see if a prefix signals optionality
+               if (*path == DER_WALK_OPTIONAL) {
+                       optional = 1;   // For the duration of processing the following *path
+                       path++;
+                       if ((*path == DER_WALK_END) || (*path == DER_WALK_OPTIONAL)) {
+                               errno = EINVAL;
+                               return -1;
+                       }
+               }
+               // see if the current path element is a choice
+               // between unknown elements that should be
+               // skipped (except when it is optional, in which
+               // case a match should be attempted)
+               if (*path == DER_WALK_CHOICE) {
+                       choice = 1;
+                       // Special case: we advance beyond the path, so that we can check
+                       // the following element if the CHOICE is OPTIONAL
+                       path++;
+                       if ((*path == DER_WALK_END) || (*path == DER_WALK_CHOICE) || (*path == DER_WALK_OPTIONAL))  {
+                               errno = EINVAL;
+                               return -1;
+                       }
+               }
+               if (intcrs.derlen < 2) {
+                       if (intcrs.derlen == 0) {
+                               // Empty, so the path resolved only partially
+                               break;
+                       }
+                       // Something is wrong with the DER formatting
+                       errno = EBADMSG;
+                       return -1;
+               }
+               if (der_header (&intcrs, &tag, &len, &hlen)) {
+                       return -1;
+               }
+               // Now test if the tag matches that of the path;
+               // choice and optional flags are processed and
+               // make use of the ASN.1 guarantee that the
+               // first tag that hits us is decisive on how to
+               // proceed.  Although this may look loose, it is
+               // in fact very accurate parsing, albeit that the
+               // correctness is verified lazily, that is, only
+               // inasfar as it is needed for the requested path.
+               if (choice && !optional) {
+                       // this is just something unknown to skip;
+                       // that would even be true if it matched,
+                       // because the matching is deferred to
+                       // the next path element.
+                       intcrs.derptr += len;
+                       intcrs.derlen -= len;
+                       // the choice was matched; we already
+                       // skipped to next path item for choice==1
+               } else if (((tag ^ *path) & DER_WALK_MATCHBITS) == 0x00) {
+                       // matched: now either enter of skip
+                       if ((*path) & DER_WALK_ENTER) {
+                               if (tag == (DER_WALK_ENTER | DER_TAG_BITSTRING)) {
+                                       intcrs.derptr++;
+                                       len--;
+                               }
+                               intcrs.derlen = len;
+                       } else {
+                               // not DER_WALK_ENTER, so DER_WALK_SKIP
+                               intcrs.derptr += len;
+                               intcrs.derlen -= len;
+                       }
+                       // matched: skip to next path item
+                       // if we had choice==1 and optional==1,
+                       // then we matched the part after the CHOICE
+                       // and so this applies in that case too.
+                       path++;
+               } else if (optional) {
+                       // not matched the optional part: skip the data
+                       // and try the path element after the optional
+                       intcrs.derptr += len;
+                       intcrs.derlen -= len;
+                       // if the optional part was a choice, then we
+                       // are already at the next part and we are
+                       // now skipping the choice, for which we
+                       // already advanced path.  if the optional
+                       // part was not a choice, then path is at the
+                       // optional part, and we do need to skip that
+                       if (!choice) {
+                               path++;
+                       }
+               } else {
+                       // not matched and not optional:
+                       // this is a parsing error
+                       errno = EBADMSG;
+                       return -1;
+               }
+               // Forget any optional flags for the processed *path
+               optional = 0;
+               choice = 0;
+       }
+       // Return 0 when done, or >0 with the remaining pathlen otherwise
+       *crs = intcrs;
+       retval = 0;
+       while (path [retval] != DER_WALK_END) {
+               retval++;
+       }
+       return retval;
+}
diff --git a/lib/quick-der.c b/lib/quick-der.c
new file mode 100644 (file)
index 0000000..952e553
--- /dev/null
@@ -0,0 +1,15 @@
+// Combine all the .c parts into one file
+
+#include <quick-der/api.h>
+
+/* INLINE FUNCTION #include "der_isconstructed.c" */
+/* INLINE FUNCTION #include "der_isprimitive.c"   */
+/* INLINE FUNCTION #include "der_isnonempty.c"    */
+/* INLINE FUNCTION #include "der_isnull.c"        */
+#include "der_header.c"
+#include "der_walk.c"
+#include "der_skipenter.c"
+#include "der_unpack.c"
+#include "der_iterate.c"
+#include "der_pack.c"
+#include "der_prepack.c"
diff --git a/quick-der-logo.png b/quick-der-logo.png
new file mode 100644 (file)
index 0000000..65d0a9b
Binary files /dev/null and b/quick-der-logo.png differ
diff --git a/test/Makefile b/test/Makefile
new file mode 100644 (file)
index 0000000..7cb7d03
--- /dev/null
@@ -0,0 +1,34 @@
+TESTS = certio.ok # kxover.ok krb5ticket.ok
+
+LIBDIR = ../lib
+LIBQD = libquickder.a
+
+all: $(LIBDIR)/$(LIBQD) $(TESTS)
+
+clean:
+       for t in $(TESTS) ; do rm -f $$t $${t%.ok}.test ; done
+
+$(LIBDIR)/$(LIBQD):
+       make -C "$(LIBDIR)" "$(LIBQD)"
+
+install:
+       @echo #
+       @echo # The test directory constrains building, but does not install anything
+       @echo #
+
+uninstall:
+
+
+%.test: %.c $(LIBDIR)/$(LIBQD)
+       gcc -I ../include -o "$@" "$<" "$(LIBDIR)/$(LIBQD)"
+
+%.ok: %.test
+       "./$<" && touch "$@"
+
+certio.ok: certio.test
+       ./certio.test verisign.der
+       touch "$@"
+
+kxover.ok: kxover.test
+       ./kxover.test 
+
diff --git a/test/certio.c b/test/certio.c
new file mode 100644 (file)
index 0000000..8c5eb39
--- /dev/null
@@ -0,0 +1,419 @@
+/* Test the DER parser by exploring a certificate */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <quick-der/api.h>
+
+
+// Certificate  ::=  SEQUENCE  {
+//     tbsCertificate       TBSCertificate,
+//     signatureAlgorithm   AlgorithmIdentifier,
+//     signatureValue       BIT STRING  }
+// 
+// TBSCertificate  ::=  SEQUENCE  {
+//     version         [0]  EXPLICIT Version DEFAULT v1,
+//     serialNumber         CertificateSerialNumber,
+//     signature            AlgorithmIdentifier,
+//     issuer               Name,
+//     validity             Validity,
+//     subject              Name,
+//     subjectPublicKeyInfo SubjectPublicKeyInfo,
+//     issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
+//     -- If present, version MUST be v2 or v3
+//     subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
+//     -- If present, version MUST be v2 or v3
+//     extensions      [3]  EXPLICIT Extensions OPTIONAL
+//     -- If present, version MUST be v3
+// }
+//
+// Extensions  ::=  SEQUENCE SIZE (1..MAX) OF Extension
+//
+// Extension  ::=  SEQUENCE  {
+//      extnID      OBJECT IDENTIFIER,
+//      critical    BOOLEAN DEFAULT FALSE,
+//      extnValue   OCTET STRING
+//                  -- contains the DER encoding of an ASN.1 value
+//                  -- corresponding to the extension type identified
+//                  -- by extnID
+//      }
+
+
+
+
+/* The syntax parser, as it should be auto-generated one day.
+ *
+ * The parser enters as much of the structure as it can, basically taking
+ * away anything that contains DER structure.  This stops ANY or ANY DEFINED BY
+ * is used to describe a field's contents in a variable manner; you would have
+ * to continue parsing yourself.  Similarly, when an OCTET STRING or BIT STRING
+ * has DER-formatted contents that might vary.  When there is no variation,
+ * you might actually ENTER these structures; note that special processing for
+ * the BIT STRING then demands 0 remainder bits, after which it will enter one;
+ * position after the head to skip the remainder bit count.
+ */
+
+derwalk pack_Certificate[] = {
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // Certificate SEQUENCE
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // TBSCertificate SEQUENCE
+       DER_PACK_OPTIONAL,                              // version is optional
+       DER_PACK_ENTER | DER_TAG_CONTEXT (0),           // [0] EXPLICIT
+       DER_PACK_STORE | DER_TAG_INTEGER,               // version
+       DER_PACK_LEAVE,                                 // [0] EXPLICIT
+       DER_PACK_STORE | DER_TAG_INTEGER,               // serialNumber
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // signature AlgIdentif
+       DER_PACK_STORE | DER_TAG_OID,                   // algorithm OID
+       DER_PACK_ANY,                                   // parameters ANY DEFINED BY
+       DER_PACK_LEAVE,                                 // signature AlgIdentif
+       DER_PACK_STORE | DER_TAG_SEQUENCE,              // issuer Name (varsized)
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // validity SEQUENCE
+       DER_PACK_CHOICE_BEGIN,                          // utctime | genlztime
+       DER_PACK_STORE | DER_TAG_UTCTIME,               // alt :- UTCtime
+       DER_PACK_STORE | DER_TAG_GENERALIZEDTIME,       // alt :- GeneralizedTime
+       DER_PACK_CHOICE_END,                            // validity Validity
+       DER_PACK_CHOICE_BEGIN,                          // utctime | genlztime
+       DER_PACK_STORE | DER_TAG_UTCTIME,               // alt :- UTCtime
+       DER_PACK_STORE | DER_TAG_GENERALIZEDTIME,       // alt :- GeneralizedTime
+       DER_PACK_CHOICE_END,                            // Validity
+       DER_PACK_LEAVE,                                 // validity SEQUENCE
+       DER_PACK_STORE | DER_TAG_SEQUENCE,              // subject Name (varsized)
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // subjectPublicKeyInfo
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // algorithmIdentifier
+       DER_PACK_STORE | DER_TAG_OID,                   // algorithm
+       DER_PACK_ANY,                                   // parameters ANY DEFINED BY
+       DER_PACK_LEAVE,                                 // algorithmIdentifier
+       DER_PACK_STORE | DER_TAG_BITSTRING,             // subjectPublicKey
+       DER_PACK_LEAVE,                                 // subjectPublicKeyInfo
+       DER_PACK_OPTIONAL,                              // issuerUniqueID [1]
+       DER_PACK_STORE | DER_TAG_CONTEXT (1),           // [1] IMPLICIT BITSTRING
+       DER_PACK_OPTIONAL,                              // subjectUniqueID [2]
+       DER_PACK_STORE | DER_TAG_CONTEXT (2),           // [2] IMPLICIT BITSTRING
+       DER_PACK_OPTIONAL,                              // extensions [3]
+       DER_PACK_ENTER | DER_TAG_CONTEXT (3),           // [3] EXPLICIT
+       DER_PACK_STORE | DER_TAG_SEQUENCE,              // SEQUENCE OF Extension
+       DER_PACK_LEAVE,                                 // [3] EXPLICIT
+       DER_PACK_LEAVE,                                 // TBSCertificate SEQUENCE
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // sigAlg AlgIdentifier
+       DER_PACK_STORE | DER_TAG_OID,                   // algorithm
+       DER_PACK_ANY,                                   // parameters ANY DEFINED BY
+       DER_PACK_LEAVE,                                 // sigAlg AlgIdentifier
+       DER_PACK_STORE | DER_TAG_BITSTRING,             // signatureValue BITSTR
+       DER_PACK_LEAVE,                                 // Certificate SEQUENCE
+       DER_PACK_END                                    // stop after SEQUENCE
+};
+
+derwalk pack_Extension [] = {
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // Extension ::= SEQUENCE {
+       DER_PACK_STORE | DER_TAG_OID,                   // extnID
+       DER_PACK_OPTIONAL,
+       DER_PACK_STORE | DER_TAG_BOOLEAN,               // critical
+       DER_PACK_STORE | DER_TAG_OCTETSTRING,           // extnValue
+       DER_PACK_LEAVE,                                 // }
+       DER_PACK_END
+};
+
+/* Overlay structures, as they should be auto-generated some day */
+struct ovly_Time {
+       dercursor utcTime;
+       dercursor generalTime;
+};
+
+struct ovly_Validity {
+       struct ovly_Time notBefore;
+       struct ovly_Time notAfter;
+};
+
+struct ovly_AlgorithmIdentifier {
+       dercursor algorithm;
+       dercursor parameters;
+};
+
+struct ovly_SubjectPublicKeyInfo {
+       struct ovly_AlgorithmIdentifier algorithm;
+       dercursor subjectPublicKey;
+};
+
+struct ovly_TBSCertificate {
+       dercursor version;
+       dercursor serialNumber;
+       struct ovly_AlgorithmIdentifier signature;
+       dercursor issuer;
+       struct ovly_Validity validity;
+       dercursor subject;
+       struct ovly_SubjectPublicKeyInfo subjectPublicKeyInfo;
+       dercursor issuerUniqueID;
+       dercursor subjectUniqueID;
+       dercursor extensions;
+};
+
+struct ovly_Certificate {
+       struct ovly_TBSCertificate tbsCertificate;
+       struct ovly_AlgorithmIdentifier signatureAlgorithm;
+       dercursor signatureValue;
+};
+
+struct ovly_Extension {
+       dercursor extnID;
+       dercursor critical;
+       dercursor extnValue;
+};
+
+derwalk path_rdn2type[] = {
+       DER_WALK_ENTER | DER_TAG_SET,           // SET OF AttributeTypeAndValue
+       DER_WALK_ENTER | DER_TAG_SEQUENCE,      // SEQUENCE { type, value }
+       DER_WALK_ENTER | DER_TAG_OID,           // type OBJECT IDENTIFIER
+       DER_WALK_END
+};
+
+derwalk path_rdn2value[] = {
+       DER_WALK_ENTER | DER_TAG_SET,           // SET OF AttributeTypeAndValue
+       DER_WALK_ENTER | DER_TAG_SEQUENCE,      // SEQUENCE { type, value }
+       DER_WALK_SKIP  | DER_TAG_OID,           // type OBJECT IDENTIFIER
+                                               // value ANY DEFINED BY type
+       DER_WALK_END
+};
+
+void print_oid (dercursor *oid) {
+       size_t oidlen = oid->derlen;
+       uint8_t *oidptr = oid->derptr;
+       uint32_t nextoid = 0;
+       if (oidlen < 1) {
+               printf ("BAD_OID");
+               return;
+       } else {
+               printf ("%d.%d", (*oidptr) / 40, (*oidptr) % 40);
+       }
+       oidlen--;
+       oidptr++;
+       while (oidlen > 0) {
+               nextoid <<= 7;
+               nextoid |= (*oidptr) & 0x7f;
+               if ((*oidptr & 0x80) == 0x00) {
+                       printf (".%d", nextoid);
+                       nextoid = 0;
+               }
+               oidptr++;
+               oidlen--;
+       }
+       if (nextoid != 0x00) {
+               printf (".LEFTOVER_%d", nextoid);
+       }
+}
+
+void hexdump (dercursor *crs) {
+       dercursor here = *crs;
+       while (here.derlen-- > 0) {
+               printf (" %02x", *here.derptr++);
+       }
+}
+
+int main (int argc, char *argv []) {
+       int inf, otf;
+       uint8_t buf [65537];
+       size_t buflen;
+       dercursor crs;
+       dercursor iter;
+       dercursor rdn;
+       dercursor ext;
+       struct ovly_Certificate certificate;
+       struct ovly_Extension extension;
+       int prsok;
+       size_t rebuildlen;
+       int i;
+
+       memset (&certificate, 0x5A, sizeof (certificate));
+       memset (&extension, 0x5A, sizeof (extension));
+       if ((argc < 2) || (argc > 3)) {
+               printf ("Usage: %s certfile.der [rebuildfile.der]\n", argv [0]);
+               exit (1);
+       }
+       inf = open (argv [1], O_RDONLY);
+       if (inf < 0) {
+               fprintf (stderr, "Failed to open %s\n", argv [1]);
+               close (inf);
+               exit (1);
+       }
+       buflen = read (inf, buf, sizeof (buf));
+       close (inf);
+       if ((buflen == -1) || (buflen == 0)) {
+               fprintf (stderr, "Failed to read from %s\n", argv [1]);
+               exit (1);
+       }
+       if (buflen == sizeof (buf)) {
+               fprintf (stderr, "Certificate in %s too large\n", argv [1]);
+               exit (1);
+       }
+       printf ("Parsing %zu bytes from %s\n", buflen, argv [1]);
+       crs.derptr = buf;
+       crs.derlen = buflen;
+       prsok = der_unpack (&crs, pack_Certificate, (dercursor *) &certificate, 1);
+       switch (prsok) {
+       case -1:
+               perror ("Failed to unpack certificate");
+               exit (1);
+       case 0:
+               // printf ("Parsing OK, found %zu bytes worth of subject data at 0x%016llx\n", crs.derlen, (uint64_t) crs.derptr);
+               printf ("Detailed parsing OK for this Certificate\n");
+               break;
+       }
+
+       if (der_isnull (&certificate.tbsCertificate.version)) {
+               printf ("No version set (defaults to v1)\n");
+       } else {
+               printf ("Version is set to v%d\n", 1 + *certificate.tbsCertificate.version.derptr);
+       }
+
+       printf ("Serial number: ");
+       hexdump (&certificate.tbsCertificate.serialNumber);
+       printf ("\n");
+
+       crs = certificate.tbsCertificate.issuer;
+       printf ("There are %d RDNs in the issuer:\n", der_countelements (&crs));
+       if (der_iterate_first (&crs, &iter)) do {
+               // printf ("Iterator now at 0x%016llx spanning %zu\n", (uint64_t) iter.derptr, iter.derlen);
+               // printf ("Iterator tag,len is 0x%02x,0x%02x\n", iter.derptr [0], iter.derptr [1]);
+               rdn = iter;
+               der_walk (&rdn, path_rdn2type);
+               // printf ("RDNcursor #1 tag,len is 0x%02x,0x%02x,0x%02x\n", rdn.derptr [0], rdn.derptr [1],rdn.derptr [2]);
+               print_oid (&rdn);
+               rdn = iter;
+               der_walk (&rdn, path_rdn2value);
+               der_enter (&rdn); // Enter whatever DirectoryString it is
+               // printf ("RDNcursor #2 tag,len is 0x%02x,0x%02x", rdn.derptr [0], rdn.derptr [1]);
+               printf (" = \"%.*s\"\n", rdn.derlen, rdn.derptr);
+       } while (der_iterate_next (&iter));
+
+       crs = der_isnull (&certificate.tbsCertificate.validity.notBefore.utcTime)?
+               certificate.tbsCertificate.validity.notBefore.generalTime:
+               certificate.tbsCertificate.validity.notBefore.utcTime;
+       printf ("Validity.notBefore: %.*s\n", crs.derlen, crs.derptr);
+       crs = der_isnull (&certificate.tbsCertificate.validity.notAfter.utcTime)?
+               certificate.tbsCertificate.validity.notAfter.generalTime:
+               certificate.tbsCertificate.validity.notAfter.utcTime;
+       printf ("Validity.notAfter:  %.*s\n", crs.derlen, crs.derptr);
+
+       crs = certificate.tbsCertificate.subject;
+       printf ("There are %d RDNs in the subject:\n", der_countelements (&crs));
+       if (der_iterate_first (&crs, &iter)) do {
+               // printf ("Iterator now at 0x%016llx spanning %zu\n", (uint64_t) iter.derptr, iter.derlen);
+               // printf ("Iterator tag,len is 0x%02x,0x%02x\n", iter.derptr [0], iter.derptr [1]);
+               rdn = iter;
+               der_walk (&rdn, path_rdn2type);
+               // printf ("RDNcursor #1 tag,len is 0x%02x,0x%02x,0x%02x\n", rdn.derptr [0], rdn.derptr [1],rdn.derptr [2]);
+               print_oid (&rdn);
+               rdn = iter;
+               der_walk (&rdn, path_rdn2value);
+               der_enter (&rdn); // Enter whatever DirectoryString it is
+               // printf ("RDNcursor #2 tag,len is 0x%02x,0x%02x", rdn.derptr [0], rdn.derptr [1]);
+               printf (" = \"%.*s\"\n", rdn.derlen, rdn.derptr);
+       } while (der_iterate_next (&iter));
+
+       printf ("Subject Public Key AlgorithmIdentifier: ");
+       crs = certificate.tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm;
+       print_oid (&crs);
+       printf ("\n                                       ");
+       crs = certificate.tbsCertificate.subjectPublicKeyInfo.algorithm.parameters;
+       hexdump (&crs);
+       printf ("\n                                       ");
+       crs = certificate.tbsCertificate.subjectPublicKeyInfo.subjectPublicKey;
+       der_enter (&crs);
+       hexdump (&crs);
+       printf ("\n");
+
+       crs = certificate.tbsCertificate.issuerUniqueID;
+       if (!der_isnull (&crs)) {
+               printf ("Issuer Unique ID:");
+               hexdump (&crs);
+               printf ("\n");
+       }
+
+       crs = certificate.tbsCertificate.subjectUniqueID;
+       if (!der_isnull (&crs)) {
+               printf ("Subject Unique ID:");
+               hexdump (&crs);
+               printf ("\n");
+       }
+
+       crs = certificate.tbsCertificate.extensions;
+       printf ("There are %d extensions:\n", der_countelements (&crs));
+       if (der_iterate_first (&crs, &iter)) do {
+               // printf ("Iterator now at 0x%016llx spanning %zu\n", (uint64_t) iter.derptr, iter.derlen);
+               // printf ("Iterator tag,len is 0x%02x,0x%02x\n", iter.derptr [0], iter.derptr [1]);
+               ext = iter;
+printf ("Extension size %zd bytes %02x %02x %02x %02x\n", ext.derlen, ext.derptr[0], ext.derptr[1], ext.derptr[2], ext.derptr[3]);
+               prsok = der_unpack (&ext, pack_Extension, (dercursor *) &extension, 1);
+               if (prsok != 0) {
+                       fprintf (stderr, "Failed to parse extension (%d)\n", prsok);
+                       continue;
+               }
+               printf ("Extension OID: ");
+               print_oid (&extension.extnID);
+               printf ("\nExtension critical: ");
+               if (der_isnull (&extension.critical)) {
+                       printf ("FALSE (DEFAULT)\n");
+               //TODO// Would be nice to have a DER boolean test macro / function
+               } else if ((extension.critical.derlen > 0) && (*extension.critical.derptr)) {
+                       printf ("TRUE\n");
+               } else {
+                       printf ("FALSE\n");
+               }
+               printf ("Extension contents:");
+               hexdump (&extension.extnValue);
+               printf ("\n");
+       } while (der_iterate_next (&iter));
+
+       //
+       // Print the elemental length of the 16 certificate elements
+       for (i=0; i<16; i++) {
+               printf ("certificate [%2d].derlen = %zd\n", i, ((dercursor *) &certificate) [i].derlen);
+       }
+
+       //
+       // Determine the length for re-composition
+       rebuildlen = der_pack (pack_Certificate, (dercursor *) &certificate, NULL);
+       if (rebuildlen != DER_DERLEN_ERROR) {
+               printf ("To rebuild, we would need %zd bytes\n", rebuildlen);
+       } else {
+               fprintf (stderr, "Unable to determine the rebuild size for this certificate\n");
+               exit (1);
+       }
+
+       //
+       // Recompose the certificate for an output file if argv [2] has been defined
+       if (argc == 3) {
+               //
+               // Construct the output certificate in a new buffer
+               uint8_t rebuildbuf [rebuildlen]; // Daring: dynamic stack allocation
+               der_pack (pack_Certificate, (dercursor *) &certificate, rebuildbuf + rebuildlen);
+               printf ("TOTAL: Wrote %4d bytes to 0x%016llx\n", rebuildlen, rebuildbuf);
+               //
+               // Save the rebuilt certificate to the named file
+               otf = open (argv [2], O_RDWR | O_CREAT | O_TRUNC, 0644);
+               if (otf < 0) {
+                       fprintf (stderr, "Failed to create output file %s\n", argv [2]);
+                       exit (1);
+               }
+               if (write (otf, rebuildbuf, rebuildlen) != rebuildlen) {
+                       fprintf (stderr, "Not all of the %d bytes have been written to %s\n", rebuildlen, argv [2]);
+                       close (otf);
+                       exit (1);
+               }
+               close (otf);
+               //
+               // Compare the new certificate to the original one
+               if (rebuildlen != buflen) {
+                       fprintf (stderr, "The rebuilt certificate is of a different size than the input\n");
+                       exit (1);
+               }
+               if (memcmp (buf, rebuildbuf, buflen) != 0) {
+                       fprintf (stderr, "The rebuilt certificate differs from the one input\n");
+                       exit (1);
+               }
+       }
+
+       return 0;
+}
+
diff --git a/test/krb5ticket.c b/test/krb5ticket.c
new file mode 100644 (file)
index 0000000..901248f
--- /dev/null
@@ -0,0 +1,205 @@
+
+#include <stdlib.h>
+
+
+#include <quick-der/api.h>
+
+
+
+/*
+ * Ticket          ::= [APPLICATION 1] SEQUENCE {
+ *         tkt-vno         [0] INTEGER (5),
+ *         realm           [1] Realm,
+ *         sname           [2] PrincipalName,
+ *         enc-part        [3] EncryptedData -- EncTicketPart
+ * }
+ *
+ * Realm           ::= KerberosString
+ *
+ * PrincipalName   ::= SEQUENCE {
+ *         name-type       [0] Int32,
+ *         name-string     [1] SEQUENCE OF KerberosString
+ * }
+ *
+ * EncryptedData   ::= SEQUENCE {
+ *         etype   [0] Int32 -- EncryptionType --,
+ *         kvno    [1] UInt32 OPTIONAL,
+ *         cipher  [2] OCTET STRING -- ciphertext
+ * }
+ *
+ * EncTicketPart   ::= [APPLICATION 3] SEQUENCE {
+ *         flags                   [0] TicketFlags,
+ *         key                     [1] EncryptionKey,
+ *         crealm                  [2] Realm,
+ *         cname                   [3] PrincipalName,
+ *         transited               [4] TransitedEncoding,
+ *         authtime                [5] KerberosTime,
+ *         starttime               [6] KerberosTime OPTIONAL,
+ *         endtime                 [7] KerberosTime,
+ *         renew-till              [8] KerberosTime OPTIONAL,
+ *         caddr                   [9] HostAddresses OPTIONAL,
+ *         authorization-data      [10] AuthorizationData OPTIONAL
+ * }
+ *
+ * TicketFlags     ::= KerberosFlags
+ *
+ * EncryptionKey   ::= SEQUENCE {
+ *         keytype         [0] Int32 -- actually encryption type --,
+ *         keyvalue        [1] OCTET STRING
+ * }
+ *
+ * TransitedEncoding       ::= SEQUENCE {
+ *         tr-type         [0] Int32 -- must be registered --,
+ *         contents        [1] OCTET STRING
+ * }
+ *
+ * KerberosTime    ::= GeneralizedTime -- with no fractional seconds
+ *
+ * AuthorizationData       ::= SEQUENCE OF SEQUENCE {
+ *         ad-type         [0] Int32,
+ *         ad-data         [1] OCTET STRING
+ * }
+ *
+ */
+
+derwalk pack_Ticket [] = {
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // Ticket
+       DER_PACK_ENTER | DER_TAG_CONTEXT (0),           // [0] tkt-vno
+       DER_PACK_STORE | DER_TAG_INTEGER,               // tkt-vno (5)
+       DER_PACK_LEAVE,                                 // [0] tkt-vno
+       DER_PACK_ENTER | DER_TAG_CONTEXT (1),           // [1] realm
+       DER_PACK_STORE | DER_TAG_GENERALSTRING,         // realm
+       DER_PACK_LEAVE,                                 // [1] realm
+       DER_PACK_ENTER | DER_TAG_CONTEXT (2),           // [2] sname
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // sname
+       DER_PACK_ENTER | DER_TAG_CONTEXT (0),           // [0] name-type
+       DER_PACK_STORE | DER_TAG_INTEGER,               // name-type Int32
+       DER_PACK_STORE | DER_TAG_SEQUENCE,              // SEQUENCE OF GeneralString
+       DER_PACK_LEAVE,                                 // [0] name-type
+       DER_PACK_LEAVE,                                 // sname
+       DER_PACK_LEAVE,                                 // [2] sname
+       DER_PACK_ENTER | DER_TAG_CONTEXT (3),           // [3] enc-part
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // enc-part
+       DER_PACK_ENTER | DER_TAG_CONTEXT (0),           // [0] etype
+       DER_PACK_STORE | DER_TAG_INTEGER,               // etype Int32
+       DER_PACK_LEAVE,                                 // [0] etype
+       DER_PACK_OPTIONAL,                              // kvno OPTIONAL
+       DER_PACK_ENTER | DER_TAG_CONTEXT (1),           // [1] kvno
+       DER_PACK_STORE | DER_TAG_INTEGER,               // kvno Uint32
+       DER_PACK_LEAVE,                                 // [1] kvno
+       DER_PACK_ENTER | DER_TAG_CONTEXT (2),           // [2] cipher
+       DER_PACK_STORE | DER_TAG_OCTETSTRING,           // cipher [2] OCTETSTRING
+       DER_PACK_LEAVE,                                 // [2] cipher
+       DER_PACK_LEAVE,                                 // enc-part
+       DER_PACK_LEAVE,                                 // [3] enc-part
+       DER_PACK_LEAVE,                                 // Ticket
+       DER_PACK_END
+};
+
+
+derwalk pack_EncTicketPart [] = {
+       DER_PACK_ENTER | DER_TAG_APPLICATION (3),       // EncTicketPart [APPL 3]
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // [APPL 3] SEQUENCE {
+       DER_PACK_ENTER | DER_TAG_CONTEXT (0),           // [0] TicketFlags
+       DER_PACK_STORE | DER_TAG_INTEGER,               // TicketFlags
+       DER_PACK_LEAVE,                                 // [0] TicketFlags
+       DER_PACK_ENTER | DER_TAG_CONTEXT (1),           // [1] EncryptionKey
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // SEQUENCE {
+       DER_PACK_STORE | DER_TAG_INTEGER,               // keytype
+       DER_PACK_STORE | DER_TAG_OCTETSTRING,           // keyvalue
+       DER_PACK_LEAVE,                                 // SEQUENCE }
+       DER_PACK_LEAVE,                                 // [1] EncryptionKey
+       DER_PACK_ENTER | DER_TAG_CONTEXT (2),           // [2] Realm
+       DER_PACK_STORE | DER_TAG_GENERALSTRING,         // Realm ::= GeneralString
+       DER_PACK_LEAVE,                                 // [2] Realm
+       DER_PACK_ENTER | DER_TAG_CONTEXT (3),           // [3] PrincipalName
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // PrincipalName SEQUENCE {
+       DER_PACK_STORE | DER_TAG_INTEGER,               // name-type
+       DER_PACK_STORE | DER_TAG_SEQUENCE,              // SEQUENCE OF GeneralString
+       DER_PACK_LEAVE,                                 // SEQUENCE } PrincipalName
+       DER_PACK_LEAVE,                                 // [3] PrincipalName
+       DER_PACK_ENTER | DER_TAG_CONTEXT (4),           // [4] TransitedEncoding
+       DER_PACK_ENTER | DER_TAG_SEQUENCE,              // TransEnc ::= SEQUENCE {
+       DER_PACK_ENTER | DER_TAG_CONTEXT (0),           // [0] tr-type
+       DER_PACK_STORE | DER_TAG_INTEGER,               // tr-type
+       DER_PACK_LEAVE,                                 // [0] tr-type
+       DER_PACK_ENTER | DER_TAG_CONTEXT (1),           // [1] contents
+       DER_PACK_STORE | DER_TAG_OCTETSTRING,           // contents
+       DER_PACK_LEAVE,                                 // [1] contents
+       DER_PACK_LEAVE,                                 // SEQUENCE } TrancEnc
+       DER_PACK_LEAVE,                                 // [4] TransitedEncoding
+       DER_PACK_ENTER | DER_TAG_CONTEXT (5),           // [5] authtime
+       DER_PACK_STORE | DER_TAG_GENERALIZEDTIME,       // KerberosTime
+       DER_PACK_LEAVE,                                 // [5] authtime
+       DER_PACK_OPTIONAL,                              // [6] starttime OPTIONAL
+       DER_PACK_ENTER | DER_TAG_CONTEXT (6),           // [6] starttime
+       DER_PACK_STORE | DER_TAG_GENERALIZEDTIME,       // KerberosTime
+       DER_PACK_LEAVE,                                 // [6] starttime
+       DER_PACK_ENTER | DER_TAG_CONTEXT (7),           // [7] endtime
+       DER_PACK_STORE | DER_TAG_GENERALIZEDTIME,       // KerberosTime
+       DER_PACK_LEAVE,                                 // [7] endtime
+       DER_PACK_OPTIONAL,                              // [8] renew-till OPTIONAL
+       DER_PACK_ENTER | DER_TAG_CONTEXT (8),           // [8] renew-till
+       DER_PACK_STORE | DER_TAG_GENERALIZEDTIME,       // KerberosTime
+       DER_PACK_LEAVE,                                 // [8] renew-till
+       DER_PACK_OPTIONAL,                              // [9] caddr OPTIONAL
+       DER_PACK_ENTER | DER_TAG_CONTEXT (9),           // [9] caddr
+       DER_PACK_STORE | DER_TAG_SEQUENCE,              // SEQUENCE OF HostAddress
+       DER_PACK_LEAVE,                                 // [9] caddr
+       DER_PACK_OPTIONAL,                              // [10] authz-data OPTIONAL
+       DER_PACK_ENTER | DER_TAG_CONTEXT (10),          // [10] authz-data
+       DER_PACK_STORE | DER_TAG_SEQUENCE,              // SEQUENCE OF SEQUENCE...
+       DER_PACK_LEAVE,                                 // [10] authz-data
+       DER_PACK_LEAVE,                                 // SEQUENCE }
+       DER_PACK_LEAVE,                                 // EncTicketPart [APPL 3]
+       DER_PACK_END
+};
+
+
+struct ovly_PrincipalName {
+       dercursor name_type;
+       dercursor name_string;
+};
+
+struct ovly_EncryptedData {
+       dercursor etype;
+       dercursor kvno;
+       dercursor cipher;
+};
+
+struct ovly_Ticket {
+       dercursor tkt_vno;
+       dercursor realm;
+       struct ovly_PrincipalName sname;
+       struct ovly_EncryptedData enc_part;
+};
+
+
+struct ovly_EncryptionKey {
+       dercursor keytype;
+       dercursor keyvalue;
+};
+
+struct ovly_TransitedEncoding {
+       dercursor tr_type;
+       dercursor contents;
+};
+
+struct ovly_EncTicketPart {
+       dercursor flags;
+       struct ovly_EncryptionKey key;
+       dercursor crealm;
+       struct ovly_TransitedEncoding transited;
+       dercursor authtime;
+       dercursor starttime;
+       dercursor endtime;
+       dercursor renew_till;
+       dercursor caddr;
+       dercursor authorization_data;
+};
+
+
+int main (int argc, char *argv []) {
+       exit (1);       // To be implemented
+}
+
diff --git a/test/kxover.c b/test/kxover.c
new file mode 100644 (file)
index 0000000..c889284
--- /dev/null
@@ -0,0 +1,179 @@
+/* Test the DER parser by finding the certificates in PKINIT or KXOVER */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <quick-der/api.h>
+
+
+/********** RFC 4556:
+
+       PA-PK-AS-REQ ::= SEQUENCE {
+          signedAuthPack          [0] IMPLICIT OCTET STRING,
+                   -- Contains a CMS type ContentInfo encoded
+                   -- according to [RFC3852].
+                   -- The contentType field of the type ContentInfo
+                   -- is id-signedData (1.2.840.113549.1.7.2),
+                   -- and the content field is a SignedData.
+                   -- The eContentType field for the type SignedData is
+                   -- id-pkinit-authData (1.3.6.1.5.2.3.1), and the
+                   -- eContent field contains the DER encoding of the
+                   -- type AuthPack.
+                   -- AuthPack is defined below.
+          trustedCertifiers       [1] SEQUENCE OF
+                      ExternalPrincipalIdentifier OPTIONAL,
+                   -- Contains a list of CAs, trusted by the client,
+                   -- that can be used to certify the KDC.
+                   -- Each ExternalPrincipalIdentifier identifies a CA
+                   -- or a CA certificate (thereby its public key).
+                   -- The information contained in the
+                   -- trustedCertifiers SHOULD be used by the KDC as
+                   -- hints to guide its selection of an appropriate
+                   -- certificate chain to return to the client.
+          kdcPkId                 [2] IMPLICIT OCTET STRING
+                                      OPTIONAL,
+                   -- Contains a CMS type SignerIdentifier encoded
+                   -- according to [RFC3852].
+                   -- Identifies, if present, a particular KDC
+                   -- public key that the client already has.
+          ...
+       }
+
+*/
+
+
+/********** RFC 3852:
+
+      ContentInfo ::= SEQUENCE {
+        contentType ContentType,
+        content [0] EXPLICIT ANY DEFINED BY contentType }
+
+      ContentType ::= OBJECT IDENTIFIER
+
+      id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2)
+         us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 }
+
+      SignedData ::= SEQUENCE {
+        version CMSVersion,
+        digestAlgorithms DigestAlgorithmIdentifiers,
+        encapContentInfo EncapsulatedContentInfo,
+        certificates [0] IMPLICIT CertificateSet OPTIONAL,
+        crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
+        signerInfos SignerInfos }
+
+      CertificateSet ::= SET OF CertificateChoices
+
+      CertificateChoices ::= CHOICE {
+       certificate Certificate,
+       extendedCertificate [0] IMPLICIT ExtendedCertificate, -- Obsolete
+       v1AttrCert [1] IMPLICIT AttributeCertificateV1,       -- Obsolete
+       v2AttrCert [2] IMPLICIT AttributeCertificateV2,
+       other [3] IMPLICIT OtherCertificateFormat }
+
+      OtherCertificateFormat ::= SEQUENCE {
+       otherCertFormat OBJECT IDENTIFIER,
+       otherCert ANY DEFINED BY otherCertFormat }
+
+*/
+
+
+
+derwalk path_KXoverASreq2certificateChoices [] = {
+       DER_WALK_ENTER | DER_TAG_SEQUENCE,      // PA-PK-AS-REQ ::= SEQUENCE {...}
+       DER_WALK_ENTER | DER_TAG_CONTEXT (0),   // signedAuthPack [0] IMPLICIT
+       DER_WALK_ENTER | DER_TAG_SEQUENCE,      // ContentInfo ::= SEQUENCE {...}
+       DER_WALK_SKIP  | DER_TAG_OID,           // contentType OBJECT IDENTIFIER
+       DER_WALK_ENTER | DER_TAG_CONTEXT (0),   // content [0] EXPLICIT ANY ...
+       DER_WALK_ENTER | DER_TAG_SEQUENCE,      // SignedData ::= SEQUENCE {...}
+       DER_WALK_SKIP  | DER_TAG_INTEGER,       // version CMSVersion
+       DER_WALK_SKIP  | DER_TAG_SET,           // digestAlgorithms SET OF ...
+       DER_WALK_SKIP  | DER_TAG_SEQUENCE,      // encapContentInfo SEQUENCE {...}
+       DER_WALK_END                            // certificates [0] IMPLICIT
+                                               //   SET OF CertificateChoices (!)
+};
+
+int main (int argc, char *argv []) {
+       int inf;
+       uint8_t buf [65537];
+       size_t buflen;
+       dercursor crs;
+       dercursor iter;
+       int prsok;
+       int outfn = 2;
+       if (argc < 2) {
+               fprintf (stderr, "Usage: %s kxover-as-req.der [outcert0.der outcert1.der ...]\n", argv [0]);
+               exit (1);
+       }
+       inf = open (argv [1], O_RDONLY);
+       if (inf < 0) {
+               fprintf (stderr, "Failed to open %s\n", argv [1]);
+               exit (1);
+       }
+       buflen = read (inf, buf, sizeof (buf));
+       close (inf);
+       if ((buflen == -1) || (buflen == 0)) {
+               fprintf (stderr, "Failed to read from %s\n", argv [1]);
+               exit (1);
+       }
+       if (buflen == sizeof (buf)) {
+               fprintf (stderr, "Certificate in %s too large\n", argv [1]);
+               exit (1);
+       }
+       printf ("Parsing %zu bytes from %s\n", buflen, argv [1]);
+       crs.derptr = buf;
+       crs.derlen = buflen;
+       prsok = der_walk (&crs, path_KXoverASreq2certificateChoices);
+       switch (prsok) {
+       case -1:
+               perror ("Failed to find certificate set in KXOVER AS-Request");
+               exit (1);
+       case 0:
+               printf ("Parsing OK, found %zu bytes worth of certificate set data at 0x%016llx\n", crs.derlen, (uint64_t) crs.derptr);
+               break;
+       default:
+               printf ("Parsing ended with %d bytes left in pattern\n", prsok);
+               exit (1);
+       }
+       printf ("Cursor is now at 0x%016llx spanning %zu\n", (uint64_t) crs.derptr, crs.derlen);
+       if (der_iterate_first (&crs, &iter)) do {
+               printf ("Iterator now at 0x%016llx spanning %zu\n", (uint64_t) iter.derptr, iter.derlen);
+               printf ("Iterator tag,len is 0x%02x,0x%02x\n", iter.derptr [0], iter.derptr [1]);
+               switch (iter.derptr [0] & 0xdf) {
+               case DER_TAG_SEQUENCE:
+                       printf ("This is a certificate\n");
+                       if (outfn < argc) {
+                               int fout = open (argv [outfn], O_WRONLY);
+                               if (fout < 0) {
+                                       fprintf (stderr, "Failed to open %s for writing, skipping it\n", argv [outfn]);
+                               } else if (write (fout, iter.derptr, iter.derlen) != iter.derlen) {
+                                       fprintf (stderr, "Tried to save to %s, but not all bytes may have arrived\n", argv [outfn]);
+                                       close (fout);
+                               } else {
+                                       close (fout);
+                                       printf ("Wrote this certificate to %s\n", argv [outfn]);
+                               }
+                               outfn++;
+                       } else {
+                               printf ("Provide an extra filename if you want me to save the certificate's DER format\n");
+                       }
+                       break;
+               case DER_TAG_CONTEXT (0):
+                       printf ("This is an extendedCertificate (OBSOLETE)\n");
+                       break;
+               case DER_TAG_CONTEXT (1):
+                       printf ("This is a v1AttrCert (OBSOLETE)\n");
+                       break;
+               case DER_TAG_CONTEXT (2):
+                       printf ("This is a v2AttrCert\n");
+                       break;
+               case DER_TAG_CONTEXT (3):
+                       printf ("This follows an OID-specified OtherCertificateFormat\n");
+                       break;
+               }
+       } while (der_iterate_next (&iter));
+       return 0;
+}
+
diff --git a/test/verisign.der b/test/verisign.der
new file mode 100644 (file)
index 0000000..55923a0
Binary files /dev/null and b/test/verisign.der differ
diff --git a/tool/Makefile b/tool/Makefile
new file mode 100644 (file)
index 0000000..6fdfff9
--- /dev/null
@@ -0,0 +1,12 @@
+PREFIX=/usr/local
+
+all:
+
+clean:
+
+install:
+       install -m 0755 hexio/derdump.py "$(PREFIX)/bin/derdump"
+
+uninstall:
+       rm -f "$(PREFIX)/bin/derdump"
+
diff --git a/tool/hexio b/tool/hexio
new file mode 160000 (submodule)
index 0000000..dcc5f9c
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit dcc5f9ca71bde24fd8ad7a47ea86f8bd221b7103