From c2f1a563a50bd611aaf330142e828dfb1b43f18e Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Wed, 20 Apr 2022 13:51:28 +0100 Subject: [PATCH] Utility function ssh_key_clone(). This makes a second independent copy of an existing ssh_key, for situations where one piece of code is going to want to keep it after its current owner frees it. In order to have it work on an arbitrary ssh_key, whether public-only or a full public+private key pair, I've had to add an ssh_key query method to ask whether a private key is known. I'm surprised I haven't found a need for that before! But I suppose in most situations in an SSH client you statically know which kind of key you're dealing with. --- crypto/dsa.c | 7 +++++++ crypto/ecc-ssh.c | 17 +++++++++++++++++ crypto/rsa.c | 7 +++++++ ssh.h | 6 ++++++ utils/CMakeLists.txt | 1 + utils/ssh_key_clone.c | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 70 insertions(+) create mode 100644 utils/ssh_key_clone.c diff --git a/crypto/dsa.c b/crypto/dsa.c index 638eaf42..530bbe09 100644 --- a/crypto/dsa.c +++ b/crypto/dsa.c @@ -317,6 +317,12 @@ static void dsa_openssh_blob(ssh_key *key, BinarySink *bs) put_mp_ssh2(bs, dsa->x); } +static bool dsa_has_private(ssh_key *key) +{ + struct dsa_key *dsa = container_of(key, struct dsa_key, sshk); + return dsa->x != NULL; +} + static int dsa_pubkey_bits(const ssh_keyalg *self, ptrlen pub) { ssh_key *sshk; @@ -495,6 +501,7 @@ const ssh_keyalg ssh_dsa = { .public_blob = dsa_public_blob, .private_blob = dsa_private_blob, .openssh_blob = dsa_openssh_blob, + .has_private = dsa_has_private, .cache_str = dsa_cache_str, .components = dsa_components, .pubkey_bits = dsa_pubkey_bits, diff --git a/crypto/ecc-ssh.c b/crypto/ecc-ssh.c index 1f724ff6..9f637c2e 100644 --- a/crypto/ecc-ssh.c +++ b/crypto/ecc-ssh.c @@ -787,6 +787,12 @@ static void ecdsa_private_blob(ssh_key *key, BinarySink *bs) put_mp_ssh2(bs, ek->privateKey); } +static bool ecdsa_has_private(ssh_key *key) +{ + struct ecdsa_key *ek = container_of(key, struct ecdsa_key, sshk); + return ek->privateKey != NULL; +} + static void eddsa_private_blob(ssh_key *key, BinarySink *bs) { struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); @@ -796,6 +802,12 @@ static void eddsa_private_blob(ssh_key *key, BinarySink *bs) put_mp_le_fixedlen(bs, ek->privateKey, ek->curve->fieldBytes); } +static bool eddsa_has_private(ssh_key *key) +{ + struct eddsa_key *ek = container_of(key, struct eddsa_key, sshk); + return ek->privateKey != NULL; +} + static ssh_key *ecdsa_new_priv(const ssh_keyalg *alg, ptrlen pub, ptrlen priv) { ssh_key *sshk = ecdsa_new_pub(alg, pub); @@ -1254,6 +1266,7 @@ const ssh_keyalg ssh_ecdsa_ed25519 = { .public_blob = eddsa_public_blob, .private_blob = eddsa_private_blob, .openssh_blob = eddsa_openssh_blob, + .has_private = eddsa_has_private, .cache_str = eddsa_cache_str, .components = eddsa_components, .pubkey_bits = ec_shared_pubkey_bits, @@ -1279,6 +1292,7 @@ const ssh_keyalg ssh_ecdsa_ed448 = { .public_blob = eddsa_public_blob, .private_blob = eddsa_private_blob, .openssh_blob = eddsa_openssh_blob, + .has_private = eddsa_has_private, .cache_str = eddsa_cache_str, .components = eddsa_components, .pubkey_bits = ec_shared_pubkey_bits, @@ -1308,6 +1322,7 @@ const ssh_keyalg ssh_ecdsa_nistp256 = { .public_blob = ecdsa_public_blob, .private_blob = ecdsa_private_blob, .openssh_blob = ecdsa_openssh_blob, + .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, .pubkey_bits = ec_shared_pubkey_bits, @@ -1337,6 +1352,7 @@ const ssh_keyalg ssh_ecdsa_nistp384 = { .public_blob = ecdsa_public_blob, .private_blob = ecdsa_private_blob, .openssh_blob = ecdsa_openssh_blob, + .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, .pubkey_bits = ec_shared_pubkey_bits, @@ -1366,6 +1382,7 @@ const ssh_keyalg ssh_ecdsa_nistp521 = { .public_blob = ecdsa_public_blob, .private_blob = ecdsa_private_blob, .openssh_blob = ecdsa_openssh_blob, + .has_private = ecdsa_has_private, .cache_str = ecdsa_cache_str, .components = ecdsa_components, .pubkey_bits = ec_shared_pubkey_bits, diff --git a/crypto/rsa.c b/crypto/rsa.c index d9be3106..4087035d 100644 --- a/crypto/rsa.c +++ b/crypto/rsa.c @@ -522,6 +522,12 @@ static key_components *rsa2_components(ssh_key *key) return rsa_components(rsa); } +static bool rsa2_has_private(ssh_key *key) +{ + RSAKey *rsa = container_of(key, RSAKey, sshk); + return rsa->private_exponent != NULL; +} + static void rsa2_public_blob(ssh_key *key, BinarySink *bs) { RSAKey *rsa = container_of(key, RSAKey, sshk); @@ -869,6 +875,7 @@ static const struct ssh2_rsa_extra .public_blob = rsa2_public_blob, \ .private_blob = rsa2_private_blob, \ .openssh_blob = rsa2_openssh_blob, \ + .has_private = rsa2_has_private, \ .cache_str = rsa2_cache_str, \ .components = rsa2_components, \ .pubkey_bits = rsa2_pubkey_bits, \ diff --git a/ssh.h b/ssh.h index 3bd90dfc..b703945f 100644 --- a/ssh.h +++ b/ssh.h @@ -841,6 +841,7 @@ struct ssh_keyalg { void (*public_blob)(ssh_key *key, BinarySink *); void (*private_blob)(ssh_key *key, BinarySink *); void (*openssh_blob) (ssh_key *key, BinarySink *); + bool (*has_private) (ssh_key *key); char *(*cache_str) (ssh_key *key); key_components *(*components) (ssh_key *key); @@ -878,6 +879,8 @@ static inline void ssh_key_private_blob(ssh_key *key, BinarySink *bs) { key->vt->private_blob(key, bs); } static inline void ssh_key_openssh_blob(ssh_key *key, BinarySink *bs) { key->vt->openssh_blob(key, bs); } +static inline bool ssh_key_has_private(ssh_key *key) +{ return key->vt->has_private(key); } static inline char *ssh_key_cache_str(ssh_key *key) { return key->vt->cache_str(key); } static inline key_components *ssh_key_components(ssh_key *key) @@ -902,6 +905,9 @@ static inline const char *ssh_keyalg_alternate_ssh_id( unsigned nullkey_supported_flags(const ssh_keyalg *self); const char *nullkey_alternate_ssh_id(const ssh_keyalg *self, unsigned flags); +/* Utility functions implemented centrally */ +ssh_key *ssh_key_clone(ssh_key *key); + /* * SSH2 ECDH key exchange vtable */ diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 94192f98..9e995e04 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -49,6 +49,7 @@ add_sources_from_current_dir(utils smemclr.c smemeq.c spr_get_error_message.c + ssh_key_clone.c ssh2_pick_fingerprint.c sshutils.c strbuf.c diff --git a/utils/ssh_key_clone.c b/utils/ssh_key_clone.c new file mode 100644 index 00000000..5824bdbf --- /dev/null +++ b/utils/ssh_key_clone.c @@ -0,0 +1,32 @@ +/* + * Make a copy of an existing ssh_key object, e.g. to survive after + * the original is freed. + */ + +#include "misc.h" +#include "ssh.h" + +ssh_key *ssh_key_clone(ssh_key *key) +{ + /* + * To avoid having to add a special method in the vtable API, we + * clone by round-tripping through public and private blobs. + */ + strbuf *pub = strbuf_new_nm(); + ssh_key_public_blob(key, BinarySink_UPCAST(pub)); + + ssh_key *copy; + + if (ssh_key_has_private(key)) { + strbuf *priv = strbuf_new_nm(); + ssh_key_private_blob(key, BinarySink_UPCAST(priv)); + copy = ssh_key_new_priv(ssh_key_alg(key), ptrlen_from_strbuf(pub), + ptrlen_from_strbuf(priv)); + strbuf_free(priv); + } else { + copy = ssh_key_new_pub(ssh_key_alg(key), ptrlen_from_strbuf(pub)); + } + + strbuf_free(pub); + return copy; +}