common: Add support for the new extended private key format.

* agent/findkey.c (write_extended_private_key): New function.
(agent_write_private_key): Detect if an existing file is in extended
format and update the key within if it is.
(read_key_file): Handle the new format.
* agent/keyformat.txt: Document the new format.
* common/Makefile.am: Add the new files.
* common/private-keys.c: New file.
* common/private-keys.h: Likewise.
* common/t-private-keys.c: Likewise.
* common/util.h (alphap, alnump): New macros.
* tests/migrations: Add test demonstrating that we can cope with the
new format.

--
GnuPG 2.3+ will use a new format to store private keys that is both
more flexible and easier to read and edit by human beings.  The new
format stores name,value-pairs using the common mail and http header
convention.

This patch adds the parser and support code and prepares GnuPG 2.1 for
the new format.

Signed-off-by: Justus Winter <justus@g10code.com>
This commit is contained in:
Justus Winter 2016-04-08 19:21:12 +02:00
parent c6d1f2f08c
commit 12af2630cf
14 changed files with 1831 additions and 13 deletions

View File

@ -35,6 +35,7 @@
#include "agent.h"
#include "i18n.h"
#include "../common/ssh-utils.h"
#include "../common/private-keys.h"
#ifndef O_BINARY
#define O_BINARY 0
@ -51,6 +52,75 @@ struct try_unprotect_arg_s
};
static gpg_error_t
write_extended_private_key (char *fname, estream_t fp,
const void *buf, size_t len)
{
gpg_error_t err;
pkc_t pk = NULL;
gcry_sexp_t key = NULL;
int remove = 0;
int line;
err = pkc_parse (&pk, &line, fp);
if (err)
{
log_error ("error parsing '%s' line %d: %s\n",
fname, line, gpg_strerror (err));
goto leave;
}
err = gcry_sexp_sscan (&key, NULL, buf, len);
if (err)
goto leave;
err = pkc_set_private_key (pk, key);
if (err)
goto leave;
err = es_fseek (fp, 0, SEEK_SET);
if (err)
goto leave;
err = pkc_write (pk, fp);
if (err)
{
log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
remove = 1;
goto leave;
}
if (ftruncate (es_fileno (fp), es_ftello (fp)))
{
err = gpg_error_from_syserror ();
log_error ("error truncating '%s': %s\n", fname, gpg_strerror (err));
remove = 1;
goto leave;
}
if (es_fclose (fp))
{
err = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
remove = 1;
goto leave;
}
else
fp = NULL;
bump_key_eventcounter ();
leave:
if (fp)
es_fclose (fp);
if (remove)
gnupg_remove (fname);
xfree (fname);
gcry_sexp_release (key);
pkc_release (pk);
return err;
}
/* Write an S-expression formatted key to our key storage. With FORCE
passed as true an existing key with the given GRIP will get
overwritten. */
@ -77,7 +147,7 @@ agent_write_private_key (const unsigned char *grip,
return gpg_error (GPG_ERR_EEXIST);
}
fp = es_fopen (fname, force? "wb,mode=-rw" : "wbx,mode=-rw");
fp = es_fopen (fname, force? "rb+,mode=-rw" : "wbx,mode=-rw");
if (!fp)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
@ -86,6 +156,38 @@ agent_write_private_key (const unsigned char *grip,
return tmperr;
}
/* See if an existing key is in extended format. */
if (force)
{
gpg_error_t rc;
char first;
if (es_fread (&first, 1, 1, fp) != 1)
{
rc = gpg_error_from_syserror ();
log_error ("error reading first byte from '%s': %s\n",
fname, strerror (errno));
xfree (fname);
es_fclose (fp);
return rc;
}
rc = es_fseek (fp, 0, SEEK_SET);
if (rc)
{
log_error ("error seeking in '%s': %s\n", fname, strerror (errno));
xfree (fname);
es_fclose (fp);
return rc;
}
if (first != '(')
{
/* Key is in extended format. */
return write_extended_private_key (fname, fp, buffer, length);
}
}
if (es_fwrite (buffer, length, 1, fp) != 1)
{
gpg_error_t tmperr = gpg_error_from_syserror ();
@ -95,6 +197,18 @@ agent_write_private_key (const unsigned char *grip,
xfree (fname);
return tmperr;
}
/* When force is given, the file might have to be truncated. */
if (force && ftruncate (es_fileno (fp), es_ftello (fp)))
{
gpg_error_t tmperr = gpg_error_from_syserror ();
log_error ("error truncating '%s': %s\n", fname, gpg_strerror (tmperr));
es_fclose (fp);
gnupg_remove (fname);
xfree (fname);
return tmperr;
}
if (es_fclose (fp))
{
gpg_error_t tmperr = gpg_error_from_syserror ();
@ -531,6 +645,7 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result)
size_t buflen, erroff;
gcry_sexp_t s_skey;
char hexgrip[40+4+1];
char first;
*result = NULL;
@ -548,6 +663,50 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result)
return rc;
}
if (es_fread (&first, 1, 1, fp) != 1)
{
rc = gpg_error_from_syserror ();
log_error ("error reading first byte from '%s': %s\n",
fname, strerror (errno));
xfree (fname);
es_fclose (fp);
return rc;
}
rc = es_fseek (fp, 0, SEEK_SET);
if (rc)
{
log_error ("error seeking in '%s': %s\n", fname, strerror (errno));
xfree (fname);
es_fclose (fp);
return rc;
}
if (first != '(')
{
/* Key is in extended format. */
pkc_t pk;
int line;
rc = pkc_parse (&pk, &line, fp);
es_fclose (fp);
if (rc)
log_error ("error parsing '%s' line %d: %s\n",
fname, line, gpg_strerror (rc));
else
{
rc = pkc_get_private_key (pk, result);
pkc_release (pk);
if (rc)
log_error ("error getting private key from '%s': %s\n",
fname, gpg_strerror (rc));
}
xfree (fname);
return rc;
}
if (fstat (es_fileno (fp), &st))
{
rc = gpg_error_from_syserror ();

View File

@ -16,7 +16,71 @@ and should have permissions 700.
The secret keys are stored in files with a name matching the
hexadecimal representation of the keygrip[2] and suffixed with ".key".
* Unprotected Private Key Format
* Extended Private Key Format
GnuPG 2.3+ will use a new format to store private keys that is both
more flexible and easier to read and edit by human beings. The new
format stores name,value-pairs using the common mail and http header
convention. Example (here indented with two spaces):
Description: Key to sign all GnuPG released tarballs.
The key is actually stored on a smart card.
Use-for-ssh: yes
OpenSSH-cert: long base64 encoded string wrapped so that this
key file can be easily edited with a standard editor.
Key: (shadowed-private-key
(rsa
(n #00AA1AD2A55FD8C8FDE9E1941772D9CC903FA43B268CB1B5A1BAFDC900
2961D8AEA153424DC851EF13B83AC64FBE365C59DC1BD3E83017C90D4365B4
83E02859FC13DB5842A00E969480DB96CE6F7D1C03600392B8E08EF0C01FC7
19F9F9086B25AD39B4F1C2A2DF3E2BE317110CFFF21D4A11455508FE407997
601260816C8422297C0637BB291C3A079B9CB38A92CE9E551F80AA0EBF4F0E
72C3F250461E4D31F23A7087857FC8438324A013634563D34EFDDCBF2EA80D
F9662C9CCD4BEF2522D8BDFED24CEF78DC6B309317407EAC576D889F88ADA0
8C4FFB480981FB68C5C6CA27503381D41018E6CDC52AAAE46B166BDC10637A
E186A02BA2497FDC5D1221#)
(e #00010001#)
(shadowed t1-v1
(#D2760001240102000005000011730000# OPENPGP.1)
)))
GnuPG 2.2 is able to read and update keys using the new format, but
will not create new files using the new format. Furthermore, it only
makes use of the value stored under the name 'Key:'.
Keys in the extended format can be recognized by looking at the first
byte of the file. If it starts with a '(' it is a naked S-expression,
otherwise it is a key in extended format.
** Names
A name must start with a letter and end with a colon. Valid
characters are all ASCII letters, numbers and the hyphen. Comparison
of names is done case insensitively. Names may be used several times
to represent an array of values.
The name "Key:" is special in that it may occur only once and the
associated value holds the actual S-expression with the cryptographic
key. The S-expression is formatted using the 'Advanced Format'
(GCRYSEXP_FMT_ADVANCED) that avoids non-printable characters so that
the file can be easily inspected and edited. See section 'Private Key
Format' below for details.
** Values
Values are UTF-8 encoded strings. Values can be wrapped at any point,
and continued in the next line indicated by leading whitespace. A
continuation line with one leading space does not introduce a blank so
that the lines can be effectively concatenated. A blank line as part
of a continuation line encodes a newline.
** Comments
Lines containing only whitespace, and lines starting with whitespace
followed by '#' are considered to be comments and are ignored.
* Private Key Format
** Unprotected Private Key Format
The content of the file is an S-Expression like the ones used with
Libgcrypt. Here is an example of an unprotected file:
@ -42,7 +106,7 @@ optional but required for some operations to calculate the fingerprint
of the key. This timestamp should be a string with the number of
seconds since Epoch or an ISO time string (yyyymmddThhmmss).
* Protected Private Key Format
** Protected Private Key Format
A protected key is like this:
@ -67,7 +131,7 @@ optional; the isotimestamp is 15 bytes long (e.g. "19610711T172000").
The currently defined protection modes are:
** openpgp-s2k3-sha1-aes-cbc
*** openpgp-s2k3-sha1-aes-cbc
This describes an algorithm using using AES in CBC mode for
encryption, SHA-1 for integrity protection and the String to Key
@ -116,7 +180,7 @@ The currently defined protection modes are:
the stored one - If they don't match the integrity of the key is not
given.
** openpgp-s2k3-ocb-aes
*** openpgp-s2k3-ocb-aes
This describes an algorithm using using AES-128 in OCB mode, a nonce
of 96 bit, a taglen of 128 bit, and the String to Key algorithm 3
@ -154,7 +218,7 @@ The currently defined protection modes are:
(protected-at "18950523T000000")
)
** openpgp-native
*** openpgp-native
This is a wrapper around the OpenPGP Private Key Transport format
which resembles the standard OpenPGP format and allows the use of an
@ -191,7 +255,7 @@ The currently defined protection modes are:
(uri http://foo.bar x-foo:whatever_you_want)
(comment whatever))
* Shadowed Private Key Format
** Shadowed Private Key Format
To keep track of keys stored on IC cards we use a third format for
private kyes which are called shadow keys as they are only a reference
@ -219,7 +283,7 @@ readers don't allow passing a variable length PIN.
More items may be added to the list.
* OpenPGP Private Key Transfer Format
** OpenPGP Private Key Transfer Format
This format is used to transfer keys between gpg and gpg-agent.
@ -251,7 +315,7 @@ This format is used to transfer keys between gpg and gpg-agent.
* S2KSALT is the 8 byte salt
* S2KCOUNT is the count value from RFC-4880.
* Persistent Passphrase Format
** Persistent Passphrase Format
Note: That this has not yet been implemented.

View File

@ -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

740
common/private-keys.c Normal file
View File

@ -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;
}

109
common/private-keys.h Normal file
View File

@ -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 */

543
common/t-private-keys.c Normal file
View File

@ -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;
}

View File

@ -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'))

View File

@ -28,11 +28,17 @@ AM_CFLAGS =
TESTS_ENVIRONMENT = GPG_AGENT_INFO= LC_ALL=C
TESTS = from-classic.test
TESTS = from-classic.test \
extended-private-key-format.test
TEST_FILES = from-classic.gpghome/pubring.gpg.asc \
from-classic.gpghome/secring.gpg.asc \
from-classic.gpghome/trustdb.gpg.asc
from-classic.gpghome/trustdb.gpg.asc \
extended-private-key-format.gpghome/trustdb.gpg.asc \
extended-private-key-format.gpghome/pubring.kbx.asc \
extended-private-key-format.gpghome/private-keys-v1.d/13FDB8809B17C5547779F9D205C45F47CE0217CE.key.asc \
extended-private-key-format.gpghome/private-keys-v1.d/343D8AF79796EE107D645A2787A9D9252F924E6F.key.asc \
extended-private-key-format.gpghome/private-keys-v1.d/8B5ABF3EF9EB8D96B91A0B8C2C4401C91C834C34.key.asc
EXTRA_DIST = $(TESTS) $(TEST_FILES)

View File

@ -0,0 +1,27 @@
-----BEGIN PGP ARMORED FILE-----
Version: GnuPG v2
Comment: Use "gpg --dearmor" for unpacking
S2V5OiAocHJpdmF0ZS1rZXkgKHJzYSAobiAjMDBBODUyNTY3NkVDRTRENzVGRTZE
MDA3M0YyQkY5OUE2RjQ5MzNDRUJERDQKIDUyOEFGNTZFNEM2MUUyRjczMTI0NzM5
MzY0NUREQUY1OEVBREQ1NjUyOUMyNTM5Nzc4MjM2NDYzREYyRDQ1ODUyMEU4MEUK
IDM0QzA1ODI0MkIzRkY4OEREMzlBODgzQjQ3NUI2NkNFQUFCQkM5OTg5RkYwMUZG
RTczNzY2MEU5QjYxQkI5REM5MTIwNUQKIDQyOEZGRkU4RjY3NUZBRUY2MTM2NThD
NzJEQTZENzUwQzBFQkM0MEFGNjIzRDIwNjY5MkM4MjUxNEM0MDREODgyNUFCNzAK
IDEwMDEjKShlICMwMTAxIykoZCAjMDBCQ0EwMDE0NDg1RkI3NkQ1MEU5QjZDQkE1
NzIxQUMxMTIxMzkwRjg2MDhENDA4NEIKIEQwNDVBODc2REYzODEwRjExNEJDMkQ2
OEVCNTUyRTYxQjAxRURCQzI0ODFGMDhDODI4MzJFMDBFMjc5RDY3QTg1MzA1NUQK
IENBRTVDMjM1Njg1MUNCRTM2RDYxMEM0RDJBQjQzRkE2NTU5ODVDNDQ2OUQxRDkx
MUUxQUZEODE3RUFBNUZFRTBGRjI2NTcKIDRDMzU5RTE3NTI4NzA1MjE5NDUzQjUx
QUVDMTBEQkY3NTYyQjA2MUQ1QzY2QzM1QkIzRjlGMEIyMjJCOUQxOTZCOSMpKHAK
ICAjMDBDMzNDNTgwNjM5OTZCRDU5NzUyQUFCREZEQUNEQUE3QjRCNjZBQTE3NTRF
RTBEODlCNzc5NEYwREU4RkY3MjRDNTQKIDlGRjExMkEzMzI5MkJCOUQ3QkNFRTc5
NEYwODAyNEMzRTU1RkQ4MjMzRjUwNzlFRDQ5OTFDNERGMjYxOEQ5IykocSAjMDAK
IERDQjU5NDVGMDBGMUFGNDM4QkQ0QzMxMUI4QkFDQTNEOURCMEFEMTY1MTk4NjUz
NDIwMzBGMURGMzA1N0U1NTMyQzQ3RjUKIDhEMzMwM0NCQTNDOEEyOTgxNEY2MTdC
N0IzREVFOThGQUFBQUVFODExQjQ5OEZBQUYyMTc3Qjc3NjkjKSh1ICMyOUZCMkQK
IEY2OUIyMzVBNDlBOTA2QjEwRUY3RDhGODFBQUVBOEFEODFFN0NEREUxRjRBNzlD
RTI0NEJGOEZDRTZERDVFQjE4MTFCMEIKIEQ1RTUxNjVCOTU3MDg1MDM2OTAxREQy
ODVBNjI4QzI5N0E3ODJEQTgxNTczQTQzRDFDMDkjKSkpCg==
=laTh
-----END PGP ARMORED FILE-----

View File

@ -0,0 +1,17 @@
-----BEGIN PGP ARMORED FILE-----
Version: GnuPG v2
Comment: Use "gpg --dearmor" for unpacking
KDExOnByaXZhdGUta2V5KDM6ZHNhKDE6cDEyOToArHGqWD0rP0Nn/c3nYELTD4m1
gqR7f2+l1ZUMdHcweYwn/fVjaJKmbR+9GzeHWP398FWYs5mCU1DIfrZLF0nJnAJ6
WRnN9TL+oub1BqqLvCmDSngRuZZ2gUX8DVmD8xTsPnDnG74QDUnvtnpDIAs32sg5
dnusstrriXD8xXgt0g8pKDE6cTIxOgC449htJbbp5rkJHvBDs4YxEIkk5ykoMTpn
MTI4Ol+ITxpSMOT5R67Bu4XWoYU7nVeYURpb6LJ8LK2CV7ygECwFdRFdukiGFB+a
TP8nF6xtuXalaBuerkKp4QXVKqOIkp7MWN2TAOOg9eERHPT//whryf49meNYMPLv
KAe60udHY76Glm+Zso+24WnEwXX2od1PHVV3CItWRb7YmhgGKSgxOnkxMjg6AgXt
40h2lpiIHTjbu6fiCBzbr5j2eQX3cNoydkRphJ66bqD+DsPW/Ag0WBCQxgRaLgMr
db64fQT+fyjbTBLbC8ytt5hpCbm/q5x3TTXDAUNjoB3CnA/tQItBy7qqq/A0d3FZ
grr6AixK58uZ4wauy8LRZCph67UZ8akcgwJkmVkpKDE6eDIwOn/Y1rjZASGMK9IG
b1y/ZDKT0zkTKSkp
=muRa
-----END PGP ARMORED FILE-----

View File

@ -0,0 +1,20 @@
-----BEGIN PGP ARMORED FILE-----
Version: GnuPG v2
Comment: Use "gpg --dearmor" for unpacking
S2V5OiAocHJpdmF0ZS1rZXkgKGVsZyAocCAjMDBDQ0Q4QjFGOURBQzc0RDgwOEND
NTJGMEQ4OTQ2NERBNTU0QzY5RDY3RjMKIDMyM0M0MkE5NUM5OTYyREY0MjEyNkVD
MEUwOTcxRjQ5QjgxMTUyOUE2QTJBRTlGMEFERUI4MzlBNjM0NjE1Q0Q1NkZBNTQK
IEY1QTBCN0VGMjVBMEUyRkU4NDNGQTJFNkUwMjFDQUI0MTE5RTYwMzk0QzlENkEz
RjdBRDRGNTc3OTZEMzY2NjlBNTEyNjYKIEMyN0E4RDFDNUE2QjQxNDFENUM4MzFF
ODQ1NDFGM0M4MTFFODkwNzg5ODAzMzgyOTVGODJCN0Y3RkQ0MzMzRUZEOTMzMTIK
IEYyQUIjKShnICMwNiMpKHkgIzM3NzNBNkQ5RUM4ODlENzZFMzI0RDZFNUVDMjFC
RDQ1Njk5ODMxQUU0RkQwQUUwMzc4MjAKIDVCQUU1QjhDRTg1RkFEQUJEN0U2QjdD
NzMwMjVDQjNENzMwRDVDNTgyOTAzNEQ3NkJFMDg1NUMyRTlGRjdBNDkyM0VGRkEK
IEYxNkE5NjY2OTQ0REJDNjI5NDgzOEZDM0YwOUZGOTY0QThEMDIzQ0I4RUJBMzMy
RkIwNTFFQTAyODIwRUU2MTIwRkZCRTYKIDJCMzZBMjAyQjNDNzUyRjlEQTc2QjJF
QzExQTY3RDJFMzVFNjZFQzEwNjM1ODdCMjI1MDBFOEE0NkQxNTdCNzUjKSh4ICMK
IDY5MTVDNkNFRDI1ODE0M0Y4OTM3QjEzMzVGNDg4N0YwMDQyQjdDNjMwMDUzOThG
OTM5NkJCODUzMjM4Q0I2IykpKQo=
=6fkh
-----END PGP ARMORED FILE-----

View File

@ -0,0 +1,39 @@
-----BEGIN PGP ARMORED FILE-----
Version: GnuPG v2
Comment: Use "gpg --dearmor" for unpacking
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
dYeJLGB5MYPnSPwADYVfNtLxsKZESLA=
=tULv
-----END PGP ARMORED FILE-----

View File

@ -0,0 +1,31 @@
-----BEGIN PGP ARMORED FILE-----
Version: GnuPG v2
Comment: Use "gpg --dearmor" for unpacking
AWdwZwMDAQUBAgAAVxi2IwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQoAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
CgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
CgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
CgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
CgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
=eBUi
-----END PGP ARMORED FILE-----

View File

@ -0,0 +1,57 @@
#!/bin/sh
# Copyright 2016 g10 Code GmbH
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved. This file is
# distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY, to the extent permitted by law; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
if [ -z "$srcdir" ]; then
echo "not called from make" >&2
exit 1
fi
unset GNUPGHOME
set -e
# (We may not use a relative name for gpg-agent.)
GPG_AGENT="$(cd ../../agent && /bin/pwd)/gpg-agent"
GPG="../../g10/gpg --no-permission-warning --no-greeting --no-secmem-warning
--batch --agent-program=${GPG_AGENT}|--debug-quick-random"
TEST="extended-private-key-format"
setup_home()
{
XGNUPGHOME="`mktemp -d`"
mkdir -p "$XGNUPGHOME/private-keys-v1.d"
for F in $srcdir/$TEST.gpghome/*.asc; do
$GPG --dearmor <"$F" >"$XGNUPGHOME/`basename $F .asc`"
done
for F in $srcdir/$TEST.gpghome/private-keys-v1.d/*.asc; do
$GPG --dearmor <"$F" >"$XGNUPGHOME/private-keys-v1.d/`basename $F .asc`"
done
chmod go-rwx $XGNUPGHOME/* $XGNUPGHOME/*/*
export GNUPGHOME="$XGNUPGHOME"
}
cleanup_home()
{
rm -rf -- "$XGNUPGHOME"
}
assert_keys_usable()
{
for KEY in C40FDECF ECABF51D; do
$GPG --list-secret-keys $KEY >/dev/null
done
}
setup_home
assert_keys_usable
cleanup_home
# XXX try changing a key, and check that the format is not changed.