summaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
Diffstat (limited to 'common')
-rw-r--r--common/Makefile.am7
-rw-r--r--common/private-keys.c740
-rw-r--r--common/private-keys.h109
-rw-r--r--common/t-private-keys.c543
-rw-r--r--common/util.h3
5 files changed, 1400 insertions, 2 deletions
diff --git a/common/Makefile.am b/common/Makefile.am
index de6a4a8fa..4a35f64be 100644
--- a/common/Makefile.am
+++ b/common/Makefile.am
@@ -89,7 +89,8 @@ common_sources = \
strlist.c strlist.h \
call-gpg.c call-gpg.h \
exectool.c exectool.h \
- server-help.c server-help.h
+ server-help.c server-help.h \
+ private-keys.c private-keys.h
if HAVE_W32_SYSTEM
common_sources += w32-reg.c w32-afunix.c w32-afunix.h
@@ -154,7 +155,8 @@ endif
module_tests = t-stringhelp t-timestuff \
t-convert t-percent t-gettime t-sysutils t-sexputil \
t-session-env t-openpgp-oid t-ssh-utils \
- t-mapstrings t-zb32 t-mbox-util t-iobuf t-strlist
+ t-mapstrings t-zb32 t-mbox-util t-iobuf t-strlist \
+ t-private-keys
if !HAVE_W32CE_SYSTEM
module_tests += t-exechelp
endif
@@ -203,6 +205,7 @@ t_zb32_LDADD = $(t_common_ldadd)
t_mbox_util_LDADD = $(t_common_ldadd)
t_iobuf_LDADD = $(t_common_ldadd)
t_strlist_LDADD = $(t_common_ldadd)
+t_private_keys_LDADD = $(t_common_ldadd)
# System specific test
if HAVE_W32_SYSTEM
diff --git a/common/private-keys.c b/common/private-keys.c
new file mode 100644
index 000000000..d77ce1643
--- /dev/null
+++ b/common/private-keys.c
@@ -0,0 +1,740 @@
+/* private-keys.c - Parser and writer for the extended private key format.
+ * Copyright (C) 2016 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of either
+ *
+ * - the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * or
+ *
+ * - the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * or both in parallel, as here.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <assert.h>
+#include <gcrypt.h>
+#include <gpg-error.h>
+#include <string.h>
+
+#include "private-keys.h"
+#include "mischelp.h"
+#include "strlist.h"
+#include "util.h"
+
+struct private_key_container
+{
+ struct private_key_entry *first;
+ struct private_key_entry *last;
+};
+
+
+struct private_key_entry
+{
+ struct private_key_entry *prev;
+ struct private_key_entry *next;
+
+ /* The name. Comments and blank lines have NAME set to NULL. */
+ char *name;
+
+ /* The value as stored in the file. We store it when when we parse
+ a file so that we can reproduce it. */
+ strlist_t raw_value;
+
+ /* The decoded value. */
+ char *value;
+};
+
+
+
+/* Allocation and deallocation. */
+
+/* Allocate a private key container structure. */
+pkc_t
+pkc_new (void)
+{
+ return xtrycalloc (1, sizeof (struct private_key_container));
+}
+
+
+static void
+pke_release (pke_t entry)
+{
+ if (entry == NULL)
+ return;
+
+ xfree (entry->name);
+ if (entry->value)
+ wipememory (entry->value, strlen (entry->value));
+ xfree (entry->value);
+ free_strlist_wipe (entry->raw_value);
+ xfree (entry);
+}
+
+
+/* Release a private key container structure. */
+void
+pkc_release (pkc_t pk)
+{
+ pke_t e, next;
+
+ if (pk == NULL)
+ return;
+
+ for (e = pk->first; e; e = next)
+ {
+ next = e->next;
+ pke_release (e);
+ }
+
+ xfree (pk);
+}
+
+
+
+/* Dealing with names and values. */
+
+/* Check whether the given name is valid. Valid names start with a
+ letter, end with a colon, and contain only alphanumeric characters
+ and the hyphen. */
+static int
+valid_name (const char *name)
+{
+ size_t i, len = strlen (name);
+
+ if (! alphap (name) || len == 0 || name[len - 1] != ':')
+ return 0;
+
+ for (i = 1; i < len - 1; i++)
+ if (! alnump (&name[i]) && name[i] != '-')
+ return 0;
+
+ return 1;
+}
+
+
+/* Makes sure that ENTRY has a RAW_VALUE. */
+static gpg_error_t
+assert_raw_value (pke_t entry)
+{
+ gpg_error_t err = 0;
+ size_t len, offset;
+#define LINELEN 70
+ char buf[LINELEN+3];
+
+ if (entry->raw_value)
+ return 0;
+
+ len = strlen (entry->value);
+ offset = 0;
+ while (len)
+ {
+ size_t amount, linelen = LINELEN;
+
+ /* On the first line we need to subtract space for the name. */
+ if (entry->raw_value == NULL && strlen (entry->name) < linelen)
+ linelen -= strlen (entry->name);
+
+ /* See if the rest of the value fits in this line. */
+ if (len <= linelen)
+ amount = len;
+ else
+ {
+ size_t i;
+
+ /* Find a suitable space to break on. */
+ for (i = linelen - 1; linelen - i < 30 && linelen - i > offset; i--)
+ if (ascii_isspace (entry->value[i]))
+ break;
+
+ if (ascii_isspace (entry->value[i]))
+ {
+ /* Found one. */
+ amount = i;
+ }
+ else
+ {
+ /* Just induce a hard break. */
+ amount = linelen;
+ }
+ }
+
+ snprintf (buf, sizeof buf, " %.*s\n", (int) amount,
+ &entry->value[offset]);
+ if (append_to_strlist_try (&entry->raw_value, buf) == NULL)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ offset += amount;
+ len -= amount;
+ }
+
+ leave:
+ if (err)
+ {
+ free_strlist_wipe (entry->raw_value);
+ entry->raw_value = NULL;
+ }
+
+ return err;
+#undef LINELEN
+}
+
+
+/* Computes the length of the value encoded as continuation. If
+ *SWALLOW_WS is set, all whitespace at the beginning of S is
+ swallowed. If START is given, a pointer to the beginning of the
+ value is stored there. */
+static size_t
+continuation_length (const char *s, int *swallow_ws, const char **start)
+{
+ size_t len;
+
+ if (*swallow_ws)
+ {
+ /* The previous line was a blank line and we inserted a newline.
+ Swallow all whitespace at the beginning of this line. */
+ while (ascii_isspace (*s))
+ s++;
+ }
+ else
+ {
+ /* Iff a continuation starts with more than one space, it
+ encodes a space. */
+ if (ascii_isspace (*s))
+ s++;
+ }
+
+ /* Strip whitespace at the end. */
+ len = strlen (s);
+ while (len > 0 && ascii_isspace (s[len-1]))
+ len--;
+
+ if (len == 0)
+ {
+ /* Blank lines encode newlines. */
+ len = 1;
+ s = "\n";
+ *swallow_ws = 1;
+ }
+ else
+ *swallow_ws = 0;
+
+ if (start)
+ *start = s;
+
+ return len;
+}
+
+
+/* Makes sure that ENTRY has a VALUE. */
+static gpg_error_t
+assert_value (pke_t entry)
+{
+ size_t len;
+ int swallow_ws;
+ strlist_t s;
+ char *p;
+
+ if (entry->value)
+ return 0;
+
+ len = 0;
+ swallow_ws = 0;
+ for (s = entry->raw_value; s; s = s->next)
+ len += continuation_length (s->d, &swallow_ws, NULL);
+
+ /* Add one for the terminating zero. */
+ len += 1;
+
+ entry->value = p = xtrymalloc (len);
+ if (entry->value == NULL)
+ return gpg_error_from_syserror ();
+
+ swallow_ws = 0;
+ for (s = entry->raw_value; s; s = s->next)
+ {
+ const char *start;
+ size_t l = continuation_length (s->d, &swallow_ws, &start);
+
+ memcpy (p, start, l);
+ p += l;
+ }
+
+ *p++ = 0;
+ assert (p - entry->value == len);
+
+ return 0;
+}
+
+
+/* Get the name. */
+char *
+pke_name (pke_t pke)
+{
+ return pke->name;
+}
+
+
+/* Get the value. */
+char *
+pke_value (pke_t pke)
+{
+ if (assert_value (pke))
+ return NULL;
+ return pke->value;
+}
+
+
+
+/* Adding and modifying values. */
+
+/* Add (NAME, VALUE, RAW_VALUE) to PK. NAME may be NULL for comments
+ and blank lines. At least one of VALUE and RAW_VALUE must be
+ given. If PRESERVE_ORDER is not given, entries with the same name
+ are grouped. NAME, VALUE and RAW_VALUE is consumed. */
+static gpg_error_t
+_pkc_add (pkc_t pk, char *name, char *value, strlist_t raw_value,
+ int preserve_order)
+{
+ gpg_error_t err = 0;
+ pke_t e;
+
+ assert (value || raw_value);
+
+ if (name && ! valid_name (name))
+ {
+ err = gpg_error (GPG_ERR_INV_NAME);
+ goto leave;
+ }
+
+ if (name && strcasecmp (name, "Key:") == 0 && pkc_lookup (pk, "Key:"))
+ {
+ err = gpg_error (GPG_ERR_INV_NAME);
+ goto leave;
+ }
+
+ e = xtrycalloc (1, sizeof *e);
+ if (e == NULL)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ e->name = name;
+ e->value = value;
+ e->raw_value = raw_value;
+
+ if (pk->first)
+ {
+ pke_t last;
+
+ if (preserve_order)
+ last = pk->last;
+ else
+ {
+ /* See if there is already an entry with NAME. */
+ last = pkc_lookup (pk, name);
+
+ /* If so, find the last in that block. */
+ if (last)
+ while (last->next)
+ {
+ pke_t next = last->next;
+
+ if (next->name && strcasecmp (next->name, name) == 0)
+ last = next;
+ else
+ break;
+ }
+ /* Otherwise, just find the last entry. */
+ else
+ last = pk->last;
+ }
+
+ if (last->next)
+ {
+ e->prev = last;
+ e->next = last->next;
+ last->next = e;
+ e->next->prev = e;
+ }
+ else
+ {
+ e->prev = last;
+ last->next = e;
+ pk->last = e;
+ }
+ }
+ else
+ pk->first = pk->last = e;
+
+ leave:
+ if (err)
+ {
+ xfree (name);
+ if (value)
+ wipememory (value, strlen (value));
+ xfree (value);
+ free_strlist_wipe (raw_value);
+ }
+
+ return err;
+}
+
+
+/* Add (NAME, VALUE) to PK. If an entry with NAME already exists, it
+ is not updated but the new entry is appended. */
+gpg_error_t
+pkc_add (pkc_t pk, const char *name, const char *value)
+{
+ char *k, *v;
+
+ k = xtrystrdup (name);
+ if (k == NULL)
+ return gpg_error_from_syserror ();
+
+ v = xtrystrdup (value);
+ if (v == NULL)
+ {
+ xfree (k);
+ return gpg_error_from_syserror ();
+ }
+
+ return _pkc_add (pk, k, v, NULL, 0);
+}
+
+
+/* Add (NAME, VALUE) to PK. If an entry with NAME already exists, it
+ is updated with VALUE. If multiple entries with NAME exist, the
+ first entry is updated. */
+gpg_error_t
+pkc_set (pkc_t pk, const char *name, const char *value)
+{
+ pke_t e;
+
+ if (! valid_name (name))
+ return GPG_ERR_INV_NAME;
+
+ e = pkc_lookup (pk, name);
+ if (e)
+ {
+ char *v;
+
+ v = xtrystrdup (value);
+ if (v == NULL)
+ return gpg_error_from_syserror ();
+
+ free_strlist_wipe (e->raw_value);
+ e->raw_value = NULL;
+ if (e->value)
+ wipememory (e->value, strlen (e->value));
+ xfree (e->value);
+ e->value = v;
+
+ return 0;
+ }
+ else
+ return pkc_add (pk, name, value);
+}
+
+
+/* Delete the given entry from PK. */
+void
+pkc_delete (pkc_t pk, pke_t entry)
+{
+ if (entry->prev)
+ entry->prev->next = entry->next;
+ else
+ pk->first = entry->next;
+
+ if (entry->next)
+ entry->next->prev = entry->prev;
+ else
+ pk->last = entry->prev;
+
+ pke_release (entry);
+}
+
+
+
+/* Lookup and iteration. */
+
+/* Get the first non-comment entry. */
+pke_t
+pkc_first (pkc_t pk)
+{
+ pke_t entry;
+ for (entry = pk->first; entry; entry = entry->next)
+ if (entry->name)
+ return entry;
+ return NULL;
+}
+
+
+/* Get the first entry with the given name. */
+pke_t
+pkc_lookup (pkc_t pk, const char *name)
+{
+ pke_t entry;
+ for (entry = pk->first; entry; entry = entry->next)
+ if (entry->name && strcasecmp (entry->name, name) == 0)
+ return entry;
+ return NULL;
+}
+
+
+/* Get the next non-comment entry. */
+pke_t
+pke_next (pke_t entry)
+{
+ for (entry = entry->next; entry; entry = entry->next)
+ if (entry->name)
+ return entry;
+ return NULL;
+}
+
+
+/* Get the next entry with the given name. */
+pke_t
+pke_next_value (pke_t entry, const char *name)
+{
+ for (entry = entry->next; entry; entry = entry->next)
+ if (entry->name && strcasecmp (entry->name, name) == 0)
+ return entry;
+ return NULL;
+}
+
+
+
+/* Private key handling. */
+
+/* Get the private key. */
+gpg_error_t
+pkc_get_private_key (pkc_t pk, gcry_sexp_t *retsexp)
+{
+ gpg_error_t err;
+ pke_t e;
+
+ e = pkc_lookup (pk, "Key:");
+ if (e == NULL)
+ return gpg_error (GPG_ERR_MISSING_KEY);
+
+ err = assert_value (e);
+ if (err)
+ return err;
+
+ return gcry_sexp_sscan (retsexp, NULL, e->value, strlen (e->value));
+}
+
+
+/* Set the private key. */
+gpg_error_t
+pkc_set_private_key (pkc_t pk, gcry_sexp_t sexp)
+{
+ gpg_error_t err;
+ char *raw, *clean, *p;
+ size_t len, i;
+
+ len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
+
+ raw = xtrymalloc (len);
+ if (raw == NULL)
+ return gpg_error_from_syserror ();
+
+ clean = xtrymalloc (len);
+ if (clean == NULL)
+ {
+ xfree (raw);
+ return gpg_error_from_syserror ();
+ }
+
+ gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, raw, len);
+
+ /* Strip any whitespace at the end. */
+ i = strlen (raw) - 1;
+ while (i && ascii_isspace (raw[i]))
+ {
+ raw[i] = 0;
+ i--;
+ }
+
+ /* Replace any newlines with spaces, remove superfluous whitespace. */
+ len = strlen (raw);
+ for (p = clean, i = 0; i < len; i++)
+ {
+ char c = raw[i];
+
+ /* Collapse contiguous and superfluous spaces. */
+ if (ascii_isspace (c) && i > 0
+ && (ascii_isspace (raw[i-1]) || raw[i-1] == '(' || raw[i-1] == ')'))
+ continue;
+
+ if (c == '\n')
+ c = ' ';
+
+ *p++ = c;
+ }
+ *p = 0;
+
+ err = pkc_set (pk, "Key:", clean);
+ xfree (raw);
+ xfree (clean);
+ return err;
+}
+
+
+
+/* Parsing and serialization. */
+
+/* Parse STREAM and return a newly allocated private key container
+ structure in RESULT. If ERRLINEP is given, the line number the
+ parser was last considering is stored there. */
+gpg_error_t
+pkc_parse (pkc_t *result, int *errlinep, estream_t stream)
+{
+ gpg_error_t err = 0;
+ gpgrt_ssize_t len;
+ char *buf = NULL;
+ size_t buf_len = 0;
+ char *name = NULL;
+ strlist_t raw_value = NULL;
+
+
+ *result = pkc_new ();
+ if (*result == NULL)
+ return gpg_error_from_syserror ();
+
+ if (errlinep)
+ *errlinep = 0;
+ while ((len = es_read_line (stream, &buf, &buf_len, NULL)))
+ {
+ char *p;
+ if (errlinep)
+ *errlinep += 1;
+
+ /* Skip any whitespace. */
+ for (p = buf; *p && ascii_isspace (*p); p++)
+ /* Do nothing. */;
+
+ if (name && (spacep (buf) || *p == 0))
+ {
+ /* A continuation. */
+ if (append_to_strlist_try (&raw_value, buf) == NULL)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ continue;
+ }
+
+ /* No continuation. Add the current entry if any. */
+ if (raw_value)
+ {
+ err = _pkc_add (*result, name, NULL, raw_value, 1);
+ if (err)
+ goto leave;
+ }
+
+ /* And prepare for the next one. */
+ name = NULL;
+ raw_value = NULL;
+
+ if (*p != 0 && *p != '#')
+ {
+ char *colon, *value, tmp;
+
+ colon = strchr (buf, ':');
+ if (colon == NULL)
+ {
+ err = gpg_error (GPG_ERR_INV_VALUE);
+ goto leave;
+ }
+
+ value = colon + 1;
+ tmp = *value;
+ *value = 0;
+ name = xstrdup (p);
+ *value = tmp;
+
+ if (name == NULL)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ if (append_to_strlist (&raw_value, value) == NULL)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ continue;
+ }
+
+ if (append_to_strlist (&raw_value, buf) == NULL)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+
+ /* Add the final entry. */
+ if (raw_value)
+ err = _pkc_add (*result, name, NULL, raw_value, 1);
+
+ leave:
+ gpgrt_free (buf);
+ if (err)
+ {
+ pkc_release (*result);
+ *result = NULL;
+ }
+
+ return err;
+}
+
+
+/* Write a representation of PK to STREAM. */
+gpg_error_t
+pkc_write (pkc_t pk, estream_t stream)
+{
+ gpg_error_t err;
+ pke_t entry;
+ strlist_t s;
+
+ for (entry = pk->first; entry; entry = entry->next)
+ {
+ if (entry->name)
+ es_fputs (entry->name, stream);
+
+ err = assert_raw_value (entry);
+ if (err)
+ return err;
+
+ for (s = entry->raw_value; s; s = s->next)
+ es_fputs (s->d, stream);
+
+ if (es_ferror (stream))
+ return gpg_error_from_syserror ();
+ }
+
+ return 0;
+}
diff --git a/common/private-keys.h b/common/private-keys.h
new file mode 100644
index 000000000..d21e94f7c
--- /dev/null
+++ b/common/private-keys.h
@@ -0,0 +1,109 @@
+/* private-keys.h - Parser and writer for the extended private key format.
+ * Copyright (C) 2016 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of either
+ *
+ * - the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * or
+ *
+ * - the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * or both in parallel, as here.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GNUPG_COMMON_PRIVATE_KEYS_H
+#define GNUPG_COMMON_PRIVATE_KEYS_H
+
+struct private_key_container;
+typedef struct private_key_container *pkc_t;
+
+struct private_key_entry;
+typedef struct private_key_entry *pke_t;
+
+
+
+/* Memory management, and dealing with entries. */
+
+/* Allocate a private key container structure. */
+pkc_t pkc_new (void);
+
+/* Release a private key container structure. */
+void pkc_release (pkc_t pk);
+
+/* Get the name. */
+char *pke_name (pke_t pke);
+
+/* Get the value. */
+char *pke_value (pke_t pke);
+
+
+
+/* Lookup and iteration. */
+
+/* Get the first non-comment entry. */
+pke_t pkc_first (pkc_t pk);
+
+/* Get the first entry with the given name. */
+pke_t pkc_lookup (pkc_t pk, const char *name);
+
+/* Get the next non-comment entry. */
+pke_t pke_next (pke_t entry);
+
+/* Get the next entry with the given name. */
+pke_t pke_next_value (pke_t entry, const char *name);
+
+
+
+/* Adding and modifying values. */
+
+/* Add (NAME, VALUE) to PK. If an entry with NAME already exists, it
+ is not updated but the new entry is appended. */
+gpg_error_t pkc_add (pkc_t pk, const char *name, const char *value);
+
+/* Add (NAME, VALUE) to PK. If an entry with NAME already exists, it
+ is updated with VALUE. If multiple entries with NAME exist, the
+ first entry is updated. */
+gpg_error_t pkc_set (pkc_t pk, const char *name, const char *value);
+
+/* Delete the given entry from PK. */
+void pkc_delete (pkc_t pk, pke_t pke);
+
+
+
+/* Private key handling. */
+
+/* Get the private key. */
+gpg_error_t pkc_get_private_key (pkc_t pk, gcry_sexp_t *retsexp);
+
+/* Set the private key. */
+gpg_error_t pkc_set_private_key (pkc_t pk, gcry_sexp_t sexp);
+
+
+
+/* Parsing and serialization. */
+
+/* Parse STREAM and return a newly allocated private key container
+ structure in RESULT. If ERRLINEP is given, the line number the
+ parser was last considering is stored there. */
+gpg_error_t pkc_parse (pkc_t *result, int *errlinep, estream_t stream);
+
+/* Write a representation of PK to STREAM. */
+gpg_error_t pkc_write (pkc_t pk, estream_t stream);
+
+#endif /* GNUPG_COMMON_PRIVATE_KEYS_H */
diff --git a/common/t-private-keys.c b/common/t-private-keys.c
new file mode 100644
index 000000000..06415a1fa
--- /dev/null
+++ b/common/t-private-keys.c
@@ -0,0 +1,543 @@
+/* t-private-keys.c - Module test for private-keys.c
+ * Copyright (C) 2016 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "util.h"
+#include "private-keys.h"
+
+static int verbose;
+
+void
+test_getting_values (pkc_t pk)
+{
+ pke_t e;
+
+ e = pkc_lookup (pk, "Comment:");
+ assert (e);
+
+ /* Names are case-insensitive. */
+ e = pkc_lookup (pk, "comment:");
+ assert (e);
+ e = pkc_lookup (pk, "COMMENT:");
+ assert (e);
+
+ e = pkc_lookup (pk, "SomeOtherName:");
+ assert (e);
+}
+
+
+void
+test_key_extraction (pkc_t pk)
+{
+ gpg_error_t err;
+ gcry_sexp_t key;
+
+ err = pkc_get_private_key (pk, &key);
+ assert (err == 0);
+ assert (key);
+
+ if (verbose)
+ gcry_sexp_dump (key);
+
+ gcry_sexp_release (key);
+}
+
+
+void
+test_iteration (pkc_t pk)
+{
+ int i;
+ pke_t e;
+
+ i = 0;
+ for (e = pkc_first (pk); e; e = pke_next (e))
+ i++;
+ assert (i == 4);
+
+ i = 0;
+ for (e = pkc_lookup (pk, "Comment:");
+ e;
+ e = pke_next_value (e, "Comment:"))
+ i++;
+ assert (i == 3);
+}
+
+
+void
+test_whitespace (pkc_t pk)
+{
+ pke_t e;
+
+ e = pkc_lookup (pk, "One:");
+ assert (e);
+ assert (strcmp (pke_value (e), "WithoutWhitespace") == 0);
+
+ e = pkc_lookup (pk, "Two:");
+ assert (e);
+ assert (strcmp (pke_value (e), "With Whitespace") == 0);
+
+ e = pkc_lookup (pk, "Three:");
+ assert (e);
+ assert (strcmp (pke_value (e),
+ "Blank lines in continuations encode newlines.\n"
+ "Next paragraph.") == 0);
+}
+
+
+struct
+{
+ char *value;
+ void (*test_func) (pkc_t);
+} tests[] =
+ {
+ {
+ "# This is a comment followed by an empty line\n"
+ "\n",
+ NULL,
+ },
+ {
+ "# This is a comment followed by two empty lines, Windows style\r\n"
+ "\r\n"
+ "\r\n",
+ NULL,
+ },
+ {
+ "# Some name,value pairs\n"
+ "Comment: Some comment.\n"
+ "SomeOtherName: Some value.\n",
+ test_getting_values,
+ },
+ {
+ " # Whitespace is preserved as much as possible\r\n"
+ "Comment:Some comment.\n"
+ "SomeOtherName: Some value. \n",
+ test_getting_values,
+ },
+ {
+ "# Values may be continued in the next line as indicated by leading\n"
+ "# space\n"
+ "Comment: Some rather long\n"
+ " comment that is continued in the next line.\n"
+ "\n"
+ " Blank lines with or without whitespace are allowed within\n"
+ " continuations to allow paragraphs.\n"
+ "SomeOtherName: Some value.\n",
+ test_getting_values,
+ },
+ {
+ "# Names may be given multiple times forming an array of values\n"
+ "Comment: Some comment, element 0.\n"
+ "Comment: Some comment, element 1.\n"
+ "Comment: Some comment, element 2.\n"
+ "SomeOtherName: Some value.\n",
+ test_iteration,
+ },
+ {
+ "# One whitespace at the beginning of a continuation is swallowed.\n"
+ "One: Without\n"
+ " Whitespace\n"
+ "Two: With\n"
+ " Whitespace\n"
+ "Three: Blank lines in continuations encode newlines.\n"
+ "\n"
+ " Next paragraph.\n",
+ test_whitespace,
+ },
+ {
+ "Description: Key to sign all GnuPG released tarballs.\n"
+ " The key is actually stored on a smart card.\n"
+ "Use-for-ssh: yes\n"
+ "OpenSSH-cert: long base64 encoded string wrapped so that this\n"
+ " key file can be easily edited with a standard editor.\n"
+ "Key: (shadowed-private-key\n"
+ " (rsa\n"
+ " (n #00AA1AD2A55FD8C8FDE9E1941772D9CC903FA43B268CB1B5A1BAFDC900\n"
+ " 2961D8AEA153424DC851EF13B83AC64FBE365C59DC1BD3E83017C90D4365B4\n"
+ " 83E02859FC13DB5842A00E969480DB96CE6F7D1C03600392B8E08EF0C01FC7\n"
+ " 19F9F9086B25AD39B4F1C2A2DF3E2BE317110CFFF21D4A11455508FE407997\n"
+ " 601260816C8422297C0637BB291C3A079B9CB38A92CE9E551F80AA0EBF4F0E\n"
+ " 72C3F250461E4D31F23A7087857FC8438324A013634563D34EFDDCBF2EA80D\n"
+ " F9662C9CCD4BEF2522D8BDFED24CEF78DC6B309317407EAC576D889F88ADA0\n"
+ " 8C4FFB480981FB68C5C6CA27503381D41018E6CDC52AAAE46B166BDC10637A\n"
+ " E186A02BA2497FDC5D1221#)\n"
+ " (e #00010001#)\n"
+ " (shadowed t1-v1\n"
+ " (#D2760001240102000005000011730000# OPENPGP.1)\n"
+ " )))\n",
+ test_key_extraction,
+ },
+ };
+
+
+static char *
+pkc_to_string (pkc_t pk)
+{
+ gpg_error_t err;
+ char *buf;
+ size_t len;
+ estream_t sink;
+
+ sink = es_fopenmem (0, "rw");
+ assert (sink);
+
+ err = pkc_write (pk, sink);
+ assert (err == 0);
+
+ len = es_ftell (sink);
+ buf = xmalloc (len+1);
+ assert (buf);
+
+ es_fseek (sink, 0, SEEK_SET);
+ es_read (sink, buf, len, NULL);
+ buf[len] = 0;
+
+ es_fclose (sink);
+ return buf;
+}
+
+
+void dummy_free (void *p) { (void) p; }
+void *dummy_realloc (void *p, size_t s) { (void) s; return p; }
+
+void
+run_tests (void)
+{
+ gpg_error_t err;
+ pkc_t pk;
+
+ int i;
+ for (i = 0; i < DIM (tests); i++)
+ {
+ estream_t source;
+ char *buf;
+ size_t len;
+
+ len = strlen (tests[i].value);
+ source = es_mopen (tests[i].value, len, len,
+ 0, dummy_realloc, dummy_free, "r");
+ assert (source);
+
+ err = pkc_parse (&pk, NULL, source);
+ assert (err == 0);
+ assert (pk);
+
+ if (verbose)
+ {
+ err = pkc_write (pk, es_stderr);
+ assert (err == 0);
+ }
+
+ buf = pkc_to_string (pk);
+ assert (memcmp (tests[i].value, buf, len) == 0);
+
+ es_fclose (source);
+ xfree (buf);
+
+ if (tests[i].test_func)
+ tests[i].test_func (pk);
+
+ pkc_release (pk);
+ }
+}
+
+
+void
+run_modification_tests (void)
+{
+ gpg_error_t err;
+ pkc_t pk;
+ pke_t e;
+ gcry_sexp_t key;
+ char *buf;
+
+ pk = pkc_new ();
+ assert (pk);
+
+ pkc_set (pk, "Foo:", "Bar");
+ buf = pkc_to_string (pk);
+ assert (strcmp (buf, "Foo: Bar\n") == 0);
+ xfree (buf);
+
+ pkc_set (pk, "Foo:", "Baz");
+ buf = pkc_to_string (pk);
+ assert (strcmp (buf, "Foo: Baz\n") == 0);
+ xfree (buf);
+
+ pkc_set (pk, "Bar:", "Bazzel");
+ buf = pkc_to_string (pk);
+ assert (strcmp (buf, "Foo: Baz\nBar: Bazzel\n") == 0);
+ xfree (buf);
+
+ pkc_add (pk, "Foo:", "Bar");
+ buf = pkc_to_string (pk);
+ assert (strcmp (buf, "Foo: Baz\nFoo: Bar\nBar: Bazzel\n") == 0);
+ xfree (buf);
+
+ pkc_add (pk, "DontExistYet:", "Bar");
+ buf = pkc_to_string (pk);
+ assert (strcmp (buf, "Foo: Baz\nFoo: Bar\nBar: Bazzel\nDontExistYet: Bar\n")
+ == 0);
+ xfree (buf);
+
+ pkc_delete (pk, pkc_lookup (pk, "DontExistYet:"));
+ buf = pkc_to_string (pk);
+ assert (strcmp (buf, "Foo: Baz\nFoo: Bar\nBar: Bazzel\n") == 0);
+ xfree (buf);
+
+ pkc_delete (pk, pke_next_value (pkc_lookup (pk, "Foo:"), "Foo:"));
+ buf = pkc_to_string (pk);
+ assert (strcmp (buf, "Foo: Baz\nBar: Bazzel\n") == 0);
+ xfree (buf);
+
+ pkc_delete (pk, pkc_lookup (pk, "Foo:"));
+ buf = pkc_to_string (pk);
+ assert (strcmp (buf, "Bar: Bazzel\n") == 0);
+ xfree (buf);
+
+ pkc_delete (pk, pkc_first (pk));
+ buf = pkc_to_string (pk);
+ assert (strcmp (buf, "") == 0);
+ xfree (buf);
+
+ pkc_set (pk, "Foo:", "A really long value spanning across multiple lines"
+ " that has to be wrapped at a convenient space.");
+ buf = pkc_to_string (pk);
+ assert (strcmp (buf, "Foo: A really long value spanning across multiple"
+ " lines that has to be\n wrapped at a convenient space.\n")
+ == 0);
+ xfree (buf);
+
+ pkc_set (pk, "Foo:", "XA really long value spanning across multiple lines"
+ " that has to be wrapped at a convenient space.");
+ buf = pkc_to_string (pk);
+ assert (strcmp (buf, "Foo: XA really long value spanning across multiple"
+ " lines that has to\n be wrapped at a convenient space.\n")
+ == 0);
+ xfree (buf);
+
+ pkc_set (pk, "Foo:", "XXXXA really long value spanning across multiple lines"
+ " that has to be wrapped at a convenient space.");
+ buf = pkc_to_string (pk);
+ assert (strcmp (buf, "Foo: XXXXA really long value spanning across multiple"
+ " lines that has\n to be wrapped at a convenient space.\n")
+ == 0);
+ xfree (buf);
+
+ pkc_set (pk, "Foo:", "Areallylongvaluespanningacrossmultiplelines"
+ "thathastobewrappedataconvenientspacethatisnotthere.");
+ buf = pkc_to_string (pk);
+ assert (strcmp (buf, "Foo: Areallylongvaluespanningacrossmultiplelinesthat"
+ "hastobewrappedataco\n nvenientspacethatisnotthere.\n")
+ == 0);
+ xfree (buf);
+ pkc_release (pk);
+
+ pk = pkc_new ();
+ assert (pk);
+
+ err = gcry_sexp_build (&key, NULL, "(hello world)");
+ assert (err == 0);
+ assert (key);
+
+ err = pkc_set_private_key (pk, key);
+ gcry_sexp_release (key);
+ assert (err == 0);
+ buf = pkc_to_string (pk);
+ assert (strcmp (buf, "Key: (hello world)\n") == 0);
+ xfree (buf);
+ pkc_release (pk);
+}
+
+
+void
+convert (const char *fname)
+{
+ gpg_error_t err;
+ estream_t source;
+ gcry_sexp_t key;
+ char *buf;
+ size_t buflen;
+ gpgrt_ssize_t nread;
+ struct stat st;
+ pkc_t pk;
+
+ source = es_fopen (fname, "rb");
+ if (source == NULL)
+ goto leave;
+
+ if (fstat (es_fileno (source), &st))
+ goto leave;
+
+ buflen = st.st_size;
+ buf = xtrymalloc (buflen+1);
+ assert (buf);
+
+ if (es_fread (buf, buflen, 1, source) != 1)
+ goto leave;
+
+ err = gcry_sexp_sscan (&key, NULL, buf, buflen);
+ if (err)
+ {
+ fprintf (stderr, "malformed s-expression in %s\n", fname);
+ exit (1);
+ }
+
+ pk = pkc_new ();
+ assert (pk);
+
+ err = pkc_set_private_key (pk, key);
+ assert (err == 0);
+
+ err = pkc_write (pk, es_stdout);
+ assert (err == 0);
+
+ return;
+
+ leave:
+ perror (fname);
+ exit (1);
+}
+
+
+void
+parse (const char *fname)
+{
+ gpg_error_t err;
+ estream_t source;
+ char *buf;
+ pkc_t pk_a, pk_b;
+ pke_t e;
+ int line;
+
+ source = es_fopen (fname, "rb");
+ if (source == NULL)
+ {
+ perror (fname);
+ exit (1);
+ }
+
+ err = pkc_parse (&pk_a, &line, source);
+ if (err)
+ {
+ fprintf (stderr, "failed to parse %s line %d: %s\n",
+ fname, line, gpg_strerror (err));
+ exit (1);
+ }
+
+ buf = pkc_to_string (pk_a);
+ xfree (buf);
+
+ pk_b = pkc_new ();
+ assert (pk_b);
+
+ for (e = pkc_first (pk_a); e; e = pke_next (e))
+ {
+ gcry_sexp_t key = NULL;
+
+ if (strcasecmp (pke_name (e), "Key:") == 0)
+ {
+ err = pkc_get_private_key (pk_a, &key);
+ if (err)
+ key = NULL;
+ }
+
+ if (key)
+ {
+ err = pkc_set_private_key (pk_b, key);
+ assert (err == 0);
+ }
+ else
+ {
+ err = pkc_add (pk_b, pke_name (e), pke_value (e));
+ assert (err == 0);
+ }
+ }
+
+ buf = pkc_to_string (pk_b);
+ if (verbose)
+ fprintf (stdout, "%s", buf);
+ xfree (buf);
+}
+
+
+void
+print_usage (void)
+{
+ fprintf (stderr,
+ "usage: t-private-keys [--verbose]"
+ " [--convert <private-key-file>"
+ " || --parse <extended-private-key-file>]\n");
+ exit (2);
+}
+
+
+int
+main (int argc, char **argv)
+{
+ enum { TEST, CONVERT, PARSE } command = TEST;
+
+ if (argc)
+ { argc--; argv++; }
+ if (argc && !strcmp (argv[0], "--verbose"))
+ {
+ verbose = 1;
+ argc--; argv++;
+ }
+
+ if (argc && !strcmp (argv[0], "--convert"))
+ {
+ command = CONVERT;
+ argc--; argv++;
+ if (argc != 1)
+ print_usage ();
+ }
+
+ if (argc && !strcmp (argv[0], "--parse"))
+ {
+ command = PARSE;
+ argc--; argv++;
+ if (argc != 1)
+ print_usage ();
+ }
+
+ switch (command)
+ {
+ case TEST:
+ run_tests ();
+ run_modification_tests ();
+ break;
+
+ case CONVERT:
+ convert (*argv);
+ break;
+
+ case PARSE:
+ parse (*argv);
+ break;
+ }
+
+ return 0;
+}
diff --git a/common/util.h b/common/util.h
index 6410b11d4..466c519bd 100644
--- a/common/util.h
+++ b/common/util.h
@@ -333,6 +333,9 @@ int _gnupg_isatty (int fd);
/*-- Macros to replace ctype ones to avoid locale problems. --*/
#define spacep(p) (*(p) == ' ' || *(p) == '\t')
#define digitp(p) (*(p) >= '0' && *(p) <= '9')
+#define alphap(p) ((*(p) >= 'A' && *(p) <= 'Z') \
+ || (*(p) >= 'a' && *(p) <= 'z'))
+#define alnump(p) (alphap (p) || digitp (p))
#define hexdigitp(a) (digitp (a) \
|| (*(a) >= 'A' && *(a) <= 'F') \
|| (*(a) >= 'a' && *(a) <= 'f'))