diff options
Diffstat (limited to '')
| -rw-r--r-- | src/Makefile.am | 3 | ||||
| -rw-r--r-- | src/data-identify.c | 247 | ||||
| -rw-r--r-- | src/gpgme-tool.c | 73 | ||||
| -rw-r--r-- | src/gpgme.def | 2 | ||||
| -rw-r--r-- | src/gpgme.h.in | 19 | ||||
| -rw-r--r-- | src/libgpgme.vers | 1 | ||||
| -rw-r--r-- | src/parsetlv.c | 103 | ||||
| -rw-r--r-- | src/parsetlv.h | 48 | 
8 files changed, 488 insertions, 8 deletions
| diff --git a/src/Makefile.am b/src/Makefile.am index 37e34071..1f951039 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 index 00000000..96006335 --- /dev/null +++ b/src/data-identify.c @@ -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; +} diff --git a/src/gpgme-tool.c b/src/gpgme-tool.c index 0ebababb..2bf7654a 100644 --- a/src/gpgme-tool.c +++ b/src/gpgme-tool.c @@ -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; diff --git a/src/gpgme.def b/src/gpgme.def index 7610d37e..0478cb62 100644 --- a/src/gpgme.def +++ b/src/gpgme.def @@ -211,5 +211,7 @@ EXPORTS      gpgme_signers_count                   @160 +    gpgme_data_identify                   @161 +  ; END diff --git a/src/gpgme.h.in b/src/gpgme.h.in index f644a509..5c4de6be 100644 --- a/src/gpgme.h.in +++ b/src/gpgme.h.in @@ -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; +  /* 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 diff --git a/src/libgpgme.vers b/src/libgpgme.vers index 0b2e89db..fe18e6a5 100644 --- a/src/libgpgme.vers +++ b/src/libgpgme.vers @@ -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 index 00000000..70c95189 --- /dev/null +++ b/src/parsetlv.c @@ -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 index 00000000..153073c1 --- /dev/null +++ b/src/parsetlv.h @@ -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*/ | 
