Add function gpgme_data_identify.
authorWerner Koch <wk@gnupg.org>
Fri, 9 Aug 2013 17:19:26 +0000 (19:19 +0200)
committerWerner Koch <wk@gnupg.org>
Fri, 9 Aug 2013 17:19:26 +0000 (19:19 +0200)
* src/gpgme.h.in (gpgme_data_type_t): New.
(gpgme_data_identify): New prototype.
* src/data-identify.c: New.
* src/parsetlv.c, src/parsetlv.h: New.  Take from gpa.
* src/libgpgme.vers, src/gpgme.def: Add gpgme_data_identify.
* src/gpgme-tool.c (status): Add STATUS_IDENTIFY_RESULT.
(gt_identify): New.
(cmd_identify): New.

(hlp_passwd): Move close to cmd_passwd.
--

It is often useful to have a way to identify the data which needs
processing.  This is such a common task that it makes sense to
implement this in gpgme to avoid diverging implementations.

NEWS
doc/gpgme.texi
src/Makefile.am
src/data-identify.c [new file with mode: 0644]
src/gpgme-tool.c
src/gpgme.def
src/gpgme.h.in
src/libgpgme.vers
src/parsetlv.c [new file with mode: 0644]
src/parsetlv.h [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 5c871dd..8f700fd 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -7,9 +7,13 @@ Noteworthy changes in version 1.4.3 (unreleased)
  * Under Windows the default engines names are first searched in the
    installation directory of the gpgme DLL.
 
+ * New function gpgme_data_identify to detect the type of a message.
+
  * Interface changes relative to the 1.4.2 release:
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  gpgme_signers_count            NEW.
+ gpgme_data_type_t              NEW.
+ gpgme_data_identify            NEW.
 
 
 Noteworthy changes in version 1.4.2 (2013-05-28)
index 08e2f0f..4ec0bfe 100644 (file)
@@ -1885,6 +1885,7 @@ be used to manipulate both.
 @menu
 * Data Buffer I/O Operations::    I/O operations on data buffers.
 * Data Buffer Meta-Data::         Meta-data manipulation of data buffers.
+* Data Buffer Convenience::       Convenience fucntion for data buffers.
 @end menu
 
 
@@ -2047,6 +2048,56 @@ The function @code{gpgme_data_set_encoding} changes the encoding of
 the data object with the handle @var{dh} to @var{enc}.
 @end deftypefun
 
+@node Data Buffer Convenience
+@subsection Data Buffer Convenience Functions
+@cindex data buffer, convenience
+@cindex type of data
+@cindex identify
+
+@deftp {Data type} {enum gpgme_data_type_t}
+@tindex gpgme_data_type_t
+The @code{gpgme_data_type_t} type is used to return the detected type
+of the content of a data buffer.
+@end deftp
+
+@table @code
+@item GPGME_DATA_TYPE_INVALID
+This is returned by @code{gpgme_data_identify} if it was not possible
+to identify the data.  Reasons for this might be a non-seekable stream
+or a memory problem.  The value is 0.
+@item GPGME_DATA_TYPE_UNKNOWN
+The type of the data is not known.
+@item GPGME_DATA_TYPE_PGP_SIGNED
+The data is an OpenPGP signed message.  This may be a binary
+signature, a detached one or a cleartext signature.
+@item GPGME_DATA_TYPE_PGP_OTHER
+This is a generic OpenPGP message.  In most cases this will be
+encrypted data.
+@item GPGME_DATA_TYPE_PGP_KEY
+This is an OpenPGP key (private or public).
+@item GPGME_DATA_TYPE_CMS_SIGNED
+This is a CMS signed message.
+@item GPGME_DATA_TYPE_CMS_ENCRYPTED
+This is a CMS encrypted (enveloped data) message.
+@item GPGME_DATA_TYPE_CMS_OTHER
+This is used for other CMS message types.
+@item GPGME_DATA_TYPE_X509_CERT
+The data is a X.509 certificate
+@item GPGME_DATA_TYPE_PKCS12
+The data is a PKCS#12 message.  This is commonly used to exchange
+private keys for X.509.
+@end table
+
+@deftypefun gpgme_data_type_t gpgme_data_identify (@w{gpgme_data_t @var{dh}})
+The function @code{gpgme_data_identify} returns the type of the data
+with the handle @var{dh}.  If it is not possible to perform the
+identification, the function returns zero
+(@code{GPGME_DATA_TYPE_INVALID}).  Note that depending on how the data
+object has been created the identification may not be possible or the
+data object may change its internal state (file pointer moved).  For
+file or memory based data object, the state should not change.
+@end deftypefun
+
 
 @c
 @c    Chapter Contexts
index 37e3407..1f95103 100644 (file)
@@ -103,8 +103,9 @@ endif
 # unresolved symbols to the thread module.
 main_sources =                                                         \
        util.h conversion.c get-env.c context.h ops.h                   \
+       parsetlv.c parsetlv.h                                           \
        data.h data.c data-fd.c data-stream.c data-mem.c data-user.c    \
-       data-compat.c                                                   \
+       data-compat.c data-identify.c                                   \
        signers.c sig-notation.c                                        \
        wait.c wait-global.c wait-private.c wait-user.c wait.h          \
        op-support.c                                                    \
diff --git a/src/data-identify.c b/src/data-identify.c
new file mode 100644 (file)
index 0000000..9600633
--- /dev/null
@@ -0,0 +1,247 @@
+/* data-identify.c - Try to identify the data
+   Copyright (C) 2013 g10 Code GmbH
+
+   This file is part of GPGME.
+
+   GPGME 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.
+
+   GPGME 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 program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "gpgme.h"
+#include "data.h"
+#include "util.h"
+#include "parsetlv.h"
+
+/* The size of the sample data we take for detection.  */
+#define SAMPLE_SIZE 2048
+
+
+
+/* Note that DATA may be binary but a final nul is required so that
+   string operations will find a terminator.
+
+   Returns: GPGME_DATA_TYPE_xxxx */
+static gpgme_data_type_t
+basic_detection (const char *data, size_t datalen)
+{
+  tlvinfo_t ti;
+  const char *s;
+  size_t n;
+  int maybe_p12 = 0;
+
+  if (datalen < 24) /* Object is probably too short for detection.  */
+    return GPGME_DATA_TYPE_UNKNOWN;
+
+  /* This is a common example of a CMS object - it is obvious that we
+     only need to read a few bytes to get to the OID:
+  30 82 0B 59 06 09 2A 86 48 86 F7 0D 01 07 02 A0 82 0B 4A 30 82 0B 46 02
+  ----------- ++++++++++++++++++++++++++++++++
+  SEQUENCE    OID (signedData)
+  (2 byte len)
+
+    A PKCS#12 message is:
+
+  30 82 08 59 02 01 03 30 82 08 1F 06 09 2A 86 48 86 F7 0D 01 07 01 A0 82
+  ----------- ++++++++ ----------- ++++++++++++++++++++++++++++++++
+  SEQUENCE    INTEGER  SEQUENCE    OID (data)
+
+    A X.509 certificate is:
+
+  30 82 05 B8 30 82 04 A0 A0 03 02 01 02 02 07 15 46 A0 BF 30 07 39 30 0D
+  ----------- +++++++++++ ----- ++++++++ --------------------------
+  SEQUENCE    SEQUENCE    [0]   INTEGER  INTEGER                    SEQU
+              (tbs)            (version) (s/n)                      (Algo)
+
+    Thus we need to read at least 22 bytes, we add 2 bytes to cope with
+    length headers stored with 4 bytes.
+  */
+
+
+  s = data;
+  n = datalen;
+
+  if (parse_tlv (&s, &n, &ti))
+    goto try_pgp; /* Not properly BER encoded.  */
+  if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_SEQUENCE
+        && ti.is_cons))
+    goto try_pgp; /* A CMS object always starts with a sequence.  */
+
+  if (parse_tlv (&s, &n, &ti))
+    goto try_pgp; /* Not properly BER encoded.  */
+  if (ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_SEQUENCE
+      && ti.is_cons && n >= ti.length)
+    {
+      if (parse_tlv (&s, &n, &ti))
+        goto try_pgp;
+      if (!(ti.cls == ASN1_CLASS_CONTEXT && ti.tag == 0
+            && ti.is_cons && ti.length == 3 && n >= ti.length))
+        goto try_pgp;
+
+      if (parse_tlv (&s, &n, &ti))
+        goto try_pgp;
+      if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_INTEGER
+            && !ti.is_cons && ti.length == 1 && n && (*s == 1 || *s == 2)))
+        goto try_pgp;
+      s++;
+      n--;
+      if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_INTEGER
+            && !ti.is_cons))
+        goto try_pgp;
+      /* Because the now following S/N may be larger than the sample
+         data we have, we stop parsing here and don't check for the
+         algorithm ID.  */
+      return GPGME_DATA_TYPE_X509_CERT;
+    }
+  if (ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_INTEGER
+      && !ti.is_cons && ti.length == 1 && n && *s == 3)
+    {
+      maybe_p12 = 1;
+      s++;
+      n--;
+      if (parse_tlv (&s, &n, &ti))
+        goto try_pgp;
+      if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_SEQUENCE
+            && ti.is_cons))
+        goto try_pgp;
+      if (parse_tlv (&s, &n, &ti))
+        goto try_pgp;
+    }
+  if (ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_OBJECT_ID
+      && !ti.is_cons && ti.length && n >= ti.length)
+    {
+      if (ti.length == 9)
+        {
+          if (!memcmp (s, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x01", 9))
+            {
+              /* Data.  */
+              return (maybe_p12 ? GPGME_DATA_TYPE_PKCS12
+                      /*     */ : GPGME_DATA_TYPE_CMS_OTHER);
+            }
+          if (!memcmp (s, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x02", 9))
+            {
+              /* Signed Data.  */
+              return (maybe_p12 ? GPGME_DATA_TYPE_PKCS12
+                      /*     */ : GPGME_DATA_TYPE_CMS_SIGNED);
+            }
+          if (!memcmp (s, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x03", 9))
+            return GPGME_DATA_TYPE_CMS_ENCRYPTED; /* Enveloped Data.  */
+          if (!memcmp (s, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x05", 9))
+            return GPGME_DATA_TYPE_CMS_OTHER; /* Digested Data.  */
+          if (!memcmp (s, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x06", 9))
+            return GPGME_DATA_TYPE_CMS_OTHER; /* Encrypted Data.  */
+        }
+      else if (ti.length == 11)
+        {
+          if (!memcmp (s, "\x2A\x86\x48\x86\xF7\x0D\x01\x09\x10\x01\x02", 11))
+            return GPGME_DATA_TYPE_CMS_OTHER; /* Auth Data.  */
+        }
+    }
+
+
+ try_pgp:
+  /* Check whether this might be a non-armored PGP message.  We need
+     to do this before checking for armor lines, so that we don't get
+     fooled by armored messages inside a signed binary PGP message.  */
+  if ((data[0] & 0x80))
+    {
+      /* That might be a binary PGP message.  At least it is not plain
+         ASCII.  Of course this might be certain lead-in text of
+         armored CMS messages.  However, I am not sure whether this is
+         at all defined and in any case it is uncommon.  Thus we don't
+         do any further plausibility checks but stupidly assume no CMS
+         armored data will follow.  */
+      return GPGME_DATA_TYPE_UNKNOWN;
+    }
+
+  /* Now check whether there are armor lines.  */
+  for (s = data; s && *s; s = (*s=='\n')?(s+1):((s=strchr (s,'\n'))?(s+1):s))
+    {
+      if (!strncmp (s, "-----BEGIN ", 11))
+        {
+          if (!strncmp (s+11, "SIGNED ", 7))
+            return GPGME_DATA_TYPE_CMS_SIGNED;
+          if (!strncmp (s+11, "ENCRYPTED ", 10))
+            return GPGME_DATA_TYPE_CMS_ENCRYPTED;
+          if (!strncmp (s+11, "PGP ", 4))
+            {
+              if (!strncmp (s+15, "SIGNATURE", 9))
+                return GPGME_DATA_TYPE_PGP_SIGNED;
+              if (!strncmp (s+15, "SIGNED MESSAGE", 14))
+                return GPGME_DATA_TYPE_PGP_SIGNED;
+              if (!strncmp (s+15, "PUBLIC KEY BLOCK", 16))
+                return GPGME_DATA_TYPE_PGP_KEY;
+              if (!strncmp (s+15, "PRIVATE KEY BLOCK", 17))
+                return GPGME_DATA_TYPE_PGP_KEY;
+              if (!strncmp (s+15, "SECRET KEY BLOCK", 16))
+                return GPGME_DATA_TYPE_PGP_KEY;
+              if (!strncmp (s+15, "ARMORED FILE", 12))
+                return GPGME_DATA_TYPE_UNKNOWN;
+              return GPGME_DATA_TYPE_PGP_OTHER; /* PGP MESSAGE */
+            }
+          if (!strncmp (s+11, "CERTIFICATE", 11))
+            return GPGME_DATA_TYPE_X509_CERT;
+          if (!strncmp (s+11, "PKCS12", 6))
+            return GPGME_DATA_TYPE_PKCS12;
+          return GPGME_DATA_TYPE_CMS_OTHER; /* Not PGP, thus we assume CMS.  */
+        }
+    }
+
+  return GPGME_DATA_TYPE_UNKNOWN;
+}
+
+
+/* Try to detect the type of the data.  Note that this function works
+   only on seekable data objects.  The function tries to reset the
+   file pointer but there is no guarantee that it will work.
+
+   FIXME: We may want to add internal buffering so that this function
+   can be implemented for allmost all kind of data objects.
+ */
+gpgme_data_type_t
+gpgme_data_identify (gpgme_data_t dh, int reserved)
+{
+  gpgme_data_type_t result;
+  char *sample;
+  int n;
+  gpgme_off_t off;
+
+  /* Check whether we can seek the data object.  */
+  off = gpgme_data_seek (dh, 0, SEEK_CUR);
+  if (off == (gpgme_off_t)(-1))
+    return GPGME_DATA_TYPE_INVALID;
+
+  /* Allocate a buffer and read the data. */
+  sample = malloc (SAMPLE_SIZE);
+  if (!sample)
+    return GPGME_DATA_TYPE_INVALID; /* Ooops.  */
+  n = gpgme_data_read (dh, sample, SAMPLE_SIZE - 1);
+  if (n < 0)
+    {
+      free (sample);
+      return GPGME_DATA_TYPE_INVALID; /* Ooops.  */
+    }
+  sample[n] = 0;  /* (Required for our string functions.)  */
+
+  result = basic_detection (sample, n);
+  free (sample);
+  gpgme_data_seek (dh, off, SEEK_SET);
+
+  return result;
+}
index 0ebabab..2bf7654 100644 (file)
@@ -1435,7 +1435,8 @@ typedef enum status
     STATUS_INCLUDE_CERTS,
     STATUS_KEYLIST_MODE,
     STATUS_RECIPIENT,
-    STATUS_ENCRYPT_RESULT
+    STATUS_ENCRYPT_RESULT,
+    STATUS_IDENTIFY_RESULT
   } status_t;
 
 const char *status_string[] =
@@ -1448,7 +1449,8 @@ const char *status_string[] =
     "INCLUDE_CERTS",
     "KEYLIST_MODE",
     "RECIPIENT",
-    "ENCRYPT_RESULT"
+    "ENCRYPT_RESULT",
+    "IDENTIFY_RESULT"
   };
 
 struct gpgme_tool
@@ -2065,11 +2067,6 @@ gt_vfs_create (gpgme_tool_t gt, const char *container_file, int flags)
 }
 
 
-static const char hlp_passwd[] =
-  "PASSWD <user-id>\n"
-  "\n"
-  "Ask the backend to change the passphrase for the key\n"
-  "specified by USER-ID.";
 gpg_error_t
 gt_passwd (gpgme_tool_t gt, char *fpr)
 {
@@ -2086,6 +2083,29 @@ gt_passwd (gpgme_tool_t gt, char *fpr)
 }
 
 
+gpg_error_t
+gt_identify (gpgme_tool_t gt, gpgme_data_t data)
+{
+  const char *s = "?";
+
+  switch (gpgme_data_identify (data, 0))
+    {
+    case GPGME_DATA_TYPE_INVALID: return gpg_error (GPG_ERR_GENERAL);
+    case GPGME_DATA_TYPE_UNKNOWN      : s = "unknown"; break;
+    case GPGME_DATA_TYPE_PGP_SIGNED   : s = "PGP-signed"; break;
+    case GPGME_DATA_TYPE_PGP_OTHER    : s = "PGP"; break;
+    case GPGME_DATA_TYPE_PGP_KEY      : s = "PGP-key"; break;
+    case GPGME_DATA_TYPE_CMS_SIGNED   : s = "CMS-signed"; break;
+    case GPGME_DATA_TYPE_CMS_ENCRYPTED: s = "CMS-encrypted"; break;
+    case GPGME_DATA_TYPE_CMS_OTHER    : s = "CMS"; break;
+    case GPGME_DATA_TYPE_X509_CERT    : s = "X.509"; break;
+    case GPGME_DATA_TYPE_PKCS12       : s = "PKCS12"; break;
+    }
+  gt_write_status (gt, STATUS_IDENTIFY_RESULT, s, NULL);
+  return 0;
+}
+
+
 #define GT_RESULT_ENCRYPT 0x1
 #define GT_RESULT_DECRYPT 0x2
 #define GT_RESULT_SIGN 0x4
@@ -3374,6 +3394,11 @@ cmd_vfs_create (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_passwd[] =
+  "PASSWD <user-id>\n"
+  "\n"
+  "Ask the backend to change the passphrase for the key\n"
+  "specified by USER-ID.";
 static gpg_error_t
 cmd_passwd (assuan_context_t ctx, char *line)
 {
@@ -3430,6 +3455,39 @@ cmd_hash_algo_name (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_identify[] =
+  "IDENTIY\n"
+  "\n"
+  "Identify the type of data set with the INPUT command.";
+static gpg_error_t
+cmd_identify (assuan_context_t ctx, char *line)
+{
+  struct server *server = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  assuan_fd_t inp_fd;
+  char *inp_fn;
+  gpgme_data_t inp_data;
+
+  inp_fd = server->input_fd;
+  inp_fn = server->input_filename;
+  if (inp_fd == ASSUAN_INVALID_FD && !inp_fn)
+    return GPG_ERR_ASS_NO_INPUT;
+
+  err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
+                         &server->input_stream);
+  if (err)
+    return err;
+
+  err = gt_identify (server->gt, inp_data);
+
+  gpgme_data_release (inp_data);
+  server_reset_fds (server);
+
+  return err;
+}
+
+
+
 /* Tell the assuan library about our commands.  */
 static gpg_error_t
 register_commands (assuan_context_t ctx)
@@ -3488,6 +3546,7 @@ register_commands (assuan_context_t ctx)
     { "PUBKEY_ALGO_NAME", cmd_pubkey_algo_name },
     { "HASH_ALGO_NAME", cmd_hash_algo_name },
     { "PASSWD", cmd_passwd, hlp_passwd },
+    { "IDENTIFY", cmd_identify, hlp_identify },
     { NULL }
   };
   int idx;
index 7610d37..0478cb6 100644 (file)
@@ -211,5 +211,7 @@ EXPORTS
 
     gpgme_signers_count                   @160
 
+    gpgme_data_identify                   @161
+
 ; END
 
index f644a50..5c4de6b 100644 (file)
@@ -210,6 +210,22 @@ typedef enum
   }
 gpgme_data_encoding_t;
 
+/* Known data types.  */
+typedef enum
+  {
+    GPGME_DATA_TYPE_INVALID      = 0,   /* Not detected.  */
+    GPGME_DATA_TYPE_UNKNOWN      = 1,
+    GPGME_DATA_TYPE_PGP_SIGNED   = 0x10,
+    GPGME_DATA_TYPE_PGP_OTHER    = 0x12,
+    GPGME_DATA_TYPE_PGP_KEY      = 0x13,
+    GPGME_DATA_TYPE_CMS_SIGNED   = 0x20,
+    GPGME_DATA_TYPE_CMS_ENCRYPTED= 0x21,
+    GPGME_DATA_TYPE_CMS_OTHER    = 0x22,
+    GPGME_DATA_TYPE_X509_CERT    = 0x23,
+    GPGME_DATA_TYPE_PKCS12       = 0x24,
+  }
+gpgme_data_type_t;
+
 \f
 /* Public key algorithms from libgcrypt.  */
 typedef enum
@@ -1149,6 +1165,9 @@ char *gpgme_data_get_file_name (gpgme_data_t dh);
 gpgme_error_t gpgme_data_set_file_name (gpgme_data_t dh,
                                        const char *file_name);
 
+/* Try to identify the type of the data in DH.  */
+gpgme_data_type_t gpgme_data_identify (gpgme_data_t dh, int reserved);
+
 
 /* Create a new data buffer which retrieves the data from the callback
    function READ_CB.  Deprecated, please use gpgme_data_new_from_cbs
index 0b2e89d..fe18e6a 100644 (file)
@@ -29,6 +29,7 @@ GPGME_1.1 {
 
     gpgme_data_set_file_name;
     gpgme_data_get_file_name;
+    gpgme_data_identify;
 
     gpgme_sig_notation_clear;
     gpgme_sig_notation_add;
diff --git a/src/parsetlv.c b/src/parsetlv.c
new file mode 100644 (file)
index 0000000..70c9518
--- /dev/null
@@ -0,0 +1,103 @@
+/* parsetlv.c -  ASN.1 TLV functions
+ * Copyright (C) 2005, 2007, 2008, 2012 g10 Code GmbH
+ *
+ * This file 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 file 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 program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "parsetlv.h"
+
+
+/* Simple but pretty complete ASN.1 BER parser.  Parse the data at the
+   address of BUFFER with a length given at the address of SIZE.  On
+   success return 0 and update BUFFER and SIZE to point to the value.
+   Do not update them on error.  The information about the object are
+   stored in the caller allocated TI structure.  */
+int
+_gpgme_parse_tlv (char const **buffer, size_t *size, tlvinfo_t *ti)
+{
+  int c;
+  unsigned long tag;
+  const unsigned char *buf = (const unsigned char *)(*buffer);
+  size_t length = *size;
+
+  ti->cls = 0;
+  ti->tag = 0;
+  ti->is_cons = 0;
+  ti->is_ndef = 0;
+  ti->length = 0;
+  ti->nhdr = 0;
+
+  if (!length)
+    return -1;
+  c = *buf++; length--; ++ti->nhdr;
+
+  ti->cls = (c & 0xc0) >> 6;
+  ti->is_cons = !!(c & 0x20);
+  tag = c & 0x1f;
+
+  if (tag == 0x1f)
+    {
+      tag = 0;
+      do
+        {
+          tag <<= 7;
+          if (!length)
+            return -1;
+          c = *buf++; length--; ++ti->nhdr;
+          tag |= c & 0x7f;
+        }
+      while (c & 0x80);
+    }
+  ti->tag = tag;
+
+  if (!length)
+    return -1;
+  c = *buf++; length--; ++ti->nhdr;
+
+  if ( !(c & 0x80) )
+    ti->length = c;
+  else if (c == 0x80)
+    ti->is_ndef = 1;
+  else if (c == 0xff)
+    return -1;
+  else
+    {
+      unsigned long len = 0;
+      int count = (c & 0x7f);
+
+      if (count > sizeof (len) || count > sizeof (size_t))
+        return -1;
+
+      for (; count; count--)
+        {
+          len <<= 8;
+          if (!length)
+            return -1;
+          c = *buf++; length--; ++ti->nhdr;
+          len |= c & 0xff;
+        }
+      ti->length = len;
+    }
+
+  *buffer = (void*)buf;
+  *size = length;
+  return 0;
+}
diff --git a/src/parsetlv.h b/src/parsetlv.h
new file mode 100644 (file)
index 0000000..153073c
--- /dev/null
@@ -0,0 +1,48 @@
+/* parsetlv.h -  TLV functions defintions
+ * Copyright (C) 2012 g10 Code GmbH
+ *
+ * This file 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 file 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 program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PARSETLV_H
+#define PARSETLV_H
+
+/* ASN.1 constants.  */
+#define ASN1_CLASS_UNIVERSAL   0
+#define ASN1_CLASS_APPLICATION 1
+#define ASN1_CLASS_CONTEXT     2
+#define ASN1_CLASS_PRIVATE     3
+#define ASN1_TAG_INTEGER       2
+#define ASN1_TAG_OBJECT_ID     6
+#define ASN1_TAG_SEQUENCE     16
+
+
+/* Object used with parse_tlv.  */
+struct tlvinfo_s
+{
+  int cls;            /* The class of the tag.  */
+  int tag;            /* The tag.  */
+  int is_cons;        /* True if it is a constructed object.  */
+  int is_ndef;        /* True if the object has an indefinite length.  */
+  size_t length;      /* The length of the value.  */
+  size_t nhdr;        /* The number of octets in the header (tag,length). */
+};
+typedef struct tlvinfo_s tlvinfo_t;
+
+/*-- parsetlv.c --*/
+int _gpgme_parse_tlv (char const **buffer, size_t *size, tlvinfo_t *ti);
+#define parse_tlv(a,b,c) _gpgme_parse_tlv ((a), (b), (c))
+
+
+#endif /*PARSETLV_H*/