/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* egg-asn1x.c - ASN.1/DER parse and coding routines
Copyright (C) 2009 Stefan Walter
The Gnome Keyring Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
The Gnome Keyring Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with the Gnome Library; see the file COPYING.LIB. If not,
.
Author: Stef Walter
*/
/*
* Some portions are:
*
* Copyright (C) 2004, 2006, 2008, 2009 Free Software Foundation
* Copyright (C) 2002 Fabio Fiorina
*
* This file is part of LIBTASN1.
*
* The LIBTASN1 library is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
#include "config.h"
#include "egg-asn1x.h"
#include "egg-asn1-defs.h"
#include "egg-timegm.h"
#include
#include
/* From libtasn1's libtasn.h */
enum {
ASN1_CLASS_UNIVERSAL = 0x00,
ASN1_CLASS_APPLICATION = 0x40,
ASN1_CLASS_CONTEXT_SPECIFIC = 0x80,
ASN1_CLASS_PRIVATE = 0xC0,
ASN1_CLASS_STRUCTURED = 0x20,
};
enum {
ASN1_TAG_BOOLEAN = 0x01,
ASN1_TAG_INTEGER = 0x02,
ASN1_TAG_SEQUENCE = 0x10,
ASN1_TAG_SET = 0x11,
ASN1_TAG_OCTET_STRING = 0x04,
ASN1_TAG_BIT_STRING = 0x03,
ASN1_TAG_UTC_TIME = 0x17,
ASN1_TAG_GENERALIZED_TIME = 0x18,
ASN1_TAG_OBJECT_ID = 0x06,
ASN1_TAG_ENUMERATED = 0x0A,
ASN1_TAG_NULL = 0x05,
ASN1_TAG_GENERAL_STRING = 0x1B,
ASN1_TAG_NUMERIC_STRING = 0x12,
ASN1_TAG_IA5_STRING = 0x16,
ASN1_TAG_TELETEX_STRING = 0x14,
ASN1_TAG_PRINTABLE_STRING = 0x13,
ASN1_TAG_UNIVERSAL_STRING = 0x1C,
ASN1_TAG_BMP_STRING = 0x1E,
ASN1_TAG_UTF8_STRING = 0x0C,
ASN1_TAG_VISIBLE_STRING = 0x1A,
};
/* From libtasn1's int.h */
enum {
FLAG_UNIVERSAL = (1<<8),
FLAG_PRIVATE = (1<<9),
FLAG_APPLICATION = (1<<10),
FLAG_EXPLICIT = (1<<11),
FLAG_IMPLICIT = (1<<12),
FLAG_TAG = (1<<13),
FLAG_OPTION = (1<<14),
FLAG_DEFAULT = (1<<15),
FLAG_TRUE = (1<<16),
FLAG_FALSE = (1<<17),
FLAG_LIST = (1<<18),
FLAG_MIN_MAX = (1<<19),
FLAG_1_PARAM = (1<<20),
FLAG_SIZE = (1<<21),
FLAG_DEFINED_BY = (1<<22),
FLAG_GENERALIZED = (1<<23),
FLAG_UTC = (1<<24),
FLAG_IMPORTS = (1<<25),
FLAG_NOT_USED = (1<<26),
FLAG_SET = (1<<27),
FLAG_ASSIGN = (1<<28),
FLAG_DOWN = (1<<29),
FLAG_RIGHT = (1<<30),
};
typedef struct _Atlv Atlv;
typedef struct _Anode Anode;
struct _Atlv {
guchar cls;
gulong tag;
gint off;
gint len;
/* An actual value here */
GBytes *value;
/* Reference to what was decoded */
GBytes *decoded;
/* Chain this into a tree */
struct _Atlv *child;
struct _Atlv *next;
/* Used during encoding */
/* Encoding: for bitstring, the number of empty bits at end */
guint bits_empty : 3;
/* Encoding: tell us whether we're dealing with a bit string */
guint prefix_for_bit_string : 1;
/* Encoding: prefix a zero byte for unsigned integers */
guint prefix_with_zero_byte : 1;
/* Encoding: sort children of this tlv (ie: SETOF) */
guint sorted : 1;
};
struct _Anode {
const EggAsn1xDef *def;
const EggAsn1xDef *join;
GList *opts;
GBytes *value;
Atlv *parsed;
gchar* failure;
/* If this node was chosen out of a choice */
guint chosen : 1;
/* For bitstring the number of empty bits */
guint bits_empty : 3;
/* Whether we need to prefix a zero byte to make unsigned */
guint guarantee_unsigned : 1;
};
/* Forward Declarations */
static gboolean anode_decode_anything (GNode *, Atlv *);
static gboolean anode_decode_one (GNode *, Atlv *);
static GBytes * anode_default_boolean (GNode *node);
static GBytes * anode_default_integer (GNode *node);
static gboolean anode_validate_anything (GNode *, gboolean);
static Atlv * anode_build_anything (GNode*, gboolean want);
static gint
atoin (const char *p, gint digits)
{
gint ret = 0, base = 1;
while(--digits >= 0) {
if (p[digits] < '0' || p[digits] > '9')
return -1;
ret += (p[digits] - '0') * base;
base *= 10;
}
return ret;
}
static const guchar *
bytes_get_end (GBytes *data)
{
const guchar *beg;
gsize size;
beg = g_bytes_get_data (data, &size);
return beg + size;
}
typedef struct {
EggAllocator allocator;
gpointer allocated;
} AllocatorClosure;
static void
allocator_closure_free (gpointer data)
{
AllocatorClosure *closure = data;
g_assert (closure->allocator);
(closure->allocator) (closure->allocated, 0);
g_slice_free (AllocatorClosure, closure);
}
static GBytes *
bytes_new_with_allocator (EggAllocator allocator,
guchar **data,
gsize length)
{
AllocatorClosure *closure;
if (allocator == g_realloc)
allocator = NULL;
if (allocator) {
*data = (allocator) (NULL, length + 1);
g_return_val_if_fail (*data != NULL, NULL);
closure = g_slice_new (AllocatorClosure);
closure->allocated = *data;
closure->allocator = allocator;
return g_bytes_new_with_free_func (*data, length,
allocator_closure_free,
closure);
} else {
*data = g_malloc (length);
return g_bytes_new_take (*data, length);
}
}
static GNode*
anode_new (const EggAsn1xDef *def)
{
Anode *an = g_slice_new0 (Anode);
an->def = def;
return g_node_new (an);
}
static gpointer
anode_copy_func (gconstpointer src, gpointer unused)
{
const Anode *san = src;
Anode *an = g_slice_new0 (Anode);
an->def = san->def;
an->join = san->join;
an->opts = g_list_copy (san->opts);
return an;
}
static GNode*
anode_clone (GNode *node)
{
return g_node_copy_deep (node, anode_copy_func, NULL);
}
static int
anode_def_type (GNode *node)
{
Anode *an = node->data;
gint type = an->join ? an->join->type : an->def->type;
return type & 0xFF;
}
static gboolean
anode_def_type_is_real (GNode *node)
{
switch (anode_def_type (node)) {
case EGG_ASN1X_INTEGER:
case EGG_ASN1X_BOOLEAN:
case EGG_ASN1X_BIT_STRING:
case EGG_ASN1X_OCTET_STRING:
case EGG_ASN1X_OBJECT_ID:
case EGG_ASN1X_TIME:
case EGG_ASN1X_UTC_TIME:
case EGG_ASN1X_GENERALIZED_TIME:
case EGG_ASN1X_NULL:
case EGG_ASN1X_ENUMERATED:
case EGG_ASN1X_GENERAL_STRING:
case EGG_ASN1X_NUMERIC_STRING:
case EGG_ASN1X_IA5_STRING:
case EGG_ASN1X_TELETEX_STRING:
case EGG_ASN1X_PRINTABLE_STRING:
case EGG_ASN1X_UNIVERSAL_STRING:
case EGG_ASN1X_BMP_STRING:
case EGG_ASN1X_UTF8_STRING:
case EGG_ASN1X_VISIBLE_STRING:
return TRUE;
case EGG_ASN1X_SEQUENCE:
case EGG_ASN1X_SEQUENCE_OF:
case EGG_ASN1X_ANY:
case EGG_ASN1X_SET:
case EGG_ASN1X_SET_OF:
case EGG_ASN1X_CHOICE:
return TRUE;
case EGG_ASN1X_CONSTANT:
case EGG_ASN1X_IDENTIFIER:
case EGG_ASN1X_TAG:
case EGG_ASN1X_DEFAULT:
case EGG_ASN1X_SIZE:
case EGG_ASN1X_DEFINITIONS:
case EGG_ASN1X_IMPORTS:
return FALSE;
}
g_return_val_if_reached (FALSE);
}
static int
anode_def_flags (GNode *node)
{
Anode *an = node->data;
gint type = an->def->type;
if (an->join)
type |= an->join->type;
return type & 0xFFFFFF00;
}
static const gchar*
anode_def_name (GNode *node)
{
Anode *an = node->data;
return an->def->name;
}
static const gchar*
anode_def_value (GNode *node)
{
Anode *an = node->data;
return an->def->value;
}
static gulong
anode_def_value_as_ulong (const EggAsn1xDef *def)
{
gchar *end = NULL;
gulong lval;
g_return_val_if_fail (def->value, G_MAXULONG);
lval = strtoul (def->value, &end, 10);
g_return_val_if_fail (end && !end[0], G_MAXULONG);
return lval;
}
static GNode*
anode_child_with_name (GNode *node, const gchar *name)
{
GNode *child;
for (child = node->children; child; child = child->next) {
if (g_str_equal (name, anode_def_name (child)))
return child;
}
return NULL;
}
static void
anode_opt_add (GNode *node,
const EggAsn1xDef *def)
{
Anode *an = node->data;
an->opts = g_list_append (an->opts, (gpointer)def);
}
static EggAsn1xDef *
anode_opt_lookup (GNode *node,
gint type,
const gchar *name)
{
Anode *an = node->data;
EggAsn1xDef *def;
GList *l;
for (l = an->opts; l; l = g_list_next (l)) {
def = l->data;
if (name && def->name && !g_str_equal (name, def->name))
continue;
if ((def->type & 0xFF) == type)
return def;
}
return NULL;
}
static EggAsn1xDef *
anode_opt_lookup_value (GNode *node,
gint type,
const gchar *value)
{
Anode *an = node->data;
EggAsn1xDef *def;
GList *l;
for (l = an->opts; l; l = g_list_next (l)) {
def = l->data;
if (value && def->value && !g_str_equal (value, def->value))
continue;
if ((def->type & 0xFF) == type)
return def;
}
return NULL;
}
static GList*
anode_opts_lookup (GNode *node, gint type, const gchar *name)
{
Anode *an = node->data;
EggAsn1xDef *def;
GList *l, *res = NULL;
for (l = an->opts; l; l = g_list_next (l)) {
def = l->data;
if (name && def->name && !g_str_equal (name, def->name))
continue;
if ((def->type & 0xFF) == type)
res = g_list_prepend (res, def);
}
return g_list_reverse (res);
}
static Atlv *
atlv_new (void)
{
return g_slice_new0 (Atlv);
}
static void
atlv_free (Atlv *tlv)
{
if (!tlv)
return;
/* Free attached TLVs */
atlv_free (tlv->child);
atlv_free (tlv->next);
/* Free the TLV */
if (tlv->decoded)
g_bytes_unref (tlv->decoded);
if (tlv->value)
g_bytes_unref (tlv->value);
g_slice_free (Atlv, tlv);
}
static Atlv *
atlv_dup (Atlv *tlv,
gboolean siblings)
{
Atlv *copy;
if (!tlv)
return NULL;
copy = g_slice_new0 (Atlv);
memcpy (copy, tlv, sizeof (Atlv));
if (tlv->value != NULL)
copy->value = g_bytes_ref (tlv->value);
if (tlv->decoded != NULL)
copy->decoded = g_bytes_ref (tlv->decoded);
copy->child = atlv_dup (tlv->child, TRUE);
if (siblings)
copy->next = atlv_dup (tlv->next, TRUE);
else
copy->next = NULL;
return copy;
}
static inline GBytes *
anode_get_value (GNode *node)
{
Anode *an = node->data;
return an->value;
}
static inline void
anode_clr_value (GNode *node)
{
Anode *an = node->data;
if (an->value)
g_bytes_unref (an->value);
an->value = NULL;
atlv_free (an->parsed);
an->parsed = NULL;
}
static inline void
anode_take_value (GNode *node,
GBytes *value)
{
Anode *an = node->data;
anode_clr_value (node);
an->value = value;
}
static inline void
anode_set_value (GNode *node,
GBytes *value)
{
anode_take_value (node, g_bytes_ref (value));
}
static inline Atlv *
anode_get_parsed (GNode *node)
{
Anode *an = node->data;
return an->parsed;
}
static gboolean
anode_failure (GNode *node, const gchar *failure)
{
Anode *an = node->data;
const gchar *prefix = an->def->name;
if (!prefix && an->join)
prefix = an->join->name;
if (!prefix)
prefix = an->def->value;
if (!prefix && an->join)
prefix = an->join->value;
if (!prefix)
prefix = "unknown";
g_free (an->failure);
an->failure = g_strdup_printf ("%s: %s", prefix, failure);
g_debug ("%s %s", prefix, an->failure);
return FALSE; /* So this can be chained */
}
static const gchar*
anode_failure_get (GNode *node)
{
Anode *an = node->data;
return an->failure;
}
static void
anode_clear (GNode *node)
{
Anode *an = node->data;
anode_clr_value (node);
g_free (an->failure);
an->failure = NULL;
}
static gboolean
anode_free_func (GNode *node, gpointer unused)
{
Anode *an = node->data;
anode_clear (node);
g_list_free (an->opts);
g_slice_free (Anode, an);
return FALSE;
}
static void
anode_destroy (GNode *node)
{
if (!G_NODE_IS_ROOT (node))
g_node_unlink (node);
g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_ALL, -1, anode_free_func, NULL);
g_node_destroy (node);
}
static gulong
anode_calc_tag_for_flags (GNode *node, gint flags)
{
EggAsn1xDef *def;
/* A context specific tag */
if (flags & FLAG_TAG) {
def = anode_opt_lookup (node, EGG_ASN1X_TAG, NULL);
g_return_val_if_fail (def, G_MAXULONG);
return anode_def_value_as_ulong (def);
}
/* A tag from the universal set */
switch (anode_def_type (node)) {
case EGG_ASN1X_INTEGER:
return ASN1_TAG_INTEGER;
case EGG_ASN1X_ENUMERATED:
return ASN1_TAG_ENUMERATED;
case EGG_ASN1X_BOOLEAN:
return ASN1_TAG_BOOLEAN;
case EGG_ASN1X_BIT_STRING:
return ASN1_TAG_BIT_STRING;
case EGG_ASN1X_OCTET_STRING:
return ASN1_TAG_OCTET_STRING;
case EGG_ASN1X_OBJECT_ID:
return ASN1_TAG_OBJECT_ID;
case EGG_ASN1X_NULL:
return ASN1_TAG_NULL;
case EGG_ASN1X_GENERAL_STRING:
return ASN1_TAG_GENERAL_STRING;
case EGG_ASN1X_NUMERIC_STRING:
return ASN1_TAG_NUMERIC_STRING;
case EGG_ASN1X_IA5_STRING:
return ASN1_TAG_IA5_STRING;
case EGG_ASN1X_TELETEX_STRING:
return ASN1_TAG_TELETEX_STRING;
case EGG_ASN1X_PRINTABLE_STRING:
return ASN1_TAG_PRINTABLE_STRING;
case EGG_ASN1X_UNIVERSAL_STRING:
return ASN1_TAG_UNIVERSAL_STRING;
case EGG_ASN1X_BMP_STRING:
return ASN1_TAG_BMP_STRING;
case EGG_ASN1X_UTF8_STRING:
return ASN1_TAG_UTF8_STRING;
case EGG_ASN1X_VISIBLE_STRING:
return ASN1_TAG_VISIBLE_STRING;
case EGG_ASN1X_TIME:
if (flags & FLAG_GENERALIZED)
return ASN1_TAG_GENERALIZED_TIME;
else if (flags & FLAG_UTC)
return ASN1_TAG_UTC_TIME;
else
g_return_val_if_reached (G_MAXULONG);
case EGG_ASN1X_UTC_TIME:
return ASN1_TAG_UTC_TIME;
case EGG_ASN1X_GENERALIZED_TIME:
return ASN1_TAG_GENERALIZED_TIME;
case EGG_ASN1X_SEQUENCE:
case EGG_ASN1X_SEQUENCE_OF:
return ASN1_TAG_SEQUENCE;
case EGG_ASN1X_SET:
case EGG_ASN1X_SET_OF:
return ASN1_TAG_SET;
/* These should be handled specially */
case EGG_ASN1X_ANY:
case EGG_ASN1X_CHOICE:
return G_MAXULONG;
/* These are not real nodes */
case EGG_ASN1X_CONSTANT:
case EGG_ASN1X_IDENTIFIER:
case EGG_ASN1X_TAG:
case EGG_ASN1X_DEFAULT:
case EGG_ASN1X_SIZE:
case EGG_ASN1X_DEFINITIONS:
case EGG_ASN1X_IMPORTS:
g_return_val_if_reached (G_MAXULONG);
}
g_return_val_if_reached (G_MAXULONG);
}
static gulong
anode_calc_tag (GNode *node)
{
return anode_calc_tag_for_flags (node, anode_def_flags (node));
}
static gboolean
anode_calc_explicit_for_flags (GNode *node,
gint flags,
guchar *cls_type)
{
const EggAsn1xDef *opt;
if ((flags & FLAG_TAG) != FLAG_TAG)
return FALSE;
opt = anode_opt_lookup (node, EGG_ASN1X_TAG, NULL);
g_return_val_if_fail (opt, FALSE);
if (cls_type) {
if (opt->type & FLAG_UNIVERSAL)
*cls_type = ASN1_CLASS_UNIVERSAL;
else if (opt->type & FLAG_APPLICATION)
*cls_type = ASN1_CLASS_APPLICATION;
else if (opt->type & FLAG_PRIVATE)
*cls_type = ASN1_CLASS_PRIVATE;
else
*cls_type = ASN1_CLASS_CONTEXT_SPECIFIC;
}
if ((opt->type & FLAG_IMPLICIT) == FLAG_IMPLICIT)
return FALSE;
return TRUE;
}
/* -------------------------------------------------------------------------
* PARSING
*/
static gboolean
atlv_parse_cls_tag (const guchar *at,
const guchar *end,
guchar *cls,
gulong *tag,
gint *off)
{
gint punt, ris, last;
gint n_data;
guchar val;
g_assert (end >= at);
g_assert (cls != NULL);
g_assert (off != NULL);
n_data = end - at;
if (n_data < 2)
return FALSE;
*cls = at[0] & 0xE0;
/* short form */
if ((at[0] & 0x1F) != 0x1F) {
*off = 1;
ris = at[0] & 0x1F;
/* Long form */
} else {
punt = 1;
ris = 0;
while (punt <= n_data) {
val = at[punt++];
last = ris;
ris = ris * 128;
/* wrapper around, and no bignums... */
if (ris < last)
return FALSE;
last = ris;
ris += (val & 0x7F);
/* wrapper around, and no bignums... */
if (ris < last)
return FALSE;
if ((val & 0x7F) == val)
break;
}
if (punt >= n_data)
return FALSE;
*off = punt;
}
if (tag)
*tag = ris;
return TRUE;
}
static gint
atlv_parse_length (const guchar *at,
const guchar *end,
gint *off)
{
gint ans, last;
gint k, punt;
gint n_data;
g_assert (at != NULL);
g_assert (end != NULL);
g_assert (end > at);
g_assert (off != NULL);
*off = 0;
n_data = end - at;
/* short form */
if (!(at[0] & 128)) {
*off = 1;
return at[0];
/* Long form */
} else {
k = at[0] & 0x7F;
punt = 1;
/* definite length method */
if (k) {
ans = 0;
while (punt <= k && punt < n_data) {
last = ans;
ans = ans * 256;
/* we wrapped around, no bignum support... */
if (ans < last)
return -2;
last = ans;
ans += at[punt++];
/* we wrapped around, no bignum support... */
if (ans < last)
return -2;
}
/* indefinite length method */
} else {
ans = -1;
}
*off = punt;
return ans;
}
}
static gboolean
atlv_parse_cls_tag_len (const guchar *at,
const guchar *end,
guchar *cls,
gulong *tag,
gint *off,
gint *len)
{
gint cb1, cb2;
g_assert (at != NULL);
g_assert (end != NULL);
g_assert (end >= at);
g_assert (off != NULL);
g_assert (len != NULL);
if (!atlv_parse_cls_tag (at, end, cls, tag, &cb1))
return FALSE;
*len = atlv_parse_length (at + cb1, end, &cb2);
if (*len < -1)
return FALSE;
*off = cb1 + cb2;
if (*len >= 0 && at + *off + *len > end)
return FALSE;
return TRUE;
}
static const gchar *
atlv_parse_der_tag (guchar cls,
gulong tag,
gint off,
gint len,
GBytes *data,
const guchar **at,
Atlv *tlv)
{
const guchar *end;
const gchar *ret;
const guchar *beg;
guchar ccls;
gulong ctag;
gint clen;
gint coff;
Atlv *child;
Atlv *last;
g_assert (at != NULL);
g_assert (tlv != NULL);
end = bytes_get_end (data);
g_assert (*at <= end);
g_return_val_if_fail (*at + off + len <= end, "invalid length of tlv");
if (len < 0 && !(cls & ASN1_CLASS_STRUCTURED))
return "indefinite length on non-structured type";
beg = *at;
tlv->cls = cls;
tlv->tag = tag;
tlv->off = off;
tlv->len = len;
(*at) += off;
/* Structured TLV, with further TLVs inside */
if (cls & ASN1_CLASS_STRUCTURED) {
/* If not indefinite length, then calculate end up front */
if (len >= 0)
end = (*at) + len;
last = NULL;
while (*at < end) {
if (!atlv_parse_cls_tag_len (*at, end, &ccls, &ctag, &coff, &clen))
return "content is not encoded properly";
/* End if indefinite length? */
if (len < 0 && ccls == ASN1_CLASS_UNIVERSAL && ctag == 0 && clen == 0) {
(*at) += coff;
break;
}
/* Parse the child */
child = atlv_new ();
ret = atlv_parse_der_tag (ccls, ctag, coff, clen, data, at, child);
if (ret != NULL) {
atlv_free (child);
return ret;
}
/* Add the child to the right place */
if (last == NULL)
tlv->child = child;
else
last->next = child;
last = child;
}
/* Non-structured TLV, just a value */
} else {
tlv->value = g_bytes_new_with_free_func (*at, len,
(GDestroyNotify)g_bytes_unref,
g_bytes_ref (data));
(*at) += len;
}
/* Note the actual DER that we decoded */
tlv->decoded = g_bytes_new_with_free_func (beg, *at - beg,
(GDestroyNotify)g_bytes_unref,
g_bytes_ref (data));
return NULL; /* Success */
}
static const gchar *
atlv_parse_der (GBytes *data,
Atlv *tlv)
{
const guchar *end;
const guchar *at;
const gchar *ret;
guchar cls;
gulong tag;
gint off;
gint len;
gsize size;
at = g_bytes_get_data (data, &size);
g_return_val_if_fail (at != NULL, FALSE);
end = at + size;
if (!atlv_parse_cls_tag_len (at, end, &cls, &tag, &off, &len))
return "content is not encoded properly";
ret = atlv_parse_der_tag (cls, tag, off, len, data, &at, tlv);
if (ret != NULL)
return ret;
if (at != end)
return "extra unexpected trailing data";
return NULL; /* Success */
}
/* -------------------------------------------------------------------------
* DECODING
*/
static gboolean
anode_decode_choice (GNode *node,
Atlv *tlv)
{
gboolean have = FALSE;
GNode *child;
Anode *an;
for (child = node->children; child; child = child->next) {
an = (Anode*)child->data;
if (anode_decode_one (child, tlv)) {
an->chosen = 1;
have = TRUE;
} else {
an->chosen = 0;
}
}
if (!have)
return anode_failure (node, "no choice is present");
return TRUE;
}
static gboolean
anode_decode_sequence_or_set (GNode *node,
Atlv *tlv)
{
Atlv *ctlv;
gulong tag;
gint i;
/*
* The reason we can parse a set just like a sequence, is because in DER,
* the order of the SET is predefined by the tags. In addition the definitions
* we have are sorted.
*/
/* Tags must be in ascending order */
if (anode_def_type (node) == EGG_ASN1X_SET) {
for (ctlv = tlv->child, i = 0; ctlv != NULL; ctlv = ctlv->next, i++) {
if (i > 0 && tag > ctlv->tag)
return anode_failure (node, "content must be in ascending order");
tag = ctlv->tag;
}
}
return anode_decode_anything (node->children, tlv->child);
}
static gboolean
anode_decode_sequence_or_set_of (GNode *node,
Atlv *tlv)
{
Atlv *ctlv;
GNode *child, *other;
gulong tag;
gint i;
/* The first child */
child = node->children;
g_return_val_if_fail (child, FALSE);
for (ctlv = tlv->child, i = 0; ctlv != NULL; ctlv = ctlv->next, i++) {
/* Tag must have same tag as top */
if (i == 0)
tag = anode_calc_tag (child);
else if (tag != G_MAXULONG && ctlv->tag != tag)
return anode_failure (node, "invalid mismatched content");
/* TODO: Set of must be in ascending order in DER encoding */
if (i == 0) {
other = child;
} else {
other = anode_clone (child);
g_node_append (node, other);
}
if (!anode_decode_one (other, ctlv))
return FALSE;
}
return TRUE;
}
static gboolean
anode_decode_bit_string (GNode *node,
Atlv *tlv)
{
Anode *an = node->data;
guchar empty, mask;
GBytes *value;
const guchar *buf;
gsize len;
buf = g_bytes_get_data (tlv->value, &len);
if (len == 0)
return anode_failure (node, "invalid length bit string");
/* The first byte is the number of empty bits */
empty = buf[0];
if (empty >= 8)
return anode_failure (node, "invalid number of empty bits");
/* Free bits at end must be zero */
mask = 0xFF >> (8 - empty);
if (len > 1 && buf[len - 1] & mask)
return anode_failure (node, "bit string has invalid trailing bits");
value = g_bytes_new_from_bytes (tlv->value, 1, len - 1);
anode_take_value (node, value);
an = node->data;
an->bits_empty = empty;
return TRUE;
}
static gboolean
anode_decode_primitive (GNode *node,
Atlv *tlv,
gint flags)
{
/* Must not have any tlv children */
g_assert (tlv->child == NULL);
switch (anode_def_type (node)) {
/* Handle bit strings specially */
case EGG_ASN1X_BIT_STRING:
return anode_decode_bit_string (node, tlv);
/* The primitive value types */
case EGG_ASN1X_INTEGER:
case EGG_ASN1X_ENUMERATED:
case EGG_ASN1X_BOOLEAN:
case EGG_ASN1X_OCTET_STRING:
case EGG_ASN1X_OBJECT_ID:
case EGG_ASN1X_NULL:
case EGG_ASN1X_TIME:
case EGG_ASN1X_UTC_TIME:
case EGG_ASN1X_GENERALIZED_TIME:
case EGG_ASN1X_GENERAL_STRING:
case EGG_ASN1X_NUMERIC_STRING:
case EGG_ASN1X_IA5_STRING:
case EGG_ASN1X_TELETEX_STRING:
case EGG_ASN1X_PRINTABLE_STRING:
case EGG_ASN1X_UNIVERSAL_STRING:
case EGG_ASN1X_BMP_STRING:
case EGG_ASN1X_UTF8_STRING:
case EGG_ASN1X_VISIBLE_STRING:
anode_set_value (node, tlv->value);
return TRUE;
/* Just use the 'parsed' which is automatically set */
case EGG_ASN1X_ANY:
return TRUE;
case EGG_ASN1X_CHOICE:
return anode_decode_choice (node, tlv);
}
return anode_failure (node, "primitive value of an unexpected type"); /* UNREACHABLE: tag validation? */
}
static gboolean
anode_decode_structured (GNode *node,
Atlv *tlv,
gint flags)
{
switch (anode_def_type (node)) {
/* Just use the 'parsed' which is automatically set */
case EGG_ASN1X_ANY:
case EGG_ASN1X_GENERAL_STRING:
case EGG_ASN1X_OCTET_STRING:
case EGG_ASN1X_NUMERIC_STRING:
case EGG_ASN1X_IA5_STRING:
case EGG_ASN1X_TELETEX_STRING:
case EGG_ASN1X_PRINTABLE_STRING:
case EGG_ASN1X_UNIVERSAL_STRING:
case EGG_ASN1X_BMP_STRING:
case EGG_ASN1X_UTF8_STRING:
case EGG_ASN1X_VISIBLE_STRING:
return TRUE;
case EGG_ASN1X_CHOICE:
return anode_decode_choice (node, tlv);
case EGG_ASN1X_SEQUENCE:
case EGG_ASN1X_SET:
return anode_decode_sequence_or_set (node, tlv);
case EGG_ASN1X_SEQUENCE_OF:
case EGG_ASN1X_SET_OF:
return anode_decode_sequence_or_set_of (node, tlv);
default:
return anode_failure (node, "structured value of an unexpected type"); /* UNREACHABLE: tag validation? */
}
}
static gboolean
anode_decode_one_without_tag (GNode *node,
Atlv *tlv,
gint flags)
{
gboolean ret;
Anode *an;
/* An explicit, wrapped tag */
if (anode_calc_explicit_for_flags (node, flags, NULL)) {
if ((tlv->cls & ASN1_CLASS_CONTEXT_SPECIFIC) == 0)
return anode_failure (node, "missing context specific tag");
if (tlv->child == NULL)
return anode_failure (node, "missing context specific child");
if (tlv->child->next != NULL)
return anode_failure (node, "multiple context specific children");
flags &= ~FLAG_TAG;
ret = anode_decode_one_without_tag (node, tlv->child, flags);
/* Structured value */
} else if (tlv->cls & ASN1_CLASS_STRUCTURED) {
ret = anode_decode_structured (node, tlv, flags);
/* A primitive simple value */
} else {
ret = anode_decode_primitive (node, tlv, flags);
}
/* Mark which tlv we used for this node */
if (ret) {
an = node->data;
atlv_free (an->parsed);
an->parsed = atlv_dup (tlv, FALSE);
}
return ret;
}
static gboolean
anode_decode_one (GNode *node,
Atlv *tlv)
{
gint flags = anode_def_flags (node);
gulong tag;
tag = anode_calc_tag_for_flags (node, flags);
/* We don't know what the tag is supposed to be */
if (tag == G_MAXULONG)
tag = tlv->tag;
/* We have no match */
if (tag != tlv->tag)
return anode_failure (node, "decoded tag did not match expected");
return anode_decode_one_without_tag (node, tlv, flags);
}
static gboolean
anode_decode_option_or_default (GNode *node)
{
gint flags = anode_def_flags (node);
if (flags & FLAG_OPTION || flags & FLAG_DEFAULT) {
anode_clr_value (node);
return TRUE;
}
return FALSE;
}
static gboolean
anode_decode_anything (GNode *node,
Atlv *tlv)
{
GNode *prev = NULL;
GNode *next;
gulong tag;
gint flags;
g_assert (node != NULL);
while (tlv != NULL) {
if (node == NULL)
return anode_failure (prev, "encountered extra tag");
flags = anode_def_flags (node);
tag = anode_calc_tag_for_flags (node, flags);
/* We don't know what the tag is supposed to be */
if (tag == G_MAXULONG)
tag = tlv->tag;
/* We have no match */
if (tag != tlv->tag) {
/* See if we can skip this node */
if (anode_decode_option_or_default (node))
next = g_node_next_sibling (node);
else
next = NULL;
if (next == NULL)
return anode_failure (node, "decoded tag did not match expected");
prev = node;
node = next;
continue;
}
if (!anode_decode_one_without_tag (node, tlv, flags))
return FALSE;
/* Next node and tag */
prev = node;
node = g_node_next_sibling (node);
tlv = tlv->next;
}
/* We have no values for these nodes */
while (node != NULL) {
if (anode_decode_option_or_default (node))
node = g_node_next_sibling (node);
else
return anode_failure (node, "no decoded value");
}
return TRUE;
}
gboolean
egg_asn1x_decode_full (GNode *asn,
GBytes *data,
gint options)
{
const gchar *msg;
gboolean ret;
Anode *an;
Atlv *tlv;
g_return_val_if_fail (asn != NULL, FALSE);
g_return_val_if_fail (data != NULL, FALSE);
egg_asn1x_clear (asn);
tlv = atlv_new ();
msg = atlv_parse_der (data, tlv);
if (msg == NULL) {
ret = anode_decode_anything (asn, tlv);
/* A failure, set the message manually so it doesn't get a prefix */
} else {
an = asn->data;
g_free (an->failure);
an->failure = g_strdup (msg);
ret = FALSE;
}
atlv_free (tlv);
if (ret == FALSE)
return FALSE;
return egg_asn1x_validate (asn, !(options & EGG_ASN1X_NO_STRICT));
}
gboolean
egg_asn1x_decode (GNode *asn,
GBytes *data)
{
g_return_val_if_fail (asn != NULL, FALSE);
g_return_val_if_fail (data != NULL, FALSE);
return egg_asn1x_decode_full (asn, data, 0);
}
/* -----------------------------------------------------------------------------------
* UNPARSE
*/
static void
atlv_unparse_len (gulong len,
guchar *ans,
gint *cb)
{
guchar temp[sizeof (gulong)];
gint k;
g_assert (cb);
/* short form */
if (len < 128) {
if (ans != NULL)
ans[0] = (unsigned char)len;
*cb = 1;
/* Long form */
} else {
k = 0;
while (len) {
temp[k++] = len & 0xFF;
len = len >> 8;
}
*cb = k + 1;
if (ans != NULL) {
ans[0] = ((unsigned char) k & 0x7F) + 128;
while (k--)
ans[*cb - 1 - k] = temp[k];
}
}
}
static gint
atlv_unparse_cls_tag_len (guchar *data,
gsize n_data,
guchar cls,
gulong tag,
gint len)
{
guchar temp[sizeof(gulong)];
gint cb;
gint off = 0;
gint k;
/* Short form */
if (tag < 31) {
off += 1;
if (data) {
g_assert (n_data >= off);
data[0] = (cls & 0xE0) + ((guchar) (tag & 0x1F));
}
/* Long form */
} else {
k = 0;
while (tag) {
temp[k++] = tag & 0x7F;
tag = tag >> 7;
}
off = k + 1;
if (data) {
g_assert (n_data >= off);
data[0] = (cls & 0xE0) + 31;
while (data && k--)
data[off - 1 - k] = temp[k] + 128;
data[off - 1] -= 128;
}
}
/* And now the length */
cb = n_data - off;
atlv_unparse_len (len, data ? data + off : NULL, &cb);
off += cb;
g_assert (!data || n_data >= off);
return off;
}
static void
atlv_unparse_der (Atlv *tlv,
guchar **at,
guchar *end)
{
const guchar *exp;
const guchar *buf;
guchar *p;
guchar mask;
Atlv *ctlv;
gint off;
gsize len;
g_assert (*at <= end);
off = atlv_unparse_cls_tag_len (*at, end - *at, tlv->cls,
tlv->tag, tlv->len);
g_assert (off == tlv->off);
(*at) += off;
/* Write a value */
if (tlv->value) {
buf = g_bytes_get_data (tlv->value, &len);
p = *at;
/* Special behavior for bit strings */
if (tlv->prefix_for_bit_string) {
g_assert (len + 1 == tlv->len);
p[0] = (guchar)tlv->bits_empty;
memcpy (p + 1, buf, len);
/* Set the extra bits to zero */
if (len && tlv->bits_empty) {
mask = 0xFF >> (8 - tlv->bits_empty);
p[len] &= ~mask;
}
p += len + 1;
/* Special behavior for prefixed integers */
} else if (tlv->prefix_with_zero_byte) {
g_assert (len + 1 == tlv->len);
p[0] = 0;
memcpy (p + 1, buf, len);
p += len + 1;
/* Standard behavior */
} else {
g_assert (len == tlv->len);
memcpy (p, buf, len);
p += len;
}
*at = p;
/* Write a bunch of child TLV's */
} else {
for (ctlv = tlv->child; ctlv != NULL; ctlv = ctlv->next) {
exp = *at + ctlv->len + ctlv->off;
atlv_unparse_der (ctlv, at, end);
g_assert (exp == *at);
}
}
g_assert (*at <= end);
}
static GBytes *
atlv_unparse_to_bytes (Atlv *tlv,
EggAllocator allocator)
{
GBytes *bytes;
guchar *data;
guchar *at;
gint len;
/* Allocate enough memory for entire thingy */
len = tlv->off + tlv->len;
g_return_val_if_fail (len != 0, NULL);
bytes = bytes_new_with_allocator (allocator, &data, len);
g_return_val_if_fail (bytes != NULL, NULL);
at = data;
atlv_unparse_der (tlv, &at, data + len);
g_assert (at == data + len);
return bytes;
}
typedef struct {
GBytes *bytes;
Atlv *tlv;
} SortPair;
static gint
compare_sort_pair (gconstpointer a,
gconstpointer b)
{
const SortPair *sa = a;
const SortPair *sb = b;
return g_bytes_compare (sa->bytes, sb->bytes);
}
static void
atlv_sort_perform (Atlv *tlv,
EggAllocator allocator)
{
GList *pairs, *l;
SortPair *pair;
GBytes *bytes;
Atlv *ctlv;
Atlv *last;
gboolean sort;
for (ctlv = tlv->child; ctlv != NULL; ctlv = ctlv->next)
atlv_sort_perform (ctlv, allocator);
if (!tlv->sorted)
return;
pairs = NULL;
for (ctlv = tlv->child; ctlv != NULL; ctlv = ctlv->next) {
bytes = atlv_unparse_to_bytes (ctlv, allocator);
g_return_if_fail (bytes != NULL);
pair = g_slice_new0 (SortPair);
pair->bytes = bytes;
pair->tlv = ctlv;
pairs = g_list_prepend (pairs, pair);
}
/* Only sort of the above unparse completed for all */
sort = ctlv == NULL;
last = NULL;
pairs = g_list_sort (pairs, compare_sort_pair);
for (l = pairs; l != NULL; l = g_list_next (l)) {
pair = l->data;
/* Only if the sort completed */
if (sort) {
if (last == NULL)
tlv->child = pair->tlv;
else
last->next = pair->tlv;
last = pair->tlv;
}
g_bytes_unref (pair->bytes);
g_slice_free (SortPair, pair);
}
g_list_free (pairs);
}
static void
anode_build_cls_tag_len (GNode *node,
Atlv *tlv,
gint len)
{
gboolean explicit = FALSE;
guchar cls_type;
gint flags;
/* One for the prefix character */
if (tlv->prefix_for_bit_string ||
tlv->prefix_with_zero_byte)
len += 1;
/* Figure out the basis if the class */
switch (anode_def_type (node)) {
case EGG_ASN1X_INTEGER:
case EGG_ASN1X_BOOLEAN:
case EGG_ASN1X_BIT_STRING:
case EGG_ASN1X_OCTET_STRING:
case EGG_ASN1X_OBJECT_ID:
case EGG_ASN1X_TIME:
case EGG_ASN1X_UTC_TIME:
case EGG_ASN1X_GENERALIZED_TIME:
case EGG_ASN1X_ENUMERATED:
case EGG_ASN1X_GENERAL_STRING:
case EGG_ASN1X_NUMERIC_STRING:
case EGG_ASN1X_IA5_STRING:
case EGG_ASN1X_TELETEX_STRING:
case EGG_ASN1X_PRINTABLE_STRING:
case EGG_ASN1X_UNIVERSAL_STRING:
case EGG_ASN1X_BMP_STRING:
case EGG_ASN1X_UTF8_STRING:
case EGG_ASN1X_VISIBLE_STRING:
case EGG_ASN1X_NULL:
tlv->cls = ASN1_CLASS_UNIVERSAL;
break;
/* Container types */
case EGG_ASN1X_SEQUENCE:
case EGG_ASN1X_SET:
case EGG_ASN1X_SEQUENCE_OF:
case EGG_ASN1X_SET_OF:
tlv->cls = (ASN1_CLASS_STRUCTURED | ASN1_CLASS_UNIVERSAL);
break;
/* Transparent types shouldn't get here */
case EGG_ASN1X_ANY:
case EGG_ASN1X_CHOICE:
default:
g_assert_not_reached ();
};
flags = anode_def_flags (node);
/* Build up the class */
if (flags & FLAG_TAG) {
explicit = anode_calc_explicit_for_flags (node, flags, &cls_type);
if (explicit)
flags &= ~FLAG_TAG;
else
tlv->cls |= cls_type;
}
/* Setup the class */
tlv->tag = anode_calc_tag_for_flags (node, flags);
/* The offset and length */
tlv->len = len;
tlv->off = atlv_unparse_cls_tag_len (NULL, 0, tlv->cls, tlv->tag, len);
}
static Atlv *
anode_build_value (GNode *node)
{
Anode *an = node->data;
Atlv *tlv;
gsize len;
/* Fill this in based on the value */
if (an->value == NULL)
return NULL;
tlv = atlv_new ();
tlv->value = g_bytes_ref (an->value);
len = g_bytes_get_size (an->value);
anode_build_cls_tag_len (node, tlv, len);
return tlv;
}
static Atlv *
anode_build_bit_string (GNode *node)
{
Anode *an = node->data;
Atlv *tlv;
gsize len;
if (an->value == NULL)
return NULL;
tlv = atlv_new ();
tlv->value = g_bytes_ref (an->value);
tlv->bits_empty = an->bits_empty;
tlv->prefix_for_bit_string = 1;
len = g_bytes_get_size (an->value);
anode_build_cls_tag_len (node, tlv, len);
return tlv;
}
static Atlv *
anode_build_integer (GNode *node)
{
Anode *an = node->data;
const guchar *buf;
gboolean sign;
gsize len;
Atlv *tlv;
if (an->value == NULL)
return NULL;
tlv = atlv_new ();
tlv->value = g_bytes_ref (an->value);
buf = g_bytes_get_data (an->value, &len);
if (an->guarantee_unsigned) {
/*
* In two's complement (which DER is) this would be negative, add a zero
* byte so that it isn't. Here we just note that the result will be one
* byte longer.
*/
sign = !!(buf[0] & 0x80);
if (sign)
tlv->prefix_with_zero_byte = 1;
}
anode_build_cls_tag_len (node, tlv, len);
return tlv;
}
static Atlv *
anode_build_any (GNode *node)
{
Atlv *parsed;
/*
* Fill this in based on already parsed TLVs. It is assumed
* that any explicit tags are already present, and the functions
* for managing ANY try to enforce this.
*/
parsed = anode_get_parsed (node);
if (parsed != NULL)
return atlv_dup (parsed, FALSE);
return NULL;
}
static Atlv *
anode_build_choice (GNode *node,
gboolean want)
{
GNode *child;
g_assert (anode_def_type (node) == EGG_ASN1X_CHOICE);
child = egg_asn1x_get_choice (node);
/* Should have been checked by a previous validate */
g_return_val_if_fail (child != NULL, NULL);
return anode_build_anything (child, want);
}
static Atlv *
anode_build_structured (GNode *node,
gboolean want)
{
gboolean child_want;
Atlv *last;
Atlv *ctlv;
Atlv *tlv;
GNode *child;
gint type;
gint len;
type = anode_def_type (node);
child_want = want;
last = NULL;
len = 0;
if (type == EGG_ASN1X_SEQUENCE_OF || type == EGG_ASN1X_SET_OF)
child_want = FALSE;
if (anode_def_flags (node) & FLAG_OPTION)
want = FALSE;
tlv = atlv_new ();
for (child = node->children; child != NULL; child = child->next) {
ctlv = anode_build_anything (child, child_want);
if (ctlv != NULL) {
if (last == NULL)
tlv->child = ctlv;
else
last->next = ctlv;
last = ctlv;
len += ctlv->off + ctlv->len;
}
}
if (last == NULL) {
/* See if we should encode an empty set or seq of */
if (type == EGG_ASN1X_SEQUENCE_OF || type == EGG_ASN1X_SET_OF) {
if (!want) {
atlv_free (tlv);
return NULL;
}
} else if (!want) {
atlv_free (tlv);
return NULL;
}
}
anode_build_cls_tag_len (node, tlv, len);
if (type == EGG_ASN1X_SET_OF)
tlv->sorted = 1;
return tlv;
}
static Atlv *
anode_build_maybe_explicit (GNode *node,
Atlv *tlv,
gint flags)
{
guchar cls_type;
Atlv *wrap;
/* Now wrap in explicit tag if that's the case */
if (anode_calc_explicit_for_flags (node, flags, &cls_type)) {
wrap = atlv_new ();
wrap->cls = (ASN1_CLASS_STRUCTURED | cls_type);
wrap->tag = anode_calc_tag (node);
wrap->len = tlv->off + tlv->len;
wrap->off = atlv_unparse_cls_tag_len (NULL, 0, wrap->cls, wrap->tag, wrap->len);
wrap->child = tlv;
tlv = wrap;
}
return tlv;
}
static Atlv *
anode_build_anything_for_flags (GNode *node,
gboolean want,
gint flags)
{
Atlv *tlv;
switch (anode_def_type (node)) {
case EGG_ASN1X_BIT_STRING:
tlv = anode_build_bit_string (node);
break;
case EGG_ASN1X_INTEGER:
tlv = anode_build_integer (node);
break;
case EGG_ASN1X_BOOLEAN:
case EGG_ASN1X_OCTET_STRING:
case EGG_ASN1X_OBJECT_ID:
case EGG_ASN1X_TIME:
case EGG_ASN1X_UTC_TIME:
case EGG_ASN1X_GENERALIZED_TIME:
case EGG_ASN1X_ENUMERATED:
case EGG_ASN1X_GENERAL_STRING:
case EGG_ASN1X_NUMERIC_STRING:
case EGG_ASN1X_IA5_STRING:
case EGG_ASN1X_TELETEX_STRING:
case EGG_ASN1X_PRINTABLE_STRING:
case EGG_ASN1X_UNIVERSAL_STRING:
case EGG_ASN1X_BMP_STRING:
case EGG_ASN1X_UTF8_STRING:
case EGG_ASN1X_VISIBLE_STRING:
case EGG_ASN1X_NULL:
tlv = anode_build_value (node);
break;
/* Any should already have explicit tagging, so just return */
case EGG_ASN1X_ANY:
return anode_build_any (node);
case EGG_ASN1X_SEQUENCE:
case EGG_ASN1X_SEQUENCE_OF:
case EGG_ASN1X_SET:
case EGG_ASN1X_SET_OF:
tlv = anode_build_structured (node, want);
break;
case EGG_ASN1X_CHOICE:
tlv = anode_build_choice (node, want);
break;
default:
g_assert_not_reached ();
}
if (tlv == NULL)
return NULL;
/* Now wrap in explicit tag if that's the case */
return anode_build_maybe_explicit (node, tlv, flags);
}
static Atlv *
anode_build_anything (GNode *node,
gboolean want)
{
return anode_build_anything_for_flags (node, want,
anode_def_flags (node));
}
GBytes *
egg_asn1x_encode (GNode *asn,
EggAllocator allocator)
{
GBytes *bytes;
Atlv *tlv;
g_return_val_if_fail (asn != NULL, NULL);
g_return_val_if_fail (anode_def_type_is_real (asn), NULL);
if (!egg_asn1x_validate (asn, TRUE))
return NULL;
tlv = anode_build_anything (asn, TRUE);
/* The above validate should cause build not to return NULL */
g_return_val_if_fail (tlv != NULL, NULL);
atlv_sort_perform (tlv, allocator);
bytes = atlv_unparse_to_bytes (tlv, allocator);
atlv_free (tlv);
return bytes;
}
/* ----------------------------------------------------------------------------
* VALUE READ/WRITE
*/
static int
two_to_four_digit_year (int year)
{
time_t now;
struct tm tm;
int century, current;
g_return_val_if_fail (year >= 0 && year <= 99, -1);
/* Get the current year */
now = time (NULL);
g_return_val_if_fail (now >= 0, -1);
if (!gmtime_r (&now, &tm))
g_return_val_if_reached (-1);
current = (tm.tm_year % 100);
century = (tm.tm_year + 1900) - current;
/*
* Check if it's within 40 years before the
* current date.
*/
if (current < 40) {
if (year < current)
return century + year;
if (year > 100 - (40 - current))
return (century - 100) + year;
} else {
if (year < current && year > (current - 40))
return century + year;
}
/*
* If it's after then adjust for overflows to
* the next century.
*/
if (year < current)
return century + 100 + year;
else
return century + year;
}
static gboolean
parse_utc_time (const gchar *time, gsize n_time,
struct tm* when, gint *offset)
{
const char *p, *e;
int year;
g_assert (when);
g_assert (time);
g_assert (offset);
/* YYMMDDhhmmss.ffff Z | +0000 */
if (n_time < 6 || n_time >= 28)
return FALSE;
/* Reset everything to default legal values */
memset (when, 0, sizeof (*when));
*offset = 0;
when->tm_mday = 1;
/* Select the digits part of it */
p = time;
for (e = p; *e >= '0' && *e <= '9'; ++e);
if (p + 2 <= e) {
year = atoin (p, 2);
p += 2;
/*
* 40 years in the past is our century. 60 years
* in the future is the next century.
*/
when->tm_year = two_to_four_digit_year (year) - 1900;
}
if (p + 2 <= e) {
when->tm_mon = atoin (p, 2) - 1;
p += 2;
}
if (p + 2 <= e) {
when->tm_mday = atoin (p, 2);
p += 2;
}
if (p + 2 <= e) {
when->tm_hour = atoin (p, 2);
p += 2;
}
if (p + 2 <= e) {
when->tm_min = atoin (p, 2);
p += 2;
}
if (p + 2 <= e) {
when->tm_sec = atoin (p, 2);
p += 2;
}
if (when->tm_year < 0 || when->tm_year > 9999 ||
when->tm_mon < 0 || when->tm_mon > 11 ||
when->tm_mday < 1 || when->tm_mday > 31 ||
when->tm_hour < 0 || when->tm_hour > 23 ||
when->tm_min < 0 || when->tm_min > 59 ||
when->tm_sec < 0 || when->tm_sec > 59)
return FALSE;
/* Make sure all that got parsed */
if (p != e)
return FALSE;
/* Now the remaining optional stuff */
e = time + n_time;
/* See if there's a fraction, and discard it if so */
if (p < e && *p == '.' && p + 5 <= e)
p += 5;
/* See if it's UTC */
if (p < e && *p == 'Z') {
p += 1;
/* See if it has a timezone */
} else if ((*p == '-' || *p == '+') && p + 3 <= e) {
int off, neg;
neg = *p == '-';
++p;
off = atoin (p, 2) * 3600;
if (off < 0 || off > 86400)
return -1;
p += 2;
if (p + 2 <= e) {
off += atoin (p, 2) * 60;
p += 2;
}
/* Use TZ offset */
if (neg)
*offset = 0 - off;
else
*offset = off;
}
/* Make sure everything got parsed */
if (p != e)
return FALSE;
return TRUE;
}
static gboolean
parse_general_time (const gchar *time, gsize n_time,
struct tm* when, gint *offset)
{
const char *p, *e;
g_assert (time);
g_assert (when);
g_assert (offset);
/* YYYYMMDDhhmmss.ffff Z | +0000 */
if (n_time < 8 || n_time >= 30)
return FALSE;
/* Reset everything to default legal values */
memset (when, 0, sizeof (*when));
*offset = 0;
when->tm_mday = 1;
/* Select the digits part of it */
p = time;
for (e = p; *e >= '0' && *e <= '9'; ++e);
if (p + 4 <= e) {
when->tm_year = atoin (p, 4) - 1900;
p += 4;
}
if (p + 2 <= e) {
when->tm_mon = atoin (p, 2) - 1;
p += 2;
}
if (p + 2 <= e) {
when->tm_mday = atoin (p, 2);
p += 2;
}
if (p + 2 <= e) {
when->tm_hour = atoin (p, 2);
p += 2;
}
if (p + 2 <= e) {
when->tm_min = atoin (p, 2);
p += 2;
}
if (p + 2 <= e) {
when->tm_sec = atoin (p, 2);
p += 2;
}
if (when->tm_year < 0 || when->tm_year > 9999 ||
when->tm_mon < 0 || when->tm_mon > 11 ||
when->tm_mday < 1 || when->tm_mday > 31 ||
when->tm_hour < 0 || when->tm_hour > 23 ||
when->tm_min < 0 || when->tm_min > 59 ||
when->tm_sec < 0 || when->tm_sec > 59)
return FALSE;
/* Make sure all that got parsed */
if (p != e)
return FALSE;
/* Now the remaining optional stuff */
e = time + n_time;
/* See if there's a fraction, and discard it if so */
if (p < e && *p == '.' && p + 5 <= e)
p += 5;
/* See if it's UTC */
if (p < e && *p == 'Z') {
p += 1;
/* See if it has a timezone */
} else if ((*p == '-' || *p == '+') && p + 3 <= e) {
int off, neg;
neg = *p == '-';
++p;
off = atoin (p, 2) * 3600;
if (off < 0 || off > 86400)
return -1;
p += 2;
if (p + 2 <= e) {
off += atoin (p, 2) * 60;
p += 2;
}
/* Use TZ offset */
if (neg)
*offset = 0 - off;
else
*offset = off;
}
/* Make sure everything got parsed */
if (p != e)
return FALSE;
return TRUE;
}
static gboolean
anode_read_time (GNode *node,
GBytes *data,
struct tm *when,
glong *value)
{
const gchar *buf;
gboolean ret;
gint offset = 0;
gint flags;
gint type;
gsize len;
g_assert (data != NULL);
g_assert (when != NULL);
g_assert (value != NULL);
flags = anode_def_flags (node);
type = anode_def_type (node);
buf = g_bytes_get_data (data, &len);
if (type == EGG_ASN1X_GENERALIZED_TIME)
ret = parse_general_time (buf, len, when, &offset);
else if (type == EGG_ASN1X_UTC_TIME)
ret = parse_utc_time (buf, len, when, &offset);
else if (flags & FLAG_GENERALIZED)
ret = parse_general_time (buf, len, when, &offset);
else if (flags & FLAG_UTC)
ret = parse_utc_time (buf, len, when, &offset);
else
g_return_val_if_reached (FALSE);
if (!ret)
return anode_failure (node, "invalid time content");
/* In order to work with 32 bit time_t. */
if (sizeof (time_t) <= 4 && when->tm_year >= 2038) {
*value = (time_t)2145914603; /* 2037-12-31 23:23:23 */
/* Convert to seconds since epoch */
} else {
*value = timegm (when);
g_return_val_if_fail (*time >= 0, FALSE);
*value += offset;
}
return TRUE;
}
static gboolean
anode_read_integer_ulong (GNode *node,
GBytes *data,
gulong *value)
{
const guchar *p;
gsize len;
gsize k;
p = g_bytes_get_data (data, &len);
if (len < 1 || len > sizeof (gulong))
return FALSE;
*value = 0;
for (k = 0; k < len; ++k)
*value |= p[k] << (8 * ((len - 1) - k));
return TRUE;
}
static void
anode_write_integer_ulong (gulong value,
guchar *data,
gsize *n_data)
{
guchar buf[sizeof (gulong)];
gint bytes, i, off;
guchar *at;
gboolean sign;
gsize len;
for (i = 0; i < sizeof (gulong); ++i) {
off = sizeof (gulong) - (i + 1);
buf[i] = (value >> (off * 8)) & 0xFF;
}
for (bytes = sizeof (gulong) - 1; bytes >= 0; --bytes)
if (!buf[bytes])
break;
bytes = sizeof (gulong) - (bytes + 1);
if (bytes == 0)
bytes = 1;
/* If the first byte would make this negative, then add a zero */
at = buf + (sizeof (gulong) - bytes);
sign = !!(at[0] & 0x80);
len = bytes + (sign ? 1 : 0);
if (data) {
g_assert (*n_data >= len);
if (sign) {
data[0] = 0;
data++;
}
memcpy (data, at, bytes);
}
*n_data = len;
}
static GBytes *
anode_default_integer (GNode *node)
{
const gchar *defval;
EggAsn1xDef *opt;
gchar *end;
gulong value;
guchar *data;
gsize len;
if (!(anode_def_flags (node) & FLAG_DEFAULT))
return NULL;
/* Try to get a default */
opt = anode_opt_lookup (node, EGG_ASN1X_DEFAULT, NULL);
g_return_val_if_fail (opt != NULL, NULL);
g_return_val_if_fail (opt->value != NULL, NULL);
defval = opt->value;
opt = anode_opt_lookup (node, EGG_ASN1X_CONSTANT, defval);
if (opt != NULL) {
g_return_val_if_fail (opt->value != NULL, NULL);
defval = opt->value;
}
/* Parse out the default value */
value = strtoul (defval, &end, 10);
g_return_val_if_fail (end && !end[0], NULL);
anode_write_integer_ulong (value, NULL, &len);
data = g_malloc (len);
anode_write_integer_ulong (value, data, &len);
return g_bytes_new_take (data, len);
}
static gboolean
anode_read_string_struct (GNode *node,
Atlv *tlv,
gpointer value,
gsize *n_value)
{
const guchar *buf;
gsize len;
Atlv *ctlv;
guchar *at;
gint remaining;
g_assert (tlv != NULL);
g_assert (tlv->cls & ASN1_CLASS_STRUCTURED);
g_assert (n_value != NULL);
at = value;
remaining = *n_value;
*n_value = 0;
for (ctlv = tlv->child; ctlv != NULL; ctlv = ctlv->next) {
if (ctlv->cls & ASN1_CLASS_STRUCTURED ||
ctlv->value == NULL)
return FALSE;
buf = g_bytes_get_data (ctlv->value, &len);
*n_value += len;
if (value) {
if (remaining >= len)
memcpy (at, buf, len);
at += len;
remaining -= len;
}
}
if (value)
g_return_val_if_fail (remaining >= 0, FALSE);
return TRUE;
}
static gboolean
anode_read_string_simple (GNode *node,
GBytes *data,
gpointer value,
gsize *n_value)
{
const guchar *buf;
gsize len;
g_assert (data != NULL);
g_assert (n_value != NULL);
buf = g_bytes_get_data (data, &len);
if (value) {
g_return_val_if_fail (*n_value >= len, FALSE);
memcpy (value, buf, len);
}
*n_value = len;
return TRUE;
}
static gboolean
anode_read_boolean (GNode *node,
GBytes *data,
gboolean *value)
{
const guchar *buf;
gsize len;
g_assert (node != NULL);
g_assert (data != NULL);
g_assert (value != NULL);
buf = g_bytes_get_data (data, &len);
g_return_val_if_fail (len == 1, FALSE);
if (buf[0] == 0x00)
*value = FALSE;
else if (buf[0] == 0xFF)
*value = TRUE;
else
g_return_val_if_reached (FALSE);
return TRUE;
}
static void
anode_write_boolean (gboolean value,
guchar *data,
gsize *n_data)
{
if (data) {
g_assert (*n_data >= 1);
if (value)
data[0] = 0xFF;
else
data[0] = 0x00;
}
*n_data = 1;
}
static GBytes *
anode_default_boolean (GNode *node)
{
EggAsn1xDef *opt;
gboolean value;
guchar *data;
gsize len;
if (!(anode_def_flags (node) & FLAG_DEFAULT))
return NULL;
/* Try to get a default */
opt = anode_opt_lookup (node, EGG_ASN1X_DEFAULT, NULL);
g_return_val_if_fail (opt != NULL, NULL);
/* Parse out the default value */
if ((opt->type & FLAG_TRUE) == FLAG_TRUE)
value = TRUE;
else if ((opt->type & FLAG_FALSE) == FLAG_FALSE)
value = FALSE;
else
g_return_val_if_reached (FALSE);
anode_write_boolean (value, NULL, &len);
data = g_malloc (len);
anode_write_boolean (value, data, &len);
return g_bytes_new_take (data, len);
}
static gboolean
anode_read_object_id (GNode *node,
GBytes *data,
gchar **oid)
{
GString *result = NULL;
const guchar *p;
gboolean lead;
guint val, pval;
gsize len;
gint k;
g_assert (data != NULL);
p = g_bytes_get_data (data, &len);
if (oid)
result = g_string_sized_new (32);
pval = p[0] / 40;
val = p[0] - pval * 40;
if (result)
g_string_append_printf (result, "%u.%u", pval, val);
/* TODO: Validate first byte? */
for (k = 1, lead = 1, val = 0, pval = 0; k < len; ++k) {
/* X.690: the leading byte must never be 0x80 */
if (lead && p[k] == 0x80) {
anode_failure (node, "object id encoding is invalid");
break;
}
val = val << 7;
val |= p[k] & 0x7F;
/* Check for wrap around */
if (val < pval) {
anode_failure (node, "object id encoding is invalid");
break;
}
pval = val;
if (!(p[k] & 0x80)) {
if (result)
g_string_append_printf (result, ".%u", val);
pval = val = 0;
lead = 1;
}
}
if (k < len) {
if (result)
g_string_free (result, TRUE); /* UNREACHABLE: caught by validation */
return FALSE;
}
if (result)
*oid = g_string_free (result, FALSE);
return TRUE;
}
static gboolean
anode_write_object_id (const gchar *oid,
guchar *data,
gsize *n_data)
{
const gchar *p, *next;
gint num, num1;
guchar bit7;
gboolean had;
gint i, k, at;
at = 0;
num1 = 0;
for (i = 0; oid[0]; ++i, oid = next) {
p = strchr (oid, '.');
if (p == NULL)
next = p = oid + strlen (oid);
else
next = p + 1;
if (p == oid)
return FALSE;
num = atoin (oid, p - oid);
if (num < 0)
return FALSE;
if (i == 0) {
num1 = num;
} else if (i == 1) {
if (data) {
g_assert (*n_data > at);
data[at] = 40 * num1 + num;
}
++at;
} else {
for (had = FALSE, k = 4; k >= 0; k--) {
bit7 = (num >> (k * 7)) & 0x7F;
if (bit7 || had || !k) {
if (k)
bit7 |= 0x80;
if (data) {
g_assert (*n_data > at);
data[at] = bit7;
}
++at;
had = 1;
}
}
}
}
if (at < 2)
return FALSE;
if (data)
g_assert (*n_data >= at);
*n_data = at;
return TRUE;
}
/* -----------------------------------------------------------------------------------
* GETTING, SETTING
*/
GNode*
egg_asn1x_node (GNode *asn, ...)
{
GNode *node = asn;
const gchar *name;
va_list va;
gint type;
gint index;
g_return_val_if_fail (asn, NULL);
va_start (va, asn);
for (;;) {
type = anode_def_type (node);
/* Use integer indexes for these */
if (type == EGG_ASN1X_SEQUENCE_OF || type == EGG_ASN1X_SET_OF) {
index = va_arg (va, gint);
if (index == 0)
return node;
/* Only consider nodes that have data */
node = g_node_nth_child (node, 0);
while (node) {
if (egg_asn1x_have (node)) {
--index;
if (index == 0)
break;
}
node = g_node_next_sibling (node);
}
if (node == NULL)
return NULL;
/* Use strings for these */
} else {
name = va_arg (va, const gchar*);
if (name == NULL)
return node;
/* Warn if they're using indexes here */
if (name <= (const gchar*)4096) {
g_warning ("possible misuse of egg_asn1x_node, expected a string, but got an index");
return NULL;
}
node = anode_child_with_name (node, name);
if (node == NULL)
return NULL;
}
}
}
const gchar*
egg_asn1x_name (GNode *node)
{
g_return_val_if_fail (node != NULL, NULL);
return anode_def_name (node);
}
EggAsn1xType
egg_asn1x_type (GNode *node)
{
g_return_val_if_fail (node != NULL, 0);
return anode_def_type (node);
}
guint
egg_asn1x_count (GNode *node)
{
guint result = 0;
GNode *child;
gint type;
g_return_val_if_fail (node, 0);
type = anode_def_type (node);
if (type != EGG_ASN1X_SEQUENCE_OF && type != EGG_ASN1X_SET_OF) {
g_warning ("node passed to egg_asn1x_count was not a sequence of or set of");
return 0;
}
for (child = node->children; child; child = child->next) {
if (egg_asn1x_have (child))
++result;
}
return result;
}
GNode*
egg_asn1x_append (GNode *node)
{
GNode *child;
gint type;
g_return_val_if_fail (node, NULL);
type = anode_def_type (node);
if (type != EGG_ASN1X_SEQUENCE_OF && type != EGG_ASN1X_SET_OF) {
g_warning ("node passed to egg_asn1x_append was not a sequence of or set of");
return NULL;
}
/* There must be at least one child */
child = node->children;
g_return_val_if_fail (child, NULL);
child = anode_clone (child);
anode_clear (child);
g_node_append (node, child);
return child;
}
gboolean
egg_asn1x_have (GNode *node)
{
GNode *child;
g_return_val_if_fail (node, FALSE);
if (anode_get_value (node) || anode_get_parsed (node))
return TRUE;
for (child = node->children; child != NULL; child = child->next) {
if (egg_asn1x_have (child))
return TRUE;
}
return FALSE;
}
gboolean
egg_asn1x_get_boolean (GNode *node,
gboolean *value)
{
gboolean ret;
GBytes *data;
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (value != NULL, FALSE);
g_return_val_if_fail (anode_def_type (node) == EGG_ASN1X_BOOLEAN, FALSE);
data = anode_get_value (node);
if (data == NULL)
data = anode_default_boolean (node);
else
g_bytes_ref (data);
if (data == NULL)
return FALSE;
ret = anode_read_boolean (node, data, value);
g_bytes_unref (data);
return ret;
}
void
egg_asn1x_set_boolean (GNode *node, gboolean value)
{
GBytes *data, *def;
guchar *buf;
gsize len;
g_return_if_fail (node != NULL);
g_return_if_fail (anode_def_type (node) == EGG_ASN1X_BOOLEAN);
len = 1;
buf = g_malloc0 (1);
anode_write_boolean (value, buf, &len);
data = g_bytes_new_take (buf, len);
/* If it's equal to default, then clear */
def = anode_default_boolean (node);
if (def) {
if (g_bytes_equal (def, data)) {
anode_clr_value (node);
g_bytes_unref (data);
data = NULL;
}
g_bytes_unref (def);
}
if (data != NULL)
anode_take_value (node, data);
}
void
egg_asn1x_set_null (GNode *node)
{
g_return_if_fail (node != NULL);
g_return_if_fail (anode_def_type (node) == EGG_ASN1X_NULL);
/* Encode zero characters */
anode_clr_value (node);
anode_set_value (node, g_bytes_new_static ("", 0));
}
GQuark
egg_asn1x_get_enumerated (GNode *node)
{
gchar buf[sizeof (gulong) * 3];
EggAsn1xDef *opt;
gulong val;
GBytes *data;
g_return_val_if_fail (node != NULL, 0);
g_return_val_if_fail (anode_def_type (node) == EGG_ASN1X_ENUMERATED, 0);
data = anode_get_value (node);
if (data == NULL)
return 0;
if (!anode_read_integer_ulong (node, data, &val))
g_return_val_if_reached (0);
/* Format that as a string */
if (g_snprintf (buf, sizeof (buf), "%lu", val) < 0)
g_return_val_if_reached (0);
/* Lookup that value in our table */
opt = anode_opt_lookup_value (node, EGG_ASN1X_CONSTANT, buf);
if (opt == NULL || opt->name == NULL)
return 0;
return g_quark_from_static_string (opt->name);
}
void
egg_asn1x_set_enumerated (GNode *node,
GQuark value)
{
EggAsn1xDef *opt;
const gchar *name;
gpointer data;
gsize n_data;
gulong val;
g_return_if_fail (node != NULL);
g_return_if_fail (value != 0);
g_return_if_fail (anode_def_type (node) == EGG_ASN1X_ENUMERATED);
name = g_quark_to_string (value);
g_return_if_fail (name != NULL);
opt = anode_opt_lookup (node, EGG_ASN1X_CONSTANT, name);
g_return_if_fail (opt && opt->value);
/* TODO: Signed values */
val = anode_def_value_as_ulong (opt);
g_return_if_fail (val != G_MAXULONG);
n_data = sizeof (gulong) + 1;
data = g_malloc0 (n_data);
anode_write_integer_ulong (val, data, &n_data);
anode_clr_value (node);
anode_set_value (node, g_bytes_new_take (data, n_data));
}
gboolean
egg_asn1x_get_integer_as_ulong (GNode *node,
gulong *value)
{
gboolean ret;
GBytes *data;
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (value != NULL, FALSE);
g_return_val_if_fail (anode_def_type (node) == EGG_ASN1X_INTEGER, FALSE);
data = anode_get_value (node);
if (data == NULL)
data = anode_default_integer (node);
else
g_bytes_ref (data);
if (data == NULL)
return FALSE;
ret = anode_read_integer_ulong (node, data, value);
g_bytes_unref (data);
return ret;
}
void
egg_asn1x_set_integer_as_ulong (GNode *node,
gulong value)
{
GBytes *data, *def;
guchar *buf;
gsize len;
g_return_if_fail (node != NULL);
g_return_if_fail (anode_def_type (node) == EGG_ASN1X_INTEGER);
len = sizeof (gulong) + 1;
buf = g_malloc0 (len);
anode_write_integer_ulong (value, buf, &len);
data = g_bytes_new_take (buf, len);
/* If it's equal to default, then clear */
def = anode_default_integer (node);
if (def) {
if (g_bytes_equal (def, data)) {
anode_clr_value (node);
g_bytes_unref (data);
data = NULL;
}
g_bytes_unref (def);
}
if (data != NULL)
anode_take_value (node, data);
}
GBytes *
egg_asn1x_get_integer_as_raw (GNode *node)
{
Anode *an;
GBytes *raw;
g_return_val_if_fail (node != NULL, NULL);
g_return_val_if_fail (anode_def_type (node) == EGG_ASN1X_INTEGER, NULL);
an = node->data;
if (an->guarantee_unsigned) {
g_warning ("cannot read integer set with " /* UNREACHABLE: */
"egg_asn1x_set_integer_as_raw() " /* UNREACHABLE: */
"via egg_asn1x_get_integer_as_raw()"); /* UNREACHABLE: */
return NULL; /* UNREACHABLE: unreachable by coverage testing */
}
raw = anode_get_value (node);
if (raw != NULL)
g_bytes_ref (raw);
return raw;
}
GBytes *
egg_asn1x_get_integer_as_usg (GNode *node)
{
const guchar *p;
Anode *an;
gboolean sign;
gsize len;
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (anode_def_type (node) == EGG_ASN1X_INTEGER, FALSE);
an = node->data;
if (an->value == NULL)
return NULL;
p = g_bytes_get_data (an->value, &len);
if (!an->guarantee_unsigned) {
sign = !!(p[0] & 0x80);
if (sign) {
g_warning ("invalid two's complement integer"); /* UNREACHABLE: */
return NULL; /* UNREACHABLE: by coverage testing */
}
/* Strip off the extra zero byte that was preventing it from being negative */
if (p[0] == 0 && len > 1) {
sign = !!(p[1] & 0x80);
if (sign) {
p++;
len--;
}
}
}
return g_bytes_new_with_free_func (p, len,
(GDestroyNotify)g_bytes_unref,
g_bytes_ref (an->value));
}
void
egg_asn1x_set_integer_as_raw (GNode *node,
GBytes *value)
{
g_return_if_fail (value != NULL);
egg_asn1x_take_integer_as_raw (node, g_bytes_ref (value));
}
void
egg_asn1x_take_integer_as_raw (GNode *node,
GBytes *value)
{
gboolean sign;
const guchar *p;
Anode *an;
g_return_if_fail (node != NULL);
g_return_if_fail (value != NULL);
g_return_if_fail (anode_def_type (node) == EGG_ASN1X_INTEGER);
/* Make sure the integer is properly encoded in twos complement*/
p = g_bytes_get_data (value, NULL);
g_return_if_fail (p != NULL);
sign = !!(p[0] & 0x80);
if (sign) {
g_warning ("integer is not two's complement"); /* UNREACHABLE: */
return; /* UNREACHABLE: unless warning */
}
anode_clr_value (node);
anode_set_value (node, value);
an = node->data;
an->guarantee_unsigned = 0;
}
void
egg_asn1x_set_integer_as_usg (GNode *node,
GBytes *value)
{
g_return_if_fail (value != NULL);
egg_asn1x_take_integer_as_usg (node, g_bytes_ref (value));
}
void
egg_asn1x_take_integer_as_usg (GNode *node,
GBytes *value)
{
Anode *an;
g_return_if_fail (node != NULL);
g_return_if_fail (value != NULL);
g_return_if_fail (anode_def_type (node) == EGG_ASN1X_INTEGER);
anode_set_value (node, value);
an = node->data;
an->guarantee_unsigned = 1;
}
GNode *
egg_asn1x_get_any_as (GNode *node,
const EggAsn1xDef *defs,
const gchar *type)
{
g_return_val_if_fail (node != NULL, NULL);
g_return_val_if_fail (type != NULL, NULL);
g_return_val_if_fail (egg_asn1x_type (node) == EGG_ASN1X_ANY, NULL);
return egg_asn1x_get_any_as_full (node, defs, type, 0);
}
GNode *
egg_asn1x_get_any_as_full (GNode *node,
const EggAsn1xDef *defs,
const gchar *type,
gint options)
{
GNode *asn;
g_return_val_if_fail (node != NULL, NULL);
g_return_val_if_fail (type != NULL, NULL);
g_return_val_if_fail (egg_asn1x_type (node) == EGG_ASN1X_ANY, NULL);
asn = egg_asn1x_create (defs, type);
g_return_val_if_fail (asn != NULL, NULL);
if (!egg_asn1x_get_any_into_full (node, asn, options)) {
egg_asn1x_destroy (asn);
return NULL;
}
return asn;
}
gboolean
egg_asn1x_get_any_into (GNode *node,
GNode *into)
{
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (into != NULL, FALSE);
g_return_val_if_fail (egg_asn1x_type (node) == EGG_ASN1X_ANY, FALSE);
return egg_asn1x_get_any_into_full (node, into, 0);
}
gboolean
egg_asn1x_get_any_into_full (GNode *node,
GNode *into,
gint options)
{
Atlv *tlv;
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (into != NULL, FALSE);
g_return_val_if_fail (egg_asn1x_type (node) == EGG_ASN1X_ANY, FALSE);
tlv = anode_get_parsed (node);
if (tlv == NULL)
return FALSE;
/* If this node is explicit, then just get the contents */
if (anode_calc_explicit_for_flags (node, anode_def_flags (node), NULL)) {
tlv = tlv->child;
g_return_val_if_fail (tlv != NULL, FALSE);
}
if (!anode_decode_anything (into, tlv))
return FALSE;
return egg_asn1x_validate (into, !(options & EGG_ASN1X_NO_STRICT));
}
void
egg_asn1x_set_any_from (GNode *node,
GNode *from)
{
Anode *an;
Atlv *tlv;
g_return_if_fail (node != NULL);
g_return_if_fail (from != NULL);
g_return_if_fail (egg_asn1x_type (node) == EGG_ASN1X_ANY);
tlv = anode_build_anything (from, TRUE);
g_return_if_fail (tlv != NULL);
/* Wrap this in an explicit tag if necessary */
tlv = anode_build_maybe_explicit (node, tlv, anode_def_flags (node));
/* Mark down the tlvs for this node */
an = node->data;
atlv_free (an->parsed);
an->parsed = tlv;
}
GBytes *
egg_asn1x_get_any_raw (GNode *node,
EggAllocator allocator)
{
GBytes *bytes;
Atlv *tlv;
g_return_val_if_fail (node != NULL, NULL);
tlv = anode_build_anything (node, TRUE);
if (tlv == NULL) {
anode_failure (node, "missing value(s)");
return NULL;
}
atlv_sort_perform (tlv, allocator);
bytes = atlv_unparse_to_bytes (tlv, allocator);
atlv_free (tlv);
return bytes;
}
gboolean
egg_asn1x_set_any_raw (GNode *node,
GBytes *raw)
{
const gchar *msg;
Anode *an;
Atlv *tlv;
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (raw != NULL, FALSE);
an = node->data;
tlv = atlv_new ();
msg = atlv_parse_der (raw, tlv);
if (msg == NULL) {
/* Wrap this in an explicit tag if necessary */
tlv = anode_build_maybe_explicit (node, tlv, anode_def_flags (node));
atlv_free (an->parsed);
an->parsed = tlv;
return TRUE;
/* A failure, set the message manually so it doesn't get a prefix */
} else {
an = node->data;
g_free (an->failure);
an->failure = g_strdup (msg);
return FALSE;
}
}
GBytes *
egg_asn1x_get_element_raw (GNode *node)
{
Anode *an;
Atlv *tlv;
g_return_val_if_fail (node != NULL, NULL);
an = node->data;
tlv = an->parsed;
/* If this node is explicit, then just get the contents */
if (tlv && anode_calc_explicit_for_flags (node, anode_def_flags (node), NULL))
tlv = tlv->child;
if (!tlv || !tlv->decoded)
return NULL;
return g_bytes_ref (tlv->decoded);
}
GBytes *
egg_asn1x_get_value_raw (GNode *node)
{
GBytes *raw;
g_return_val_if_fail (node != NULL, NULL);
raw = anode_get_value (node);
if (raw != NULL)
g_bytes_ref (raw);
return raw;
}
guchar *
egg_asn1x_get_string_as_raw (GNode *node,
EggAllocator allocator,
gsize *n_string)
{
gsize length;
guchar *string;
GBytes *data;
Atlv *tlv;
gint type;
g_return_val_if_fail (node, NULL);
g_return_val_if_fail (n_string, NULL);
if (!allocator)
allocator = g_realloc;
type = anode_def_type (node);
g_return_val_if_fail (type == EGG_ASN1X_OCTET_STRING ||
type == EGG_ASN1X_GENERAL_STRING ||
type == EGG_ASN1X_NUMERIC_STRING ||
type == EGG_ASN1X_IA5_STRING ||
type == EGG_ASN1X_TELETEX_STRING ||
type == EGG_ASN1X_PRINTABLE_STRING ||
type == EGG_ASN1X_UNIVERSAL_STRING ||
type == EGG_ASN1X_BMP_STRING ||
type == EGG_ASN1X_UTF8_STRING ||
type == EGG_ASN1X_VISIBLE_STRING, NULL);
data = anode_get_value (node);
if (data != NULL) {
if (!anode_read_string_simple (node, data, NULL, &length))
g_return_val_if_reached (NULL);
string = (allocator) (NULL, length + 1);
if (string == NULL)
return NULL; /* UNREACHABLE: unless odd allocator */
if (!anode_read_string_simple (node, data, string, &length))
g_return_val_if_reached (NULL);
/* Courtesy null termination, string must however be validated! */
string[length] = 0;
*n_string = length;
return string;
}
tlv = anode_get_parsed (node);
if (tlv != NULL) {
if (!anode_read_string_struct (node, tlv, NULL, &length))
return NULL;
string = (allocator) (NULL, length + 1);
if (string == NULL)
return NULL; /* UNREACHABLE: unless odd allocator */
if (!anode_read_string_struct (node, tlv, string, &length))
g_return_val_if_reached (NULL); /* should have failed above */
/* Courtesy null termination, string must however be validated! */
string[length] = 0;
*n_string = length;
return string;
}
return NULL;
}
void
egg_asn1x_set_string_as_raw (GNode *node,
guchar *data,
gsize n_data,
GDestroyNotify destroy)
{
gint type;
g_return_if_fail (node != NULL);
g_return_if_fail (data != NULL);
type = anode_def_type (node);
g_return_if_fail (type == EGG_ASN1X_OCTET_STRING ||
type == EGG_ASN1X_GENERAL_STRING ||
type == EGG_ASN1X_NUMERIC_STRING ||
type == EGG_ASN1X_IA5_STRING ||
type == EGG_ASN1X_TELETEX_STRING ||
type == EGG_ASN1X_PRINTABLE_STRING ||
type == EGG_ASN1X_UNIVERSAL_STRING ||
type == EGG_ASN1X_BMP_STRING ||
type == EGG_ASN1X_UTF8_STRING ||
type == EGG_ASN1X_VISIBLE_STRING);
anode_set_value (node, g_bytes_new_with_free_func (data, n_data,
destroy, data));
}
void
egg_asn1x_set_string_as_bytes (GNode *node,
GBytes *bytes)
{
gint type;
g_return_if_fail (node != NULL);
g_return_if_fail (bytes != NULL);
type = anode_def_type (node);
g_return_if_fail (type == EGG_ASN1X_OCTET_STRING ||
type == EGG_ASN1X_GENERAL_STRING ||
type == EGG_ASN1X_NUMERIC_STRING ||
type == EGG_ASN1X_IA5_STRING ||
type == EGG_ASN1X_TELETEX_STRING ||
type == EGG_ASN1X_PRINTABLE_STRING ||
type == EGG_ASN1X_UNIVERSAL_STRING ||
type == EGG_ASN1X_BMP_STRING ||
type == EGG_ASN1X_UTF8_STRING ||
type == EGG_ASN1X_VISIBLE_STRING);
anode_set_value (node, g_bytes_ref (bytes));
}
GBytes *
egg_asn1x_get_string_as_bytes (GNode *node)
{
gpointer raw;
gsize length;
g_return_val_if_fail (node != NULL, NULL);
raw = egg_asn1x_get_string_as_raw (node, NULL, &length);
if (raw == NULL)
return NULL;
return g_bytes_new_take (raw, length);
}
gchar *
egg_asn1x_get_bmpstring_as_utf8 (GNode *node)
{
gchar *string;
gsize n_string;
gchar *utf8;
g_return_val_if_fail (node, NULL);
string = (gchar*)egg_asn1x_get_string_as_raw (node, NULL, &n_string);
if (!string)
return NULL;
utf8 = g_convert (string, n_string, "UTF-8", "UTF-16BE", NULL, NULL, NULL);
g_free (string);
return utf8;
}
gchar*
egg_asn1x_get_string_as_utf8 (GNode *node,
EggAllocator allocator)
{
gchar *string;
gsize n_string;
g_return_val_if_fail (node, NULL);
if (allocator == NULL)
allocator = g_realloc;
string = (gchar*)egg_asn1x_get_string_as_raw (node, allocator, &n_string);
if (!string)
return NULL;
if (!g_utf8_validate (string, n_string, NULL)) {
(allocator) (string, 0);
return NULL;
}
return string;
}
gboolean
egg_asn1x_set_string_as_utf8 (GNode *node,
gchar *data,
GDestroyNotify destroy)
{
gsize n_data;
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (data != NULL, FALSE);
n_data = strlen (data);
if (!g_utf8_validate (data, n_data, NULL))
return FALSE;
egg_asn1x_set_string_as_raw (node, (guchar*)data, n_data, destroy);
return TRUE;
}
GBytes *
egg_asn1x_get_bits_as_raw (GNode *node,
guint *n_bits)
{
gsize len;
GBytes *data;
Anode *an;
g_return_val_if_fail (node != NULL, NULL);
g_return_val_if_fail (n_bits != NULL, NULL);
g_return_val_if_fail (anode_def_type (node) == EGG_ASN1X_BIT_STRING, NULL);
data = anode_get_value (node);
if (data == NULL)
return NULL;
len = g_bytes_get_size (data);
an = node->data;
*n_bits = (len * 8) - an->bits_empty;
return g_bytes_ref (data);
}
void
egg_asn1x_set_bits_as_raw (GNode *node,
GBytes *value,
guint n_bits)
{
g_return_if_fail (node != NULL);
g_return_if_fail (value != NULL);
egg_asn1x_take_bits_as_raw (node, g_bytes_ref (value), n_bits);
}
void
egg_asn1x_take_bits_as_raw (GNode *node,
GBytes *value,
guint n_bits)
{
Anode *an;
gint type;
gsize len;
guchar empty;
g_return_if_fail (node != NULL);
g_return_if_fail (value != NULL);
type = anode_def_type (node);
g_return_if_fail (type == EGG_ASN1X_BIT_STRING);
len = (n_bits / 8);
if (n_bits % 8)
len += 1;
empty = n_bits % 8;
if (empty > 0)
empty = 8 - empty;
anode_take_value (node, value);
an = node->data;
an->bits_empty = empty;
}
gboolean
egg_asn1x_get_bits_as_ulong (GNode *node,
gulong *bits,
guint *n_bits)
{
GBytes *data;
const guchar *buf;
gsize len;
guint i, length;
guchar empty;
const guchar *p;
gulong value;
Anode *an;
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (bits != NULL, FALSE);
g_return_val_if_fail (n_bits != NULL, FALSE);
g_return_val_if_fail (anode_def_type (node) == EGG_ASN1X_BIT_STRING, FALSE);
data = anode_get_value (node);
if (data == NULL)
return FALSE;
buf = g_bytes_get_data (data, &len);
an = node->data;
empty = an->bits_empty;
length = (len * 8) - empty;
if (length > sizeof (gulong) * 8)
return FALSE;
value = 0;
p = buf;
for (i = 0; i < len; ++i)
value = value << 8 | p[i];
*bits = value >> empty;
*n_bits = length;
return TRUE;
}
void
egg_asn1x_set_bits_as_ulong (GNode *node,
gulong bits,
guint n_bits)
{
guchar *data;
gulong value;
gsize i, len;
guchar empty;
Anode *an;
gint type;
g_return_if_fail (node != NULL);
g_return_if_fail (n_bits <= sizeof (gulong) * 8);
type = anode_def_type (node);
g_return_if_fail (type == EGG_ASN1X_BIT_STRING);
empty = n_bits % 8;
if (empty > 0)
empty = 8 - empty;
len = (n_bits / 8) + (empty ? 1 : 0);
data = g_malloc0 (sizeof (gulong));
value = bits << empty;
for (i = 0; i < len; ++i)
data[len - i - 1] = (value >> i * 8) & 0xFF;
an = node->data;
an->bits_empty = empty;
anode_take_value (node, g_bytes_new_take (data, len));
}
glong
egg_asn1x_get_time_as_long (GNode *node)
{
struct tm when;
GBytes *data;
glong time;
gint type;
g_return_val_if_fail (node, -1);
type = anode_def_type (node);
/* Time is often represented as a choice, so work than in here */
if (type == EGG_ASN1X_CHOICE) {
node = egg_asn1x_get_choice (node);
if (node == NULL)
return -1;
g_return_val_if_fail (anode_def_type (node) == EGG_ASN1X_TIME ||
anode_def_type (node) == EGG_ASN1X_UTC_TIME ||
anode_def_type (node) == EGG_ASN1X_GENERALIZED_TIME, -1);
return egg_asn1x_get_time_as_long (node);
}
g_return_val_if_fail (type == EGG_ASN1X_TIME ||
type == EGG_ASN1X_UTC_TIME ||
type == EGG_ASN1X_GENERALIZED_TIME, -1);
data = anode_get_value (node);
if (data == NULL)
return -1;
if (!anode_read_time (node, data, &when, &time))
g_return_val_if_reached (-1); /* already validated */
return time;
}
gboolean
egg_asn1x_get_time_as_date (GNode *node,
GDate *date)
{
struct tm when;
GBytes *data;
glong time;
gint type;
g_return_val_if_fail (node, FALSE);
type = anode_def_type (node);
/* Time is often represented as a choice, so work than in here */
if (type == EGG_ASN1X_CHOICE) {
node = egg_asn1x_get_choice (node);
if (node == NULL)
return FALSE;
g_return_val_if_fail (anode_def_type (node) == EGG_ASN1X_TIME ||
anode_def_type (node) == EGG_ASN1X_UTC_TIME ||
anode_def_type (node) == EGG_ASN1X_GENERALIZED_TIME, FALSE);
return egg_asn1x_get_time_as_date (node, date);
}
g_return_val_if_fail (type == EGG_ASN1X_TIME ||
type == EGG_ASN1X_UTC_TIME ||
type == EGG_ASN1X_GENERALIZED_TIME, FALSE);
data = anode_get_value (node);
if (data == NULL)
return FALSE;
if (!anode_read_time (node, data, &when, &time))
g_return_val_if_reached (FALSE); /* already validated */
g_date_set_dmy (date, when.tm_mday, when.tm_mon + 1, when.tm_year + 1900);
return TRUE;
}
gchar*
egg_asn1x_get_oid_as_string (GNode *node)
{
GBytes *data;
gchar *oid;
g_return_val_if_fail (node, NULL);
g_return_val_if_fail (anode_def_type (node) == EGG_ASN1X_OBJECT_ID, NULL);
data = anode_get_value (node);
if (data == NULL)
return NULL;
if (!anode_read_object_id (node, data, &oid))
g_return_val_if_reached (NULL); /* should have been validated */
return oid;
}
gboolean
egg_asn1x_set_oid_as_string (GNode *node,
const gchar *oid)
{
guchar *data;
gsize n_data;
g_return_val_if_fail (oid != NULL, FALSE);
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (anode_def_type (node) == EGG_ASN1X_OBJECT_ID, FALSE);
/* Encoding will always be shorter than string */
n_data = strlen (oid);
data = g_malloc0 (n_data);
if (!anode_write_object_id (oid, data, &n_data)) {
g_free (data);
return FALSE;
}
anode_take_value (node, g_bytes_new_take (data, n_data));
return TRUE;
}
GQuark
egg_asn1x_get_oid_as_quark (GNode *node)
{
GQuark quark;
gchar *oid;
oid = egg_asn1x_get_oid_as_string (node);
if (!oid)
return 0;
quark = g_quark_from_string (oid);
g_free (oid);
return quark;
}
gboolean
egg_asn1x_set_oid_as_quark (GNode *node,
GQuark oid)
{
const gchar *str;
g_return_val_if_fail (oid != 0, FALSE);
str = g_quark_to_string (oid);
g_return_val_if_fail (str != NULL, FALSE);
return egg_asn1x_set_oid_as_string (node, str);
}
GNode*
egg_asn1x_get_choice (GNode *node)
{
GNode *child;
Anode *an;
g_return_val_if_fail (node, NULL);
/* One and only one of the children must be set */
for (child = node->children; child; child = child->next) {
an = (Anode*)child->data;
if (an->chosen)
return child;
}
return NULL;
}
gboolean
egg_asn1x_set_choice (GNode *node,
GNode *choice)
{
GNode *child;
Anode *an;
g_return_val_if_fail (node != NULL, FALSE);
g_return_val_if_fail (anode_def_type (node) == EGG_ASN1X_CHOICE, FALSE);
/* One and only one of the children must be set */
for (child = node->children; child; child = child->next) {
an = (Anode*)child->data;
if (child == choice) {
an->chosen = 1;
choice = NULL;
} else {
an->chosen = 0;
}
}
/* The choice is not one of the child nodes */
g_return_val_if_fail (!choice, FALSE);
return TRUE;
}
/* -----------------------------------------------------------------------------------
* VALIDATION
*/
static gboolean
anode_parse_size (GNode *node,
const gchar *text,
gulong *value)
{
EggAsn1xDef *def;
gchar *end = NULL;
if (text == NULL) {
*value = 0;
return FALSE;
} else if (g_str_equal (text, "MAX")) {
*value = G_MAXULONG;
return TRUE;
} else if (g_ascii_isalpha (text[0])) {
def = anode_opt_lookup (node, EGG_ASN1X_INTEGER, text);
g_return_val_if_fail (def, FALSE);
return anode_parse_size (node, def->value, value);
}
*value = strtoul (text, &end, 10);
g_return_val_if_fail (end && !end[0], FALSE);
return TRUE;
}
static gboolean
anode_validate_size (GNode *node,
gulong length)
{
EggAsn1xDef *size;
gulong value1 = 0;
gulong value2 = G_MAXULONG;
if (anode_def_flags (node) & FLAG_SIZE) {
size = anode_opt_lookup (node, EGG_ASN1X_SIZE, NULL);
g_return_val_if_fail (size, FALSE);
if (!anode_parse_size (node, size->value, &value1))
g_return_val_if_reached (FALSE);
if (size->type & FLAG_MIN_MAX) {
if (!anode_parse_size (node, size->name, &value2))
g_return_val_if_reached (FALSE);
if (length < value1 || length >= value2)
return anode_failure (node, "content size is out of bounds");
} else {
if (length != value1)
return anode_failure (node, "content size is not correct");
}
}
return TRUE;
}
static gboolean
anode_validate_integer (GNode *node,
GBytes *value)
{
GList *constants, *l;
gulong val, check;
gsize len;
gboolean found;
gint flags;
g_assert (value != NULL);
len = g_bytes_get_size (value);
/* Integers must be at least one byte long */
if (len == 0)
return anode_failure (node, "zero length integer");
flags = anode_def_flags (node);
if (flags & FLAG_LIST) {
/* Parse out the value, we only support small integers*/
if (!anode_read_integer_ulong (node, value, &val))
return anode_failure (node, "integer not part of list");
/* Look through the list of constants */
found = FALSE;
constants = anode_opts_lookup (node, EGG_ASN1X_CONSTANT, NULL);
for (l = constants; l; l = g_list_next (l)) {
check = anode_def_value_as_ulong (l->data);
g_return_val_if_fail (check != G_MAXULONG, FALSE);
if (check == val) {
found = TRUE;
break;
}
}
g_list_free (constants);
if (!found)
return anode_failure (node, "integer not part of listed set");
}
return TRUE;
}
static gboolean
anode_validate_enumerated (GNode *node,
GBytes *value)
{
const guchar *buf;
gsize length;
g_assert (value != NULL);
buf = g_bytes_get_data (value, &length);
/* Enumerated must be positive */
if (length > 0 && (buf[0] & 0x80))
return anode_failure (node, "enumerated must be positive");
return anode_validate_integer (node, value);
}
static gboolean
anode_validate_boolean (GNode *node,
GBytes *value)
{
const guchar *buf;
gsize len;
g_assert (value != NULL);
buf = g_bytes_get_data (value, &len);
/* Must one byte, and zero or all ones */
if (len != 1)
return anode_failure (node, "invalid length boolean");
if (buf[0] != 0x00 && buf[0] != 0xFF)
return anode_failure (node, "boolean must be true or false");
return TRUE;
}
static gboolean
anode_validate_bit_string (GNode *node,
GBytes *value)
{
g_assert (value != NULL);
/* All the decode validation done in anode_decode_bit_string */
return TRUE;
}
static gboolean
anode_validate_string (GNode *node,
GBytes *value)
{
gsize length;
if (!anode_read_string_simple (node, value, NULL, &length))
g_return_val_if_reached (FALSE);
return anode_validate_size (node, (gulong)length);
}
static gboolean
anode_validate_object_id (GNode *node,
GBytes *value)
{
return anode_read_object_id (node, value, NULL);
}
static gboolean
anode_validate_null (GNode *node,
GBytes *value)
{
g_assert (value != NULL);
return (g_bytes_get_size (value) == 0);
}
static gboolean
anode_validate_time (GNode *node,
GBytes *value)
{
glong time;
struct tm when;
return anode_read_time (node, value, &when, &time);
}
static gboolean
anode_validate_choice (GNode *node,
gboolean strict)
{
GNode *child, *choice;
Anode *an;
/* One and only one of the children must be set */
choice = egg_asn1x_get_choice (node);
if (!choice)
return anode_failure (node, "one choice must be set");
if (!anode_validate_anything (choice, strict))
return FALSE;
for (child = node->children; child; child = child->next) {
if (child != choice) {
an = (Anode*)child->data;
if (an->chosen)
return anode_failure (node, "only one choice may be set");
}
}
return TRUE;
}
static gboolean
anode_validate_sequence_or_set (GNode *node,
gboolean strict)
{
GNode *child;
/* If this is optional, and has no values, then that's all good */
if (anode_def_flags (node) & FLAG_OPTION) {
if (!egg_asn1x_have (node))
return TRUE;
}
/* All of the children must validate properly */
for (child = node->children; child; child = child->next) {
if (!anode_validate_anything (child, strict))
return FALSE;
}
return TRUE;
}
static gboolean
anode_validate_sequence_or_set_of (GNode *node,
gboolean strict)
{
GNode *child;
gulong count;
count = 0;
/* All the children must validate properly */
for (child = node->children; child; child = child->next) {
if (egg_asn1x_have (child)) {
if (!anode_validate_anything (child, strict))
return FALSE;
count++;
}
}
if (count == 0 && anode_def_flags (node) & FLAG_OPTION)
return TRUE;
return anode_validate_size (node, count);
}
static gboolean
anode_validate_anything (GNode *node,
gboolean strict)
{
GBytes *value;
Atlv *tlv;
gint type;
gint flags;
type = anode_def_type (node);
flags = anode_def_flags (node);
/* Handle these specially */
switch (type) {
case EGG_ASN1X_CHOICE:
return anode_validate_choice (node, strict);
case EGG_ASN1X_SEQUENCE:
case EGG_ASN1X_SET:
return anode_validate_sequence_or_set (node, strict);
case EGG_ASN1X_SEQUENCE_OF:
case EGG_ASN1X_SET_OF:
return anode_validate_sequence_or_set_of (node, strict);
default:
break;
}
/* Values that have been configured */
value = anode_get_value (node);
if (value) {
switch (type) {
case EGG_ASN1X_INTEGER:
return anode_validate_integer (node, value);
case EGG_ASN1X_ENUMERATED:
return anode_validate_enumerated (node, value);
case EGG_ASN1X_BOOLEAN:
return anode_validate_boolean (node, value);
case EGG_ASN1X_BIT_STRING:
return anode_validate_bit_string (node, value);
case EGG_ASN1X_OCTET_STRING:
case EGG_ASN1X_GENERAL_STRING:
case EGG_ASN1X_NUMERIC_STRING:
case EGG_ASN1X_IA5_STRING:
case EGG_ASN1X_TELETEX_STRING:
case EGG_ASN1X_PRINTABLE_STRING:
case EGG_ASN1X_UTF8_STRING:
case EGG_ASN1X_VISIBLE_STRING:
return anode_validate_string (node, value);
case EGG_ASN1X_BMP_STRING:
case EGG_ASN1X_UNIVERSAL_STRING:
return TRUE; /* TODO: Need to validate strings more completely */
case EGG_ASN1X_OBJECT_ID:
return anode_validate_object_id (node, value);
case EGG_ASN1X_NULL:
return anode_validate_null (node, value);
case EGG_ASN1X_TIME:
case EGG_ASN1X_UTC_TIME:
case EGG_ASN1X_GENERALIZED_TIME:
return anode_validate_time (node, value);
default:
g_assert_not_reached ();
}
}
/* See if there's a tlv parsed */
tlv = anode_get_parsed (node);
if (tlv) {
switch (type) {
case EGG_ASN1X_ANY:
case EGG_ASN1X_GENERAL_STRING:
case EGG_ASN1X_OCTET_STRING:
case EGG_ASN1X_NUMERIC_STRING:
case EGG_ASN1X_IA5_STRING:
case EGG_ASN1X_TELETEX_STRING:
case EGG_ASN1X_PRINTABLE_STRING:
case EGG_ASN1X_UTF8_STRING:
case EGG_ASN1X_VISIBLE_STRING:
case EGG_ASN1X_BMP_STRING:
case EGG_ASN1X_UNIVERSAL_STRING:
return TRUE;
default:
break; /* UNREACHABLE: fix compiler warning */
}
}
if (flags & FLAG_OPTION)
return TRUE;
if (flags & FLAG_DEFAULT)
return TRUE;
return anode_failure (node, "missing value");
}
gboolean
egg_asn1x_validate (GNode *asn,
gboolean strict)
{
g_return_val_if_fail (asn, FALSE);
return anode_validate_anything (asn, strict);
}
/* -----------------------------------------------------------------------------------
* TREE CREATION
*/
static gint
compare_nodes_by_tag (gconstpointer a, gconstpointer b)
{
GNode *na = (gpointer)a;
GNode *nb = (gpointer)b;
gulong taga, tagb;
g_return_val_if_fail (anode_def_flags (na) & FLAG_TAG, 0);
g_return_val_if_fail (anode_def_flags (nb) & FLAG_TAG, 0);
taga = anode_calc_tag (na);
g_return_val_if_fail (taga != G_MAXULONG, 0);
tagb = anode_calc_tag (nb);
g_return_val_if_fail (tagb != G_MAXULONG, 0);
if (taga == tagb)
return 0;
return (taga < tagb) ? -1 : 1;
}
static const EggAsn1xDef *
adef_next_sibling (const EggAsn1xDef *def)
{
int depth = 0;
g_assert (def);
g_assert (def->value || def->type || def->name);
if ((def->type & FLAG_RIGHT) == 0)
return NULL;
/* Skip past any children */
if ((def->type & FLAG_DOWN) == FLAG_DOWN) {
depth += 1;
while (depth > 0) {
++def;
if ((def->type & FLAG_DOWN) == FLAG_DOWN)
depth += 1;
if ((def->type & FLAG_RIGHT) == 0)
depth -= 1;
}
}
++def;
g_return_val_if_fail (def->value || def->type || def->name, NULL);
return def;
}
static const EggAsn1xDef *
adef_first_child (const EggAsn1xDef *def)
{
g_assert (def);
g_assert (def->value || def->type || def->name);
if ((def->type & FLAG_DOWN) == 0)
return NULL;
++def;
g_return_val_if_fail (def->value || def->type || def->name, NULL);
return def;
}
static const EggAsn1xDef *
lookup_def_of_type (const EggAsn1xDef *defs,
const gchar *name,
gint type)
{
const EggAsn1xDef *def;
g_assert (defs);
g_assert (defs->value || defs->type || defs->name);
for (def = adef_first_child (defs); def; def = adef_next_sibling (def)) {
if ((def->type & 0xFF) == type && def->name && g_str_equal (name, def->name))
return def;
}
return NULL;
}
static gboolean
traverse_and_prepare (GNode *node, gpointer data)
{
const EggAsn1xDef *defs = data;
const EggAsn1xDef *def;
const gchar *identifier;
Anode *an, *anj;
GNode *join = NULL;
GNode *child, *next;
GList *list = NULL, *l;
/* A while, because the stuff we join, could also be an identifier */
while (anode_def_type (node) == EGG_ASN1X_IDENTIFIER) {
an = node->data;
identifier = an->join ? an->join->value : an->def->value;
g_return_val_if_fail (identifier, TRUE);
egg_asn1x_destroy (join);
join = egg_asn1x_create (defs, identifier);
g_return_val_if_fail (join, TRUE);
anj = join->data;
an->join = anj->def;
}
/* Move all the children of join node into our node */
if (join) {
list = NULL;
for (child = join->children, list = NULL; child; child = child->next)
list = g_list_prepend (list, child);
list = g_list_reverse (list);
for (l = list; l; l = g_list_next (l)) {
child = l->data;
g_node_unlink (child);
g_node_append (node, child);
}
g_list_free (list);
list = NULL;
}
/* Lookup the max set size */
if (anode_def_type (node) == EGG_ASN1X_SIZE) {
identifier = anode_def_name (node);
if (identifier && !g_str_equal (identifier, "MAX") &&
g_ascii_isalpha (identifier[0])) {
def = lookup_def_of_type (defs, identifier, EGG_ASN1X_INTEGER);
g_return_val_if_fail (def, TRUE);
anode_opt_add (node, def);
}
}
/* Anything child not a real node, we put into opts */
if (anode_def_type_is_real (node)) {
child = node->children;
while (child) {
next = child->next;
if (!anode_def_type_is_real (child)) {
an = child->data;
anode_opt_add (node, an->def);
for (l = an->opts; l; l = g_list_next (l))
anode_opt_add (node, l->data);
g_node_unlink (child);
anode_destroy (child);
}
child = next;
}
}
if (join) {
an = join->data;
for (l = an->opts; l; l = g_list_next (l))
anode_opt_add (node, l->data);
egg_asn1x_destroy (join);
}
/* Sort the children of any sets */
if (anode_def_type (node) == EGG_ASN1X_SET) {
for (child = node->children; child; child = child->next)
list = g_list_prepend (list, child);
list = g_list_sort (list, compare_nodes_by_tag);
for (l = list; l; l = g_list_next (l))
g_node_unlink (l->data);
for (l = list; l; l = g_list_next (l))
g_node_append (node, l->data);
g_list_free (list);
list = NULL;
}
/* Continue traversal */
return FALSE;
}
static const EggAsn1xDef *
match_oid_in_definition (const EggAsn1xDef *def,
GHashTable *names,
const gchar *match,
const gchar **problem)
{
const EggAsn1xDef *result = NULL;
const EggAsn1xDef *odef;
const gchar *value;
GString *oid = NULL;
g_assert (match);
g_assert (problem);
g_assert (names);
for (odef = adef_first_child (def); odef; odef = adef_next_sibling (odef)) {
if ((odef->type & 0xFF) != EGG_ASN1X_CONSTANT)
continue;
g_return_val_if_fail (odef->value, NULL);
if (strspn (odef->value, "01234567890") == strlen (odef->value)) {
value = odef->value;
} else {
value = g_hash_table_lookup (names, odef->value);
/* A name resolution problem */
if (!value) {
if (oid)
g_string_free (oid, TRUE);
*problem = odef->value;
return NULL;
}
}
if (oid) {
g_string_append_c (oid, '.');
g_string_append (oid, value);
} else {
oid = g_string_new (value);
}
}
if (oid != NULL) {
if (g_str_equal (oid->str, match))
result = adef_next_sibling (def);
g_assert (def->name);
g_hash_table_insert (names, (gchar*)def->name, g_string_free (oid, FALSE));
}
return result;
}
static const EggAsn1xDef *
match_oid_in_definitions (const EggAsn1xDef *defs,
const gchar *match)
{
const EggAsn1xDef *def;
const EggAsn1xDef *result;
GHashTable *names;
gboolean progress;
const gchar *problem;
names = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
result = NULL;
for (;;) {
progress = FALSE;
problem = NULL;
for (def = adef_first_child (defs); def; def = adef_next_sibling (def)) {
/* Only work with object ids, and ones with names */
if ((def->type & 0xFF) != EGG_ASN1X_OBJECT_ID || !def->name)
continue;
/* If we've already seen this one, skip */
if (g_hash_table_lookup (names, def->name))
continue;
progress = TRUE;
result = match_oid_in_definition (def, names, match, &problem);
if (result != NULL)
break;
}
if (!problem || result) {
break;
} else if (problem && !progress) {
g_warning ("couldn't find oid definition in ASN.1 for: %s", problem);
g_return_val_if_reached (NULL);
}
}
g_hash_table_destroy (names);
return result;
}
static gboolean
is_oid_number (const gchar *p)
{
gboolean must = TRUE;
gint i;
for (i = 0; p[i] != '\0'; i++) {
if (g_ascii_isdigit (p[i])) {
must = FALSE;
} else if (must) {
return FALSE;
} else {
if (p[i] != '.')
return FALSE;
must = TRUE;
}
}
return !must;
}
GNode*
egg_asn1x_create (const EggAsn1xDef *defs,
const gchar *type)
{
const EggAsn1xDef *def;
GNode *root, *parent, *node;
int flags;
g_return_val_if_fail (defs, NULL);
g_return_val_if_fail (type, NULL);
/* An OID */
if (is_oid_number (type)) {
def = match_oid_in_definitions (defs, type);
/* An Identifier */
} else {
for (def = adef_first_child (defs); def; def = adef_next_sibling (def)) {
if (def->name && g_str_equal (type, def->name))
break;
}
}
if (def == NULL || !def->name || !def->type)
return NULL;
/* The node for this item */
root = anode_new (def);
/* Build up nodes for underlying level */
if (def->type & FLAG_DOWN) {
node = root;
for (;;) {
if (def->type & FLAG_DOWN) {
parent = node;
} else if (def->type & FLAG_RIGHT) {
g_assert (node->parent);
parent = node->parent;
} else {
parent = node->parent;
while (parent) {
flags = anode_def_flags (parent);
parent = parent->parent;
if (flags & FLAG_RIGHT)
break;
}
}
if (!parent)
break;
++def;
node = anode_new (def);
g_node_append (parent, node);
}
}
/* Load up sub identifiers */
g_node_traverse (root, G_POST_ORDER, G_TRAVERSE_ALL, -1,
traverse_and_prepare, (gpointer)defs);
return root;
}
GNode*
egg_asn1x_create_quark (const EggAsn1xDef *defs,
GQuark type)
{
g_return_val_if_fail (type, NULL);
return egg_asn1x_create (defs, g_quark_to_string (type));
}
GNode *
egg_asn1x_create_and_decode_full (const EggAsn1xDef *defs,
const gchar *identifier,
GBytes *data,
gint options)
{
GNode *asn;
g_return_val_if_fail (defs != NULL, NULL);
g_return_val_if_fail (identifier != NULL, NULL);
g_return_val_if_fail (data != NULL, NULL);
asn = egg_asn1x_create (defs, identifier);
g_return_val_if_fail (asn, NULL);
if (!egg_asn1x_decode_full (asn, data, options)) {
egg_asn1x_destroy (asn);
return NULL;
}
return asn;
}
GNode*
egg_asn1x_create_and_decode (const EggAsn1xDef *defs,
const gchar *identifier,
GBytes *data)
{
g_return_val_if_fail (defs != NULL, NULL);
g_return_val_if_fail (identifier != NULL, NULL);
g_return_val_if_fail (data != NULL, NULL);
return egg_asn1x_create_and_decode_full (defs, identifier, data, 0);
}
/* -----------------------------------------------------------------------------------
* DUMPING and MESSAGES
*/
static void
dump_append_type (GString *output, gint type)
{
#define XX(x) if (type == EGG_ASN1X_##x) g_string_append (output, #x " ")
XX(CONSTANT); XX(IDENTIFIER); XX(INTEGER); XX(BOOLEAN); XX(SEQUENCE); XX(BIT_STRING);
XX(OCTET_STRING); XX(TAG); XX(DEFAULT); XX(SIZE); XX(SEQUENCE_OF); XX(OBJECT_ID); XX(ANY);
XX(SET); XX(SET_OF); XX(DEFINITIONS); XX(TIME); XX(UTC_TIME); XX(GENERALIZED_TIME); XX(CHOICE); XX(IMPORTS); XX(NULL);
XX(ENUMERATED); XX(GENERAL_STRING); XX(NUMERIC_STRING); XX(IA5_STRING); XX(TELETEX_STRING);
XX(PRINTABLE_STRING); XX(UNIVERSAL_STRING); XX(BMP_STRING); XX(UTF8_STRING); XX(VISIBLE_STRING);
if (output->len == 0)
g_string_printf (output, "%d ", (int)type);
#undef XX
}
static void
dump_append_flags (GString *output, gint flags)
{
#define XX(x) if ((FLAG_##x & flags) == FLAG_##x) g_string_append (output, #x " ")
XX(UNIVERSAL); XX(PRIVATE); XX(APPLICATION); XX(EXPLICIT); XX(IMPLICIT); XX(TAG); XX(OPTION);
XX(DEFAULT); XX(TRUE); XX(FALSE); XX(LIST); XX(MIN_MAX); XX(1_PARAM); XX(SIZE); XX(DEFINED_BY);
XX(GENERALIZED); XX(UTC); XX(IMPORTS); XX(NOT_USED); XX(SET); XX(ASSIGN);
/* XX(DOWN); XX(RIGHT); */
#undef XX
}
static gboolean
traverse_and_dump (GNode *node, gpointer unused)
{
EggAsn1xDef *def;
guint i, depth;
GString *output;
gchar *string;
Anode *an;
GList *l;
depth = g_node_depth (node);
for (i = 0; i < depth - 1; ++i)
g_print (" ");
an = node->data;
output = g_string_new ("");
dump_append_type (output, anode_def_type (node));
dump_append_flags (output, anode_def_flags (node));
string = g_utf8_casefold (output->str, output->len - 1);
g_string_free (output, TRUE);
g_print ("+ %s: %s [%s]%s\n", anode_def_name (node), anode_def_value (node),
string, an->parsed || an->value ? " *" : "");
g_free (string);
/* Print out all the options */
for (l = an->opts; l; l = g_list_next (l)) {
for (i = 0; i < depth; ++i)
g_print (" ");
def = l->data;
output = g_string_new ("");
dump_append_type (output, def->type & 0xFF);
dump_append_flags (output, def->type);
string = g_utf8_casefold (output->str, output->len - 1);
g_string_free (output, TRUE);
g_print ("- %s: %s [%s]\n", def->name, (const gchar*)def->value, string);
g_free (string);
}
return FALSE;
}
void
egg_asn1x_dump (GNode *asn)
{
g_return_if_fail (asn);
g_node_traverse (asn, G_PRE_ORDER, G_TRAVERSE_ALL, -1, traverse_and_dump, NULL);
}
static gboolean
traverse_and_get_failure (GNode *node, gpointer user_data)
{
const gchar **failure = user_data;
g_assert (!*failure);
*failure = anode_failure_get (node);
return (*failure != NULL);
}
const gchar*
egg_asn1x_message (GNode *asn)
{
const gchar *failure = NULL;
g_return_val_if_fail (asn, NULL);
g_node_traverse (asn, G_POST_ORDER, G_TRAVERSE_ALL, -1, traverse_and_get_failure, &failure);
return failure;
}
/* -----------------------------------------------------------------------------------
* CLEARING and DESTROYING
*/
static gboolean
traverse_and_clear (GNode *node, gpointer unused)
{
GNode *child, *next;
gint type;
anode_clear (node);
type = anode_def_type (node);
if (type == EGG_ASN1X_SET_OF || type == EGG_ASN1X_SEQUENCE_OF) {
/* The first 'real' child is the template */
child = node->children;
g_return_val_if_fail (child, TRUE);
/* And any others are extras */
child = child->next;
while (child) {
next = child->next;
anode_destroy (child);
child = next;
}
}
/* Don't stop traversal */
return FALSE;
}
void
egg_asn1x_clear (GNode *asn)
{
g_return_if_fail (asn);
g_node_traverse (asn, G_POST_ORDER, G_TRAVERSE_ALL, -1, traverse_and_clear, NULL);
}
void
egg_asn1x_destroy (gpointer data)
{
GNode *node = data;
if (node != NULL) {
g_return_if_fail (G_NODE_IS_ROOT (node));
anode_destroy (node);
}
}
/* --------------------------------------------------------------------------------
* TIME PARSING
*/
glong
egg_asn1x_parse_time_general (const gchar *time, gssize n_time)
{
gboolean ret;
glong value;
struct tm when;
gint offset = 0;
g_return_val_if_fail (time, -1);
if (n_time < 0)
n_time = strlen (time);
ret = parse_general_time (time, n_time, &when, &offset);
if (!ret)
return -1;
/* In order to work with 32 bit time_t. */
if (sizeof (time_t) <= 4 && when.tm_year >= 2038) {
value = (time_t)2145914603; /* 2037-12-31 23:23:23 */
/* Convert to seconds since epoch */
} else {
value = timegm (&when);
g_return_val_if_fail (*time >= 0, FALSE);
value += offset;
}
return value;
}
glong
egg_asn1x_parse_time_utc (const gchar *time, gssize n_time)
{
gboolean ret;
glong value;
struct tm when;
gint offset = 0;
g_return_val_if_fail (time, -1);
if (n_time < 0)
n_time = strlen (time);
ret = parse_utc_time (time, n_time, &when, &offset);
if (!ret)
return -1;
/* In order to work with 32 bit time_t. */
if (sizeof (time_t) <= 4 && when.tm_year >= 2038) {
value = (time_t)2145914603; /* 2037-12-31 23:23:23 */
/* Convert to seconds since epoch */
} else {
value = timegm (&when);
g_return_val_if_fail (*time >= 0, FALSE);
value += offset;
}
return value;
}
/* --------------------------------------------------------------------------------
* BASIC RAW ELEMENT INFO
*/
gssize
egg_asn1x_element_length (const guchar *data,
gsize n_data)
{
guchar cls;
int counter = 0;
int cb, len;
gulong tag;
if (atlv_parse_cls_tag (data, data + n_data, &cls, &tag, &cb)) {
counter += cb;
len = atlv_parse_length (data + cb, data + n_data, &cb);
counter += cb;
if (len >= 0) {
len += counter;
if (n_data >= len)
return len;
}
}
return -1;
}
gconstpointer
egg_asn1x_element_content (const guchar *data,
gsize n_data,
gsize *n_content)
{
int counter = 0;
guchar cls;
gulong tag;
int cb, len;
g_return_val_if_fail (data != NULL, NULL);
g_return_val_if_fail (n_content != NULL, NULL);
/* Now get the data out of this element */
if (!atlv_parse_cls_tag (data, data + n_data, &cls, &tag, &cb))
return NULL;
counter += cb;
len = atlv_parse_length (data + cb, data + n_data, &cb);
if (len < 0)
return NULL;
counter += cb;
*n_content = len;
return (const guchar*)data + counter;
}