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.
|
||||
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
|
||||
* 2005, 2010 Free Software Foundation, Inc.
|
||||
* Copyright (C) 1998-2015 Werner Koch
|
||||
* Copyright (C) 1998-2016 Werner Koch
|
||||
*
|
||||
* This file is part of GnuPG.
|
||||
*
|
||||
@ -34,6 +34,8 @@
|
||||
#include "util.h"
|
||||
#include "main.h"
|
||||
#include "i18n.h"
|
||||
#include "membuf.h"
|
||||
#include "host2net.h"
|
||||
#include "trustdb.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"));
|
||||
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)
|
||||
* 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
|
||||
*
|
||||
* This file is part of GnuPG.
|
||||
@ -141,6 +141,7 @@ enum cmd_and_opt_values
|
||||
aExport,
|
||||
aExportSecret,
|
||||
aExportSecretSub,
|
||||
aExportSshKey,
|
||||
aCheckKeys,
|
||||
aGenRevoke,
|
||||
aDesigRevoke,
|
||||
@ -453,6 +454,7 @@ static ARGPARSE_OPTS opts[] = {
|
||||
ARGPARSE_c (aFetchKeys, "fetch-keys" , "@" ),
|
||||
ARGPARSE_c (aExportSecret, "export-secret-keys" , "@" ),
|
||||
ARGPARSE_c (aExportSecretSub, "export-secret-subkeys" , "@" ),
|
||||
ARGPARSE_c (aExportSshKey, "export-ssh-key", "@" ),
|
||||
ARGPARSE_c (aImport, "import", N_("import/merge keys")),
|
||||
ARGPARSE_c (aFastImport, "fast-import", "@"),
|
||||
#ifdef ENABLE_CARD_SUPPORT
|
||||
@ -2400,6 +2402,7 @@ main (int argc, char **argv)
|
||||
case aListSigs:
|
||||
case aExportSecret:
|
||||
case aExportSecretSub:
|
||||
case aExportSshKey:
|
||||
case aSym:
|
||||
case aClearsign:
|
||||
case aGenRevoke:
|
||||
@ -4184,6 +4187,17 @@ main (int argc, char **argv)
|
||||
free_strlist(sl);
|
||||
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:
|
||||
sl = NULL;
|
||||
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,
|
||||
PKT_public_key *pk);
|
||||
|
||||
gpg_error_t export_ssh_key (ctrl_t ctrl, const char *userid);
|
||||
|
||||
/*-- dearmor.c --*/
|
||||
int dearmor_file( const char *fname );
|
||||
int enarmor_file( const char *fname );
|
||||
|
Loading…
x
Reference in New Issue
Block a user