diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/Makefile.am | 7 | ||||
-rw-r--r-- | common/private-keys.c | 740 | ||||
-rw-r--r-- | common/private-keys.h | 109 | ||||
-rw-r--r-- | common/t-private-keys.c | 543 | ||||
-rw-r--r-- | common/util.h | 3 |
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')) |