mirror of
git://git.gnupg.org/gnupg.git
synced 2025-01-23 15:07:03 +01:00
gpg: New command --export-ssh-key
* g10/export.c: Include membuf.h and host2net.h. (key_to_sshblob): New. (export_ssh_key): New. * g10/gpg.c (aExportSshKey): New. (opts): Add command. (main): Implement that command. -- GnuPG-bug-id: 2212 I have done only a few tests rights now and the ECDSA curves do not yet work. However ssh-keygen -l accept RSA and ed25519 keys exported using this command. Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
parent
34bca9cd4b
commit
4970868d8d
296
g10/export.c
296
g10/export.c
@ -1,7 +1,7 @@
|
|||||||
/* export.c - Export keys in the OpenPGP defined format.
|
/* export.c - Export keys in the OpenPGP defined format.
|
||||||
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
|
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
|
||||||
* 2005, 2010 Free Software Foundation, Inc.
|
* 2005, 2010 Free Software Foundation, Inc.
|
||||||
* Copyright (C) 1998-2015 Werner Koch
|
* Copyright (C) 1998-2016 Werner Koch
|
||||||
*
|
*
|
||||||
* This file is part of GnuPG.
|
* This file is part of GnuPG.
|
||||||
*
|
*
|
||||||
@ -34,6 +34,8 @@
|
|||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "i18n.h"
|
#include "i18n.h"
|
||||||
|
#include "membuf.h"
|
||||||
|
#include "host2net.h"
|
||||||
#include "trustdb.h"
|
#include "trustdb.h"
|
||||||
#include "call-agent.h"
|
#include "call-agent.h"
|
||||||
|
|
||||||
@ -1350,3 +1352,295 @@ do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
|
|||||||
log_info(_("WARNING: nothing exported\n"));
|
log_info(_("WARNING: nothing exported\n"));
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static gpg_error_t
|
||||||
|
key_to_sshblob (membuf_t *mb, const char *identifier, ...)
|
||||||
|
{
|
||||||
|
va_list arg_ptr;
|
||||||
|
gpg_error_t err = 0;
|
||||||
|
unsigned char nbuf[4];
|
||||||
|
unsigned char *buf;
|
||||||
|
size_t buflen;
|
||||||
|
gcry_mpi_t a;
|
||||||
|
|
||||||
|
ulongtobuf (nbuf, (ulong)strlen (identifier));
|
||||||
|
put_membuf (mb, nbuf, 4);
|
||||||
|
put_membuf_str (mb, identifier);
|
||||||
|
va_start (arg_ptr, identifier);
|
||||||
|
while ((a = va_arg (arg_ptr, gcry_mpi_t)))
|
||||||
|
{
|
||||||
|
err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, &buf, &buflen, a);
|
||||||
|
if (err)
|
||||||
|
break;
|
||||||
|
if (!strcmp (identifier, "ssh-ed25519")
|
||||||
|
&& buflen > 5 && buf[4] == 0x40)
|
||||||
|
{
|
||||||
|
/* We need to strip our 0x40 prefix. */
|
||||||
|
put_membuf (mb, "\x00\x00\x00\x20", 4);
|
||||||
|
put_membuf (mb, buf+5, buflen-5);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
put_membuf (mb, buf, buflen);
|
||||||
|
gcry_free (buf);
|
||||||
|
}
|
||||||
|
va_end (arg_ptr);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Export the key identified by USERID in the SSH public key format.
|
||||||
|
The function exports the latest subkey with Authentication
|
||||||
|
capability unless the '!' suffix is used to export a specific
|
||||||
|
key. */
|
||||||
|
gpg_error_t
|
||||||
|
export_ssh_key (ctrl_t ctrl, const char *userid)
|
||||||
|
{
|
||||||
|
gpg_error_t err;
|
||||||
|
kbnode_t keyblock = NULL;
|
||||||
|
KEYDB_SEARCH_DESC desc;
|
||||||
|
u32 latest_date;
|
||||||
|
u32 curtime = make_timestamp ();
|
||||||
|
kbnode_t latest_key, node;
|
||||||
|
PKT_public_key *pk;
|
||||||
|
const char *identifier;
|
||||||
|
membuf_t mb;
|
||||||
|
estream_t fp = NULL;
|
||||||
|
struct b64state b64_state;
|
||||||
|
const char *fname = "-";
|
||||||
|
|
||||||
|
init_membuf (&mb, 4096);
|
||||||
|
|
||||||
|
/* We need to know whether the key has been specified using the
|
||||||
|
exact syntax ('!' suffix). Thus we need to run a
|
||||||
|
classify_user_id on our own. */
|
||||||
|
err = classify_user_id (userid, &desc, 1);
|
||||||
|
|
||||||
|
/* Get the public key. */
|
||||||
|
if (!err)
|
||||||
|
{
|
||||||
|
getkey_ctx_t getkeyctx;
|
||||||
|
|
||||||
|
err = get_pubkey_byname (ctrl, &getkeyctx, NULL, userid, &keyblock,
|
||||||
|
NULL,
|
||||||
|
0 /* Only usable keys or given exact. */,
|
||||||
|
1 /* No AKL lookup. */);
|
||||||
|
if (!err)
|
||||||
|
{
|
||||||
|
err = getkey_next (getkeyctx, NULL, NULL);
|
||||||
|
if (!err)
|
||||||
|
err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
|
||||||
|
else if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
|
||||||
|
err = 0;
|
||||||
|
}
|
||||||
|
getkey_end (getkeyctx);
|
||||||
|
}
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The finish_lookup code in getkey.c does not handle auth keys,
|
||||||
|
thus we have to duplicate the code here to find the latest
|
||||||
|
subkey. However, if the key has been found using an exact match
|
||||||
|
('!' notation) we use that key without any further checks and
|
||||||
|
even allow the use of the primary key. */
|
||||||
|
latest_date = 0;
|
||||||
|
latest_key = NULL;
|
||||||
|
for (node = keyblock; node; node = node->next)
|
||||||
|
{
|
||||||
|
if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|
||||||
|
|| node->pkt->pkttype == PKT_PUBLIC_KEY)
|
||||||
|
&& node->pkt->pkt.public_key->flags.exact)
|
||||||
|
{
|
||||||
|
latest_key = node;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!latest_key)
|
||||||
|
{
|
||||||
|
for (node = keyblock; node; node = node->next)
|
||||||
|
{
|
||||||
|
if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
pk = node->pkt->pkt.public_key;
|
||||||
|
if (DBG_LOOKUP)
|
||||||
|
log_debug ("\tchecking subkey %08lX\n",
|
||||||
|
(ulong) keyid_from_pk (pk, NULL));
|
||||||
|
if (!(pk->pubkey_usage & PUBKEY_USAGE_AUTH))
|
||||||
|
{
|
||||||
|
if (DBG_LOOKUP)
|
||||||
|
log_debug ("\tsubkey not usable for authentication\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!pk->flags.valid)
|
||||||
|
{
|
||||||
|
if (DBG_LOOKUP)
|
||||||
|
log_debug ("\tsubkey not valid\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (pk->flags.revoked)
|
||||||
|
{
|
||||||
|
if (DBG_LOOKUP)
|
||||||
|
log_debug ("\tsubkey has been revoked\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (pk->has_expired)
|
||||||
|
{
|
||||||
|
if (DBG_LOOKUP)
|
||||||
|
log_debug ("\tsubkey has expired\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (pk->timestamp > curtime && !opt.ignore_valid_from)
|
||||||
|
{
|
||||||
|
if (DBG_LOOKUP)
|
||||||
|
log_debug ("\tsubkey not yet valid\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (DBG_LOOKUP)
|
||||||
|
log_debug ("\tsubkey might be fine\n");
|
||||||
|
/* In case a key has a timestamp of 0 set, we make sure that it
|
||||||
|
is used. A better change would be to compare ">=" but that
|
||||||
|
might also change the selected keys and is as such a more
|
||||||
|
intrusive change. */
|
||||||
|
if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date))
|
||||||
|
{
|
||||||
|
latest_date = pk->timestamp;
|
||||||
|
latest_key = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!latest_key)
|
||||||
|
{
|
||||||
|
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
|
||||||
|
log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err));
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
pk = latest_key->pkt->pkt.public_key;
|
||||||
|
if (DBG_LOOKUP)
|
||||||
|
log_debug ("\tusing key %08lX\n", (ulong) keyid_from_pk (pk, NULL));
|
||||||
|
|
||||||
|
switch (pk->pubkey_algo)
|
||||||
|
{
|
||||||
|
case PUBKEY_ALGO_DSA:
|
||||||
|
identifier = "ssh-dss";
|
||||||
|
err = key_to_sshblob (&mb, identifier,
|
||||||
|
pk->pkey[0], pk->pkey[1], pk->pkey[2], pk->pkey[3],
|
||||||
|
NULL);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PUBKEY_ALGO_RSA:
|
||||||
|
case PUBKEY_ALGO_RSA_S:
|
||||||
|
identifier = "ssh-rsa";
|
||||||
|
err = key_to_sshblob (&mb, identifier, pk->pkey[1], pk->pkey[0], NULL);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PUBKEY_ALGO_ECDSA:
|
||||||
|
{
|
||||||
|
char *curveoid;
|
||||||
|
const char *curve;
|
||||||
|
|
||||||
|
curveoid = openpgp_oid_to_str (pk->pkey[0]);
|
||||||
|
if (!curveoid)
|
||||||
|
err = gpg_error_from_syserror ();
|
||||||
|
else if (!(curve = openpgp_oid_to_curve (curveoid, 0)))
|
||||||
|
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!strcmp (curve, "nistp256"))
|
||||||
|
identifier = "ecdsa-sha2-nistp256";
|
||||||
|
else if (!strcmp (curve, "nistp384"))
|
||||||
|
identifier = "ecdsa-sha2-nistp384";
|
||||||
|
else if (!strcmp (curve, "nistp521"))
|
||||||
|
identifier = "ecdsa-sha2-nistp521";
|
||||||
|
else
|
||||||
|
identifier = NULL;
|
||||||
|
|
||||||
|
if (!identifier)
|
||||||
|
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
|
||||||
|
else
|
||||||
|
err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL);
|
||||||
|
}
|
||||||
|
xfree (curveoid);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PUBKEY_ALGO_EDDSA:
|
||||||
|
if (!openpgp_oid_is_ed25519 (pk->pkey[0]))
|
||||||
|
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
identifier = "ssh-ed25519";
|
||||||
|
err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PUBKEY_ALGO_ELGAMAL_E:
|
||||||
|
case PUBKEY_ALGO_ELGAMAL:
|
||||||
|
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
err = GPG_ERR_PUBKEY_ALGO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
goto leave;
|
||||||
|
|
||||||
|
if (opt.outfile && *opt.outfile && strcmp (opt.outfile, "-"))
|
||||||
|
fp = es_fopen ((fname = opt.outfile), "w");
|
||||||
|
else
|
||||||
|
fp = es_stdout;
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
err = gpg_error_from_syserror ();
|
||||||
|
log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
es_fprintf (fp, "%s ", identifier);
|
||||||
|
err = b64enc_start_es (&b64_state, fp, "");
|
||||||
|
if (err)
|
||||||
|
goto leave;
|
||||||
|
{
|
||||||
|
void *blob;
|
||||||
|
size_t bloblen;
|
||||||
|
|
||||||
|
blob = get_membuf (&mb, &bloblen);
|
||||||
|
if (!blob)
|
||||||
|
err = gpg_error_from_syserror ();
|
||||||
|
else
|
||||||
|
err = b64enc_write (&b64_state, blob, bloblen);
|
||||||
|
xfree (blob);
|
||||||
|
if (err)
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
err = b64enc_finish (&b64_state);
|
||||||
|
if (err)
|
||||||
|
goto leave;
|
||||||
|
es_fprintf (fp, " openpgp:0x%08lX\n", (ulong)keyid_from_pk (pk, NULL));
|
||||||
|
|
||||||
|
if (es_ferror (fp))
|
||||||
|
err = gpg_error_from_syserror ();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (es_fclose (fp))
|
||||||
|
err = gpg_error_from_syserror ();
|
||||||
|
fp = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
|
||||||
|
|
||||||
|
leave:
|
||||||
|
es_fclose (fp);
|
||||||
|
xfree (get_membuf (&mb, NULL));
|
||||||
|
release_kbnode (keyblock);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
16
g10/gpg.c
16
g10/gpg.c
@ -1,6 +1,6 @@
|
|||||||
/* gpg.c - The GnuPG utility (main for gpg)
|
/* gpg.c - The GnuPG utility (main for gpg)
|
||||||
* Copyright (C) 1998-2011 Free Software Foundation, Inc.
|
* Copyright (C) 1998-2011 Free Software Foundation, Inc.
|
||||||
* Copyright (C) 1997-2014 Werner Koch
|
* Copyright (C) 1997-2016 Werner Koch
|
||||||
* Copyright (C) 2015 g10 Code GmbH
|
* Copyright (C) 2015 g10 Code GmbH
|
||||||
*
|
*
|
||||||
* This file is part of GnuPG.
|
* This file is part of GnuPG.
|
||||||
@ -141,6 +141,7 @@ enum cmd_and_opt_values
|
|||||||
aExport,
|
aExport,
|
||||||
aExportSecret,
|
aExportSecret,
|
||||||
aExportSecretSub,
|
aExportSecretSub,
|
||||||
|
aExportSshKey,
|
||||||
aCheckKeys,
|
aCheckKeys,
|
||||||
aGenRevoke,
|
aGenRevoke,
|
||||||
aDesigRevoke,
|
aDesigRevoke,
|
||||||
@ -453,6 +454,7 @@ static ARGPARSE_OPTS opts[] = {
|
|||||||
ARGPARSE_c (aFetchKeys, "fetch-keys" , "@" ),
|
ARGPARSE_c (aFetchKeys, "fetch-keys" , "@" ),
|
||||||
ARGPARSE_c (aExportSecret, "export-secret-keys" , "@" ),
|
ARGPARSE_c (aExportSecret, "export-secret-keys" , "@" ),
|
||||||
ARGPARSE_c (aExportSecretSub, "export-secret-subkeys" , "@" ),
|
ARGPARSE_c (aExportSecretSub, "export-secret-subkeys" , "@" ),
|
||||||
|
ARGPARSE_c (aExportSshKey, "export-ssh-key", "@" ),
|
||||||
ARGPARSE_c (aImport, "import", N_("import/merge keys")),
|
ARGPARSE_c (aImport, "import", N_("import/merge keys")),
|
||||||
ARGPARSE_c (aFastImport, "fast-import", "@"),
|
ARGPARSE_c (aFastImport, "fast-import", "@"),
|
||||||
#ifdef ENABLE_CARD_SUPPORT
|
#ifdef ENABLE_CARD_SUPPORT
|
||||||
@ -2400,6 +2402,7 @@ main (int argc, char **argv)
|
|||||||
case aListSigs:
|
case aListSigs:
|
||||||
case aExportSecret:
|
case aExportSecret:
|
||||||
case aExportSecretSub:
|
case aExportSecretSub:
|
||||||
|
case aExportSshKey:
|
||||||
case aSym:
|
case aSym:
|
||||||
case aClearsign:
|
case aClearsign:
|
||||||
case aGenRevoke:
|
case aGenRevoke:
|
||||||
@ -4184,6 +4187,17 @@ main (int argc, char **argv)
|
|||||||
free_strlist(sl);
|
free_strlist(sl);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case aExportSshKey:
|
||||||
|
if (argc != 1)
|
||||||
|
wrong_args ("--export-ssh-key <user-id>");
|
||||||
|
rc = export_ssh_key (ctrl, argv[0]);
|
||||||
|
if (rc)
|
||||||
|
{
|
||||||
|
write_status_failure ("export-ssh-key", rc);
|
||||||
|
log_error (_("export as ssh key failed: %s\n"), gpg_strerror (rc));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case aSearchKeys:
|
case aSearchKeys:
|
||||||
sl = NULL;
|
sl = NULL;
|
||||||
for (; argc; argc--, argv++)
|
for (; argc; argc--, argv++)
|
||||||
|
@ -368,6 +368,8 @@ gpg_error_t receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd,
|
|||||||
char **cache_nonce_addr, const char *hexgrip,
|
char **cache_nonce_addr, const char *hexgrip,
|
||||||
PKT_public_key *pk);
|
PKT_public_key *pk);
|
||||||
|
|
||||||
|
gpg_error_t export_ssh_key (ctrl_t ctrl, const char *userid);
|
||||||
|
|
||||||
/*-- dearmor.c --*/
|
/*-- dearmor.c --*/
|
||||||
int dearmor_file( const char *fname );
|
int dearmor_file( const char *fname );
|
||||||
int enarmor_file( const char *fname );
|
int enarmor_file( const char *fname );
|
||||||
|
Loading…
x
Reference in New Issue
Block a user