From 3d12521735e7ef7e48be217af0f27d68e23050a7 Mon Sep 17 00:00:00 2001 From: Vladislav Grishenko Date: Wed, 11 Mar 2020 21:09:45 +0500 Subject: Add Ed25519 support (#91) * Add support for Ed25519 as a public key type Ed25519 is a elliptic curve signature scheme that offers better security than ECDSA and DSA and good performance. It may be used for both user and host keys. OpenSSH key import and fuzzer are not supported yet. Initially inspired by Peter Szabo. * Add curve25519 and ed25519 fuzzers * Add import and export of Ed25519 keys --- common-algo.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'common-algo.c') diff --git a/common-algo.c b/common-algo.c index 2f896ab..558aad2 100644 --- a/common-algo.c +++ b/common-algo.c @@ -222,6 +222,9 @@ algo_type ssh_nocompress[] = { }; algo_type sshhostkey[] = { +#if DROPBEAR_ED25519 + {"ssh-ed25519", DROPBEAR_SIGNKEY_ED25519, NULL, 1, NULL}, +#endif #if DROPBEAR_ECDSA #if DROPBEAR_ECC_256 {"ecdsa-sha2-nistp256", DROPBEAR_SIGNKEY_ECDSA_NISTP256, NULL, 1, NULL}, -- cgit v1.2.3 From 7dc2f36c3e2d21455ae432da4d8f338e7dc0668c Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Mon, 6 Apr 2020 23:18:26 +0800 Subject: use sigtype where appropriate --- algo.h | 1 - cli-authpubkey.c | 34 ++++++++++++------- cli-kex.c | 11 +++--- common-algo.c | 23 +++---------- common-kex.c | 9 +++-- dss.h | 2 +- ed25519.h | 2 +- fuzzer-pubkey.c | 2 +- keyimport.c | 3 ++ rsa.c | 101 ++++++++++++++++++++++++++++++++++++------------------- rsa.h | 11 +++--- session.h | 3 +- signkey.c | 78 ++++++++++++++++++++++++++++++++---------- signkey.h | 24 ++++++++----- ssh.h | 4 ++- svr-authpubkey.c | 53 ++++++++++++++--------------- svr-kex.c | 2 +- sysoptions.h | 12 +++++-- 18 files changed, 233 insertions(+), 142 deletions(-) (limited to 'common-algo.c') diff --git a/algo.h b/algo.h index b12fb94..1a6d384 100644 --- a/algo.h +++ b/algo.h @@ -112,7 +112,6 @@ struct dropbear_kex { const struct ltc_hash_descriptor *hash_desc; }; -int have_algo(const char* algo, size_t algolen, const algo_type algos[]); void buf_put_algolist(buffer * buf, const algo_type localalgos[]); enum kexguess2_used { diff --git a/cli-authpubkey.c b/cli-authpubkey.c index 7cee164..d1cc1f6 100644 --- a/cli-authpubkey.c +++ b/cli-authpubkey.c @@ -33,7 +33,7 @@ #include "agentfwd.h" #if DROPBEAR_CLI_PUBKEY_AUTH -static void send_msg_userauth_pubkey(sign_key *key, int type, int realsign); +static void send_msg_userauth_pubkey(sign_key *key, enum signkey_type sigtype, int realsign); /* Called when we receive a SSH_MSG_USERAUTH_FAILURE for a pubkey request. * We use it to remove the key we tried from the list */ @@ -58,14 +58,15 @@ void recv_msg_userauth_pk_ok() { buffer* keybuf = NULL; char* algotype = NULL; unsigned int algolen; - enum signkey_type keytype; + enum signkey_type sigtype, keytype; unsigned int remotelen; TRACE(("enter recv_msg_userauth_pk_ok")) algotype = buf_getstring(ses.payload, &algolen); - keytype = signkey_type_from_name(algotype, algolen); - TRACE(("recv_msg_userauth_pk_ok: type %d", keytype)) + sigtype = signature_type_from_name(algotype, algolen); + keytype = signkey_type_from_signature(sigtype); + TRACE(("recv_msg_userauth_pk_ok: type %d", sigtype)) m_free(algotype); keybuf = buf_new(MAX_PUBKEY_SIZE); @@ -120,9 +121,10 @@ void recv_msg_userauth_pk_ok() { TRACE(("leave recv_msg_userauth_pk_ok")) } -void cli_buf_put_sign(buffer* buf, sign_key *key, int type, +static void cli_buf_put_sign(buffer* buf, sign_key *key, enum signkey_type sigtype, const buffer *data_buf) { #if DROPBEAR_CLI_AGENTFWD + // TODO: rsa-sha256 agent if (key->source == SIGNKEY_SOURCE_AGENT) { /* Format the agent signature ourselves, as buf_put_sign would. */ buffer *sigblob; @@ -133,16 +135,16 @@ void cli_buf_put_sign(buffer* buf, sign_key *key, int type, } else #endif /* DROPBEAR_CLI_AGENTFWD */ { - buf_put_sign(buf, key, type, data_buf); + buf_put_sign(buf, key, sigtype, data_buf); } } -/* TODO: make it take an agent reference to use as well */ -static void send_msg_userauth_pubkey(sign_key *key, int type, int realsign) { +static void send_msg_userauth_pubkey(sign_key *key, enum signkey_type sigtype, int realsign) { const char *algoname = NULL; unsigned int algolen; buffer* sigbuf = NULL; + enum signkey_type keytype = signkey_type_from_signature(sigtype); TRACE(("enter send_msg_userauth_pubkey")) CHECKCLEARTOWRITE(); @@ -160,10 +162,9 @@ static void send_msg_userauth_pubkey(sign_key *key, int type, int realsign) { buf_putbyte(ses.writepayload, realsign); - algoname = signkey_name_from_type(type, &algolen); - + algoname = signature_name_from_type(sigtype, &algolen); buf_putstring(ses.writepayload, algoname, algolen); - buf_put_pub_key(ses.writepayload, key, type); + buf_put_pub_key(ses.writepayload, key, keytype); if (realsign) { TRACE(("realsign")) @@ -172,7 +173,7 @@ static void send_msg_userauth_pubkey(sign_key *key, int type, int realsign) { sigbuf = buf_new(4 + ses.session_id->len + ses.writepayload->len); buf_putbufstring(sigbuf, ses.session_id); buf_putbytes(sigbuf, ses.writepayload->data, ses.writepayload->len); - cli_buf_put_sign(ses.writepayload, key, type, sigbuf); + cli_buf_put_sign(ses.writepayload, key, sigtype, sigbuf); buf_free(sigbuf); /* Nothing confidential in the buffer */ } @@ -195,8 +196,15 @@ int cli_auth_pubkey() { if (cli_opts.privkeys->first) { sign_key * key = (sign_key*)cli_opts.privkeys->first->item; + enum signkey_type sigtype = key->type; /* Send a trial request */ - send_msg_userauth_pubkey(key, key->type, 0); +#if DROPBEAR_RSA && DROPBEAR_RSA_SHA256 + // TODO: use ext-info to choose rsa kind + if (sigtype == DROPBEAR_SIGNKEY_RSA) { + sigtype = DROPBEAR_SIGNKEY_RSA_SHA256; + } +#endif + send_msg_userauth_pubkey(key, sigtype, 0); cli_ses.lastprivkey = key; TRACE(("leave cli_auth_pubkey-success")) return 1; diff --git a/cli-kex.c b/cli-kex.c index 7cefb5f..7337ffd 100644 --- a/cli-kex.c +++ b/cli-kex.c @@ -94,7 +94,7 @@ void send_msg_kexdh_init() { void recv_msg_kexdh_reply() { sign_key *hostkey = NULL; - unsigned int type, keybloblen; + unsigned int keytype, keybloblen; unsigned char* keyblob = NULL; TRACE(("enter recv_msg_kexdh_reply")) @@ -102,8 +102,8 @@ void recv_msg_kexdh_reply() { if (cli_ses.kex_state != KEXDH_INIT_SENT) { dropbear_exit("Received out-of-order kexdhreply"); } - type = ses.newkeys->algo_hostkey; - TRACE(("type is %d", type)) + keytype = ses.newkeys->algo_hostkey; + TRACE(("keytype is %d", keytype)) hostkey = new_sign_key(); keybloblen = buf_getint(ses.payload); @@ -114,7 +114,7 @@ void recv_msg_kexdh_reply() { checkhostkey(keyblob, keybloblen); } - if (buf_get_pub_key(ses.payload, hostkey, &type) != DROPBEAR_SUCCESS) { + if (buf_get_pub_key(ses.payload, hostkey, &keytype) != DROPBEAR_SUCCESS) { TRACE(("failed getting pubkey")) dropbear_exit("Bad KEX packet"); } @@ -173,7 +173,8 @@ void recv_msg_kexdh_reply() { #endif cli_ses.param_kex_algo = NULL; - if (buf_verify(ses.payload, hostkey, ses.hash) != DROPBEAR_SUCCESS) { + if (buf_verify(ses.payload, hostkey, ses.newkeys->algo_signature, + ses.hash) != DROPBEAR_SUCCESS) { dropbear_exit("Bad hostkey signature"); } diff --git a/common-algo.c b/common-algo.c index 558aad2..28f6744 100644 --- a/common-algo.c +++ b/common-algo.c @@ -237,8 +237,13 @@ algo_type sshhostkey[] = { #endif #endif #if DROPBEAR_RSA +#if DROPBEAR_RSA_SHA256 + {"rsa-sha2-256", DROPBEAR_SIGNKEY_RSA_SHA256, NULL, 1, NULL}, +#endif +#if DROPBEAR_RSA_SHA1 {"ssh-rsa", DROPBEAR_SIGNKEY_RSA, NULL, 1, NULL}, #endif +#endif #if DROPBEAR_DSS {"ssh-dss", DROPBEAR_SIGNKEY_DSS, NULL, 1, NULL}, #endif @@ -311,24 +316,6 @@ algo_type sshkex[] = { {NULL, 0, NULL, 0, NULL} }; -/* algolen specifies the length of algo, algos is our local list to match - * against. - * Returns DROPBEAR_SUCCESS if we have a match for algo, DROPBEAR_FAILURE - * otherwise */ -int have_algo(const char* algo, size_t algolen, const algo_type algos[]) { - - int i; - - for (i = 0; algos[i].name != NULL; i++) { - if (strlen(algos[i].name) == algolen - && (strncmp(algos[i].name, algo, algolen) == 0)) { - return DROPBEAR_SUCCESS; - } - } - - return DROPBEAR_FAILURE; -} - /* Output a comma separated list of algorithms to a buffer */ void buf_put_algolist(buffer * buf, const algo_type localalgos[]) { diff --git a/common-kex.c b/common-kex.c index 16b7e27..c1392c1 100644 --- a/common-kex.c +++ b/common-kex.c @@ -111,7 +111,8 @@ void send_msg_kexinit() { if (ses.send_kex_first_guess) { ses.newkeys->algo_kex = sshkex[0].data; - ses.newkeys->algo_hostkey = sshhostkey[0].val; + ses.newkeys->algo_signature = sshhostkey[0].val; + ses.newkeys->algo_hostkey = signkey_type_from_signature(ses.newkeys->algo_signature); ses.send_kex_first_guess(); } @@ -152,6 +153,7 @@ static void switch_keys() { TRACE(("switch_keys done")) ses.keys->algo_kex = ses.newkeys->algo_kex; ses.keys->algo_hostkey = ses.newkeys->algo_hostkey; + ses.keys->algo_signature = ses.newkeys->algo_signature; ses.keys->allow_compress = 0; m_free(ses.newkeys); ses.newkeys = NULL; @@ -847,8 +849,9 @@ static void read_kex_algos() { erralgo = "hostkey"; goto error; } - TRACE(("hostkey algo %s", algo->name)) - ses.newkeys->algo_hostkey = algo->val; + TRACE(("signature algo %s", algo->name)) + ses.newkeys->algo_signature = algo->val; + ses.newkeys->algo_hostkey = signkey_type_from_signature(ses.newkeys->algo_signature); /* encryption_algorithms_client_to_server */ c2s_cipher_algo = buf_match_algo(ses.payload, sshciphers, NULL, NULL); diff --git a/dss.h b/dss.h index 2eaa2f5..40806e5 100644 --- a/dss.h +++ b/dss.h @@ -30,7 +30,7 @@ #if DROPBEAR_DSS -typedef struct { +typedef struct dropbear_DSS_Key { mp_int* p; mp_int* q; diff --git a/ed25519.h b/ed25519.h index 16c0d7b..622111a 100644 --- a/ed25519.h +++ b/ed25519.h @@ -32,7 +32,7 @@ #define CURVE25519_LEN 32 -typedef struct { +typedef struct dropbear_ED25519_Key { unsigned char priv[CURVE25519_LEN]; unsigned char pub[CURVE25519_LEN]; diff --git a/fuzzer-pubkey.c b/fuzzer-pubkey.c index 033f496..7c12cdc 100644 --- a/fuzzer-pubkey.c +++ b/fuzzer-pubkey.c @@ -27,7 +27,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { unsigned int algolen; char* algoname = buf_getstring(keyblob, &algolen); - if (have_algo(algoname, algolen, sshhostkey) == DROPBEAR_FAILURE) { + if (signature_type_from_name(algoname, algolen) == DROPBEAR_SIGNKEY_NONE) { dropbear_exit("fuzzer imagined a bogus algorithm"); } diff --git a/keyimport.c b/keyimport.c index ad0c530..52ddf6e 100644 --- a/keyimport.c +++ b/keyimport.c @@ -36,6 +36,9 @@ #include "dbutil.h" #include "ecc.h" #include "ssh.h" +#include "rsa.h" +#include "dss.h" +#include "ed25519.h" static const unsigned char OSSH_PKEY_BLOB[] = "openssh-key-v1\0" /* AUTH_MAGIC */ diff --git a/rsa.c b/rsa.c index 67b90f7..ef737c8 100644 --- a/rsa.c +++ b/rsa.c @@ -35,11 +35,16 @@ #include "buffer.h" #include "ssh.h" #include "dbrandom.h" +#include "signkey.h" #if DROPBEAR_RSA +#if !(DROPBEAR_RSA_SHA1 || DROPBEAR_RSA_SHA256) +#error Somehow RSA was enabled with neither DROPBEAR_RSA_SHA1 nor DROPBEAR_RSA_SHA256 +#endif + static void rsa_pad_em(const dropbear_rsa_key * key, - const buffer *data_buf, mp_int * rsa_em); + const buffer *data_buf, mp_int * rsa_em, enum signkey_type sigtype); /* Load a public rsa key from a buffer, initialising the values. * The key will have the same format as buf_put_rsa_key. @@ -191,7 +196,8 @@ void buf_put_rsa_priv_key(buffer* buf, const dropbear_rsa_key *key) { #if DROPBEAR_SIGNKEY_VERIFY /* Verify a signature in buf, made on data by the key given. * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ -int buf_rsa_verify(buffer * buf, const dropbear_rsa_key *key, const buffer *data_buf) { +int buf_rsa_verify(buffer * buf, const dropbear_rsa_key *key, + enum signkey_type sigtype, const buffer *data_buf) { unsigned int slen; DEF_MP_INT(rsa_s); DEF_MP_INT(rsa_mdash); @@ -223,7 +229,7 @@ int buf_rsa_verify(buffer * buf, const dropbear_rsa_key *key, const buffer *data } /* create the magic PKCS padded value */ - rsa_pad_em(key, data_buf, &rsa_em); + rsa_pad_em(key, data_buf, &rsa_em, sigtype); if (mp_exptmod(&rsa_s, key->e, key->n, &rsa_mdash) != MP_OKAY) { TRACE(("failed exptmod rsa_s")) @@ -246,8 +252,10 @@ out: /* Sign the data presented with key, writing the signature contents * to the buffer */ -void buf_put_rsa_sign(buffer* buf, const dropbear_rsa_key *key, const buffer *data_buf) { - unsigned int nsize, ssize; +void buf_put_rsa_sign(buffer* buf, const dropbear_rsa_key *key, + enum signkey_type sigtype, const buffer *data_buf) { + const char *name = NULL; + unsigned int nsize, ssize, namelen = 0; unsigned int i; DEF_MP_INT(rsa_s); DEF_MP_INT(rsa_tmp1); @@ -259,7 +267,7 @@ void buf_put_rsa_sign(buffer* buf, const dropbear_rsa_key *key, const buffer *da m_mp_init_multi(&rsa_s, &rsa_tmp1, &rsa_tmp2, &rsa_tmp3, NULL); - rsa_pad_em(key, data_buf, &rsa_tmp1); + rsa_pad_em(key, data_buf, &rsa_tmp1, sigtype); /* the actual signing of the padded data */ @@ -311,7 +319,8 @@ void buf_put_rsa_sign(buffer* buf, const dropbear_rsa_key *key, const buffer *da mp_clear_multi(&rsa_tmp1, &rsa_tmp2, &rsa_tmp3, NULL); /* create the signature to return */ - buf_putstring(buf, SSH_SIGNKEY_RSA, SSH_SIGNKEY_RSA_LEN); + name = signature_name_from_type(sigtype, &namelen); + buf_putstring(buf, name, namelen); nsize = mp_unsigned_bin_size(key->n); @@ -340,51 +349,73 @@ void buf_put_rsa_sign(buffer* buf, const dropbear_rsa_key *key, const buffer *da TRACE(("leave buf_put_rsa_sign")) } -/* Creates the message value as expected by PKCS, see rfc2437 etc */ -/* format to be padded to is: - * EM = 01 | FF* | 00 | prefix | hash - * - * where FF is repeated enough times to make EM one byte - * shorter than the size of key->n - * - * prefix is the ASN1 designator prefix, - * hex 30 21 30 09 06 05 2B 0E 03 02 1A 05 00 04 14 - * - * rsa_em must be a pointer to an initialised mp_int. - */ +/* Creates the message value as expected by PKCS, + see rfc8017 section 9.2 */ static void rsa_pad_em(const dropbear_rsa_key * key, - const buffer *data_buf, mp_int * rsa_em) { + const buffer *data_buf, mp_int * rsa_em, enum signkey_type sigtype) { + /* EM = 0x00 || 0x01 || PS || 0x00 || T + PS is padding of 0xff to make EM the size of key->n + + T is the DER encoding of the hash alg (sha1 or sha256) + */ - /* ASN1 designator (including the 0x00 preceding) */ - const unsigned char rsa_asn1_magic[] = - {0x00, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, + /* From rfc8017 page 46 */ +#if DROPBEAR_RSA_SHA1 + const unsigned char T_sha1[] = + {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14}; - const unsigned int RSA_ASN1_MAGIC_LEN = 16; +#endif +#if DROPBEAR_RSA_SHA256 + const unsigned char T_sha256[] = + {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, + 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}; +#endif + int Tlen = 0; + const unsigned char *T = NULL; + const struct ltc_hash_descriptor *hash_desc = NULL; buffer * rsa_EM = NULL; hash_state hs; unsigned int nsize; + + switch (sigtype) { +#if DROPBEAR_RSA_SHA1 + case DROPBEAR_SIGNKEY_RSA: + Tlen = sizeof(T_sha1); + T = T_sha1; + hash_desc = &sha1_desc; + break; +#endif +#if DROPBEAR_RSA_SHA256 + case DROPBEAR_SIGNKEY_RSA_SHA256: + Tlen = sizeof(T_sha256); + T = T_sha256; + hash_desc = &sha256_desc; + break; +#endif + default: + assert(0); + } - dropbear_assert(key != NULL); nsize = mp_unsigned_bin_size(key->n); - rsa_EM = buf_new(nsize-1); + rsa_EM = buf_new(nsize); /* type byte */ + buf_putbyte(rsa_EM, 0x00); buf_putbyte(rsa_EM, 0x01); - /* Padding with 0xFF bytes */ - while(rsa_EM->pos != rsa_EM->size - RSA_ASN1_MAGIC_LEN - SHA1_HASH_SIZE) { + /* Padding with PS 0xFF bytes */ + while(rsa_EM->pos != rsa_EM->size - (1 + Tlen + hash_desc->hashsize)) { buf_putbyte(rsa_EM, 0xff); } + buf_putbyte(rsa_EM, 0x00); /* Magic ASN1 stuff */ - memcpy(buf_getwriteptr(rsa_EM, RSA_ASN1_MAGIC_LEN), - rsa_asn1_magic, RSA_ASN1_MAGIC_LEN); - buf_incrwritepos(rsa_EM, RSA_ASN1_MAGIC_LEN); + buf_putbytes(rsa_EM, T, Tlen); /* The hash of the data */ - sha1_init(&hs); - sha1_process(&hs, data_buf->data, data_buf->len); - sha1_done(&hs, buf_getwriteptr(rsa_EM, SHA1_HASH_SIZE)); - buf_incrwritepos(rsa_EM, SHA1_HASH_SIZE); + hash_desc->init(&hs); + hash_desc->process(&hs, data_buf->data, data_buf->len); + hash_desc->done(&hs, buf_getwriteptr(rsa_EM, hash_desc->hashsize)); + buf_incrwritepos(rsa_EM, hash_desc->hashsize); dropbear_assert(rsa_EM->pos == rsa_EM->size); diff --git a/rsa.h b/rsa.h index 4fee2e6..39e6004 100644 --- a/rsa.h +++ b/rsa.h @@ -26,13 +26,12 @@ #define DROPBEAR_RSA_H_ #include "includes.h" +#include "signkey.h" #include "buffer.h" #if DROPBEAR_RSA -#define RSA_SIGNATURE_SIZE (4+7+4+40) - -typedef struct { +typedef struct dropbear_RSA_Key { mp_int* n; mp_int* e; @@ -43,9 +42,11 @@ typedef struct { } dropbear_rsa_key; -void buf_put_rsa_sign(buffer* buf, const dropbear_rsa_key *key, const buffer *data_buf); +void buf_put_rsa_sign(buffer* buf, const dropbear_rsa_key *key, + enum signkey_type sigtype, const buffer *data_buf); #if DROPBEAR_SIGNKEY_VERIFY -int buf_rsa_verify(buffer * buf, const dropbear_rsa_key *key, const buffer *data_buf); +int buf_rsa_verify(buffer * buf, const dropbear_rsa_key *key, + enum signkey_type sigtype, const buffer *data_buf); #endif int buf_get_rsa_pub_key(buffer* buf, dropbear_rsa_key *key); int buf_get_rsa_priv_key(buffer* buf, dropbear_rsa_key *key); diff --git a/session.h b/session.h index e436882..ebfc915 100644 --- a/session.h +++ b/session.h @@ -92,7 +92,8 @@ struct key_context { struct key_context_directional trans; const struct dropbear_kex *algo_kex; - int algo_hostkey; + int algo_hostkey; /* server key type */ + int algo_signature; /* server signature type */ int allow_compress; /* whether compression has started (useful in zlib@openssh.com delayed compression case) */ diff --git a/signkey.c b/signkey.c index a0af44a..27b09a3 100644 --- a/signkey.c +++ b/signkey.c @@ -28,6 +28,9 @@ #include "buffer.h" #include "ssh.h" #include "ecdsa.h" +#include "rsa.h" +#include "dss.h" +#include "ed25519.h" static const char * const signkey_names[DROPBEAR_SIGNKEY_NUM_NAMED] = { #if DROPBEAR_RSA @@ -44,6 +47,7 @@ static const char * const signkey_names[DROPBEAR_SIGNKEY_NUM_NAMED] = { #if DROPBEAR_ED25519 "ssh-ed25519", #endif /* DROPBEAR_ED25519 */ + /* "rsa-sha2-256" is special-cased below since it is only a signature name, not key type */ }; /* malloc a new sign_key and set the dss and rsa keys to NULL */ @@ -105,6 +109,38 @@ enum signkey_type signkey_type_from_name(const char* name, unsigned int namelen) return DROPBEAR_SIGNKEY_NONE; } +/* Special case for rsa-sha2-256. This could be generalised if more + signature names are added that aren't 1-1 with public key names */ +const char* signature_name_from_type(enum signkey_type type, unsigned int *namelen) { +#if DROPBEAR_RSA_SHA256 + if (type == DROPBEAR_SIGNKEY_RSA_SHA256) { + *namelen = strlen(SSH_SIGNKEY_RSA_SHA256); + return SSH_SIGNKEY_RSA_SHA256; + } +#endif + return signkey_name_from_type(type, namelen); +} + +enum signkey_type signature_type_from_name(const char* name, unsigned int namelen) { +#if DROPBEAR_RSA_SHA256 + if (namelen == strlen(SSH_SIGNKEY_RSA_SHA256) + && memcmp(name, SSH_SIGNKEY_RSA_SHA256, namelen) == 0) { + return DROPBEAR_SIGNKEY_RSA_SHA256; + } +#endif + return signkey_type_from_name(name, namelen); +} + +enum signkey_type signkey_type_from_signature(enum signkey_type sigtype) { +#if DROPBEAR_RSA_SHA256 + if (sigtype == DROPBEAR_SIGNKEY_RSA_SHA256) { + return DROPBEAR_SIGNKEY_RSA; + } +#endif + assert(sigtype < DROPBEAR_SIGNKEY_NUM_NAMED); + return sigtype; +} + /* Returns a pointer to the key part specific to "type". Be sure to check both (ret != NULL) and (*ret != NULL) */ void ** @@ -526,31 +562,31 @@ char * sign_key_fingerprint(const unsigned char* keyblob, unsigned int keybloble #endif } -void buf_put_sign(buffer* buf, sign_key *key, enum signkey_type type, +void buf_put_sign(buffer* buf, sign_key *key, enum signkey_type sigtype, const buffer *data_buf) { - buffer *sigblob; - sigblob = buf_new(MAX_PUBKEY_SIZE); + buffer *sigblob = buf_new(MAX_PUBKEY_SIZE); + enum signkey_type keytype = signkey_type_from_signature(sigtype); #if DROPBEAR_DSS - if (type == DROPBEAR_SIGNKEY_DSS) { + if (keytype == DROPBEAR_SIGNKEY_DSS) { buf_put_dss_sign(sigblob, key->dsskey, data_buf); } #endif #if DROPBEAR_RSA - if (type == DROPBEAR_SIGNKEY_RSA) { - buf_put_rsa_sign(sigblob, key->rsakey, data_buf); + if (keytype == DROPBEAR_SIGNKEY_RSA) { + buf_put_rsa_sign(sigblob, key->rsakey, sigtype, data_buf); } #endif #if DROPBEAR_ECDSA - if (signkey_is_ecdsa(type)) { - ecc_key **eck = (ecc_key**)signkey_key_ptr(key, type); + if (signkey_is_ecdsa(keytype)) { + ecc_key **eck = (ecc_key**)signkey_key_ptr(key, keytype); if (eck && *eck) { buf_put_ecdsa_sign(sigblob, *eck, data_buf); } } #endif #if DROPBEAR_ED25519 - if (type == DROPBEAR_SIGNKEY_ED25519) { + if (keytype == DROPBEAR_SIGNKEY_ED25519) { buf_put_ed25519_sign(sigblob, key->ed25519key, data_buf); } #endif @@ -567,21 +603,27 @@ void buf_put_sign(buffer* buf, sign_key *key, enum signkey_type type, * If FAILURE is returned, the position of * buf is undefined. If SUCCESS is returned, buf will be positioned after the * signature blob */ -int buf_verify(buffer * buf, sign_key *key, const buffer *data_buf) { +int buf_verify(buffer * buf, sign_key *key, enum signkey_type expect_sigtype, const buffer *data_buf) { char *type_name = NULL; unsigned int type_name_len = 0; - enum signkey_type type; + enum signkey_type sigtype, keytype; TRACE(("enter buf_verify")) buf_getint(buf); /* blob length */ type_name = buf_getstring(buf, &type_name_len); - type = signkey_type_from_name(type_name, type_name_len); + sigtype = signature_type_from_name(type_name, type_name_len); m_free(type_name); + if (expect_sigtype != DROPBEAR_SIGNKEY_ANY + && expect_sigtype != sigtype) { + dropbear_exit("Non-matching signing type"); + } + + keytype = signkey_type_from_signature(sigtype); #if DROPBEAR_DSS - if (type == DROPBEAR_SIGNKEY_DSS) { + if (keytype == DROPBEAR_SIGNKEY_DSS) { if (key->dsskey == NULL) { dropbear_exit("No DSS key to verify signature"); } @@ -590,23 +632,23 @@ int buf_verify(buffer * buf, sign_key *key, const buffer *data_buf) { #endif #if DROPBEAR_RSA - if (type == DROPBEAR_SIGNKEY_RSA) { + if (keytype == DROPBEAR_SIGNKEY_RSA) { if (key->rsakey == NULL) { dropbear_exit("No RSA key to verify signature"); } - return buf_rsa_verify(buf, key->rsakey, data_buf); + return buf_rsa_verify(buf, key->rsakey, sigtype, data_buf); } #endif #if DROPBEAR_ECDSA - if (signkey_is_ecdsa(type)) { - ecc_key **eck = (ecc_key**)signkey_key_ptr(key, type); + if (signkey_is_ecdsa(keytype)) { + ecc_key **eck = (ecc_key**)signkey_key_ptr(key, keytype); if (eck && *eck) { return buf_ecdsa_verify(buf, *eck, data_buf); } } #endif #if DROPBEAR_ED25519 - if (type == DROPBEAR_SIGNKEY_ED25519) { + if (keytype == DROPBEAR_SIGNKEY_ED25519) { if (key->ed25519key == NULL) { dropbear_exit("No Ed25519 key to verify signature"); } diff --git a/signkey.h b/signkey.h index fa39a02..78b237b 100644 --- a/signkey.h +++ b/signkey.h @@ -26,9 +26,11 @@ #define DROPBEAR_SIGNKEY_H_ #include "buffer.h" -#include "dss.h" -#include "rsa.h" -#include "ed25519.h" + +/* Forward declarations */ +struct dropbear_DSS_Key; +struct dropbear_RSA_Key; +struct dropbear_ED25519_Key; enum signkey_type { #if DROPBEAR_RSA @@ -47,6 +49,9 @@ enum signkey_type { #endif DROPBEAR_SIGNKEY_NUM_NAMED, DROPBEAR_SIGNKEY_ECDSA_KEYGEN = 70, /* just "ecdsa" for keygen */ +#if DROPBEAR_RSA_SHA256 + DROPBEAR_SIGNKEY_RSA_SHA256, /* rsa-sha2-256 signature. has a ssh-rsa key */ +#endif DROPBEAR_SIGNKEY_ANY = 80, DROPBEAR_SIGNKEY_NONE = 90, }; @@ -66,10 +71,10 @@ struct SIGN_key { char *filename; #if DROPBEAR_DSS - dropbear_dss_key * dsskey; + struct dropbear_DSS_Key * dsskey; #endif #if DROPBEAR_RSA - dropbear_rsa_key * rsakey; + struct dropbear_RSA_Key * rsakey; #endif #if DROPBEAR_ECDSA #if DROPBEAR_ECC_256 @@ -83,7 +88,7 @@ struct SIGN_key { #endif #endif #if DROPBEAR_ED25519 - dropbear_ed25519_key * ed25519key; + struct dropbear_ED25519_Key * ed25519key; #endif }; @@ -92,14 +97,17 @@ typedef struct SIGN_key sign_key; sign_key * new_sign_key(void); const char* signkey_name_from_type(enum signkey_type type, unsigned int *namelen); enum signkey_type signkey_type_from_name(const char* name, unsigned int namelen); +const char* signature_name_from_type(enum signkey_type type, unsigned int *namelen); +enum signkey_type signature_type_from_name(const char* name, unsigned int namelen); +enum signkey_type signkey_type_from_signature(enum signkey_type sigtype); int buf_get_pub_key(buffer *buf, sign_key *key, enum signkey_type *type); int buf_get_priv_key(buffer* buf, sign_key *key, enum signkey_type *type); void buf_put_pub_key(buffer* buf, sign_key *key, enum signkey_type type); void buf_put_priv_key(buffer* buf, sign_key *key, enum signkey_type type); void sign_key_free(sign_key *key); -void buf_put_sign(buffer* buf, sign_key *key, enum signkey_type type, const buffer *data_buf); +void buf_put_sign(buffer* buf, sign_key *key, enum signkey_type sigtype, const buffer *data_buf); #if DROPBEAR_SIGNKEY_VERIFY -int buf_verify(buffer * buf, sign_key *key, const buffer *data_buf); +int buf_verify(buffer * buf, sign_key *key, enum signkey_type type, const buffer *data_buf); char * sign_key_fingerprint(const unsigned char* keyblob, unsigned int keybloblen); #endif int cmp_base64_key(const unsigned char* keyblob, unsigned int keybloblen, diff --git a/ssh.h b/ssh.h index db723b8..fb7f9f2 100644 --- a/ssh.h +++ b/ssh.h @@ -100,13 +100,15 @@ #define SSH_SERVICE_CONNECTION "ssh-connection" #define SSH_SERVICE_CONNECTION_LEN 14 -/* public key types */ +/* public/signature key types */ #define SSH_SIGNKEY_DSS "ssh-dss" #define SSH_SIGNKEY_DSS_LEN 7 #define SSH_SIGNKEY_RSA "ssh-rsa" #define SSH_SIGNKEY_RSA_LEN 7 #define SSH_SIGNKEY_ED25519 "ssh-ed25519" #define SSH_SIGNKEY_ED25519_LEN 11 +/* signature type */ +#define SSH_SIGNKEY_RSA_SHA256 "rsa-sha2-256" /* Agent commands. These aren't part of the spec, and are defined * only on the openssh implementation. */ diff --git a/svr-authpubkey.c b/svr-authpubkey.c index 5c234b9..a52667c 100644 --- a/svr-authpubkey.c +++ b/svr-authpubkey.c @@ -70,10 +70,10 @@ #define MIN_AUTHKEYS_LINE 10 /* "ssh-rsa AB" - short but doesn't matter */ #define MAX_AUTHKEYS_LINE 4200 /* max length of a line in authkeys */ -static int checkpubkey(const char* algo, unsigned int algolen, +static int checkpubkey(const char* keyalgo, unsigned int keyalgolen, const unsigned char* keyblob, unsigned int keybloblen); static int checkpubkeyperms(void); -static void send_msg_userauth_pk_ok(const char* algo, unsigned int algolen, +static void send_msg_userauth_pk_ok(const char* sigalgo, unsigned int sigalgolen, const unsigned char* keyblob, unsigned int keybloblen); static int checkfileperm(char * filename); @@ -82,16 +82,18 @@ static int checkfileperm(char * filename); void svr_auth_pubkey(int valid_user) { unsigned char testkey; /* whether we're just checking if a key is usable */ - char* algo = NULL; /* pubkey algo */ - unsigned int algolen; + char* sigalgo = NULL; + unsigned int sigalgolen; + const char* keyalgo; + unsigned int keyalgolen; unsigned char* keyblob = NULL; unsigned int keybloblen; unsigned int sign_payload_length; buffer * signbuf = NULL; sign_key * key = NULL; char* fp = NULL; - enum signkey_type type = -1; - int auth_failure = 1; + enum signkey_type sigtype, keytype; + int auth_failure = 1; TRACE(("enter pubkeyauth")) @@ -99,7 +101,11 @@ void svr_auth_pubkey(int valid_user) { * actual attempt*/ testkey = (buf_getbool(ses.payload) == 0); - algo = buf_getstring(ses.payload, &algolen); + sigalgo = buf_getstring(ses.payload, &sigalgolen); + sigtype = signature_type_from_name(sigalgo, sigalgolen); + keytype = signkey_type_from_signature(sigtype); + keyalgo = signkey_name_from_type(keytype, &keyalgolen); + keybloblen = buf_getint(ses.payload); keyblob = buf_getptr(ses.payload, keybloblen); @@ -117,8 +123,8 @@ void svr_auth_pubkey(int valid_user) { if (svr_ses.plugin_instance->checkpubkey( svr_ses.plugin_instance, &ses.plugin_session, - algo, - algolen, + keyalgo, + keyalgolen, keyblob, keybloblen, ses.authstate.username) == DROPBEAR_SUCCESS) { @@ -146,7 +152,7 @@ void svr_auth_pubkey(int valid_user) { #endif /* check if the key is valid */ if (auth_failure) { - auth_failure = checkpubkey(algo, algolen, keyblob, keybloblen) == DROPBEAR_FAILURE; + auth_failure = checkpubkey(keyalgo, keyalgolen, keyblob, keybloblen) == DROPBEAR_FAILURE; } if (auth_failure) { @@ -156,7 +162,7 @@ void svr_auth_pubkey(int valid_user) { /* let them know that the key is ok to use */ if (testkey) { - send_msg_userauth_pk_ok(algo, algolen, keyblob, keybloblen); + send_msg_userauth_pk_ok(sigalgo, sigalgolen, keyblob, keybloblen); goto out; } @@ -164,8 +170,7 @@ void svr_auth_pubkey(int valid_user) { /* get the key */ key = new_sign_key(); - type = DROPBEAR_SIGNKEY_ANY; - if (buf_get_pub_key(ses.payload, key, &type) == DROPBEAR_FAILURE) { + if (buf_get_pub_key(ses.payload, key, &keytype) == DROPBEAR_FAILURE) { send_msg_userauth_failure(0, 1); goto out; } @@ -188,7 +193,7 @@ void svr_auth_pubkey(int valid_user) { /* ... and finally verify the signature */ fp = sign_key_fingerprint(keyblob, keybloblen); - if (buf_verify(ses.payload, key, signbuf) == DROPBEAR_SUCCESS) { + if (buf_verify(ses.payload, key, sigtype, signbuf) == DROPBEAR_SUCCESS) { dropbear_log(LOG_NOTICE, "Pubkey auth succeeded for '%s' with key %s from %s", ses.authstate.pw_name, fp, svr_ses.addrstring); @@ -213,8 +218,8 @@ out: if (signbuf) { buf_free(signbuf); } - if (algo) { - m_free(algo); + if (sigalgo) { + m_free(sigalgo); } if (key) { sign_key_free(key); @@ -230,14 +235,14 @@ out: /* Reply that the key is valid for auth, this is sent when the user sends * a straight copy of their pubkey to test, to avoid having to perform * expensive signing operations with a worthless key */ -static void send_msg_userauth_pk_ok(const char* algo, unsigned int algolen, +static void send_msg_userauth_pk_ok(const char* sigalgo, unsigned int sigalgolen, const unsigned char* keyblob, unsigned int keybloblen) { TRACE(("enter send_msg_userauth_pk_ok")) CHECKCLEARTOWRITE(); buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_PK_OK); - buf_putstring(ses.writepayload, algo, algolen); + buf_putstring(ses.writepayload, sigalgo, sigalgolen); buf_putstring(ses.writepayload, (const char*)keyblob, keybloblen); encrypt_packet(); @@ -354,7 +359,7 @@ out: /* Checks whether a specified publickey (and associated algorithm) is an * acceptable key for authentication */ /* Returns DROPBEAR_SUCCESS if key is ok for auth, DROPBEAR_FAILURE otherwise */ -static int checkpubkey(const char* algo, unsigned int algolen, +static int checkpubkey(const char* keyalgo, unsigned int keyalgolen, const unsigned char* keyblob, unsigned int keybloblen) { FILE * authfile = NULL; @@ -368,14 +373,6 @@ static int checkpubkey(const char* algo, unsigned int algolen, TRACE(("enter checkpubkey")) - /* check that we can use the algo */ - if (have_algo(algo, algolen, sshhostkey) == DROPBEAR_FAILURE) { - dropbear_log(LOG_WARNING, - "Pubkey auth attempt with unknown algo for '%s' from %s", - ses.authstate.pw_name, svr_ses.addrstring); - goto out; - } - /* check file permissions, also whether file exists */ if (checkpubkeyperms() == DROPBEAR_FAILURE) { TRACE(("bad authorized_keys permissions, or file doesn't exist")) @@ -427,7 +424,7 @@ static int checkpubkey(const char* algo, unsigned int algolen, } line_num++; - ret = checkpubkey_line(line, line_num, filename, algo, algolen, keyblob, keybloblen); + ret = checkpubkey_line(line, line_num, filename, keyalgo, keyalgolen, keyblob, keybloblen); if (ret == DROPBEAR_SUCCESS) { break; } diff --git a/svr-kex.c b/svr-kex.c index af16d2f..e3e63d0 100644 --- a/svr-kex.c +++ b/svr-kex.c @@ -234,7 +234,7 @@ static void send_msg_kexdh_reply(mp_int *dh_e, buffer *ecdh_qs) { /* calc the signature */ buf_put_sign(ses.writepayload, svr_opts.hostkey, - ses.newkeys->algo_hostkey, ses.hash); + ses.newkeys->algo_signature, ses.hash); /* the SSH_MSG_KEXDH_REPLY is done */ encrypt_packet(); diff --git a/sysoptions.h b/sysoptions.h index 2c27caf..0f52431 100644 --- a/sysoptions.h +++ b/sysoptions.h @@ -139,9 +139,17 @@ If you test it please contact the Dropbear author */ * signing operations slightly slower. */ #define DROPBEAR_RSA_BLINDING 1 +#ifndef DROPBEAR_RSA_SHA1 +#define DROPBEAR_RSA_SHA1 DROPBEAR_RSA +#endif +#ifndef DROPBEAR_RSA_SHA256 +#define DROPBEAR_RSA_SHA256 DROPBEAR_RSA +#endif + /* hashes which will be linked and registered */ -#define DROPBEAR_SHA256 ((DROPBEAR_SHA2_256_HMAC) || (DROPBEAR_ECC_256) \ - || (DROPBEAR_CURVE25519) || (DROPBEAR_DH_GROUP14_SHA256)) +#define DROPBEAR_SHA256 ((DROPBEAR_SHA2_256_HMAC) || (DROPBEAR_ECC_256) \ + || (DROPBEAR_CURVE25519) || (DROPBEAR_DH_GROUP14_SHA256) \ + || (DROPBEAR_RSA_SHA256)) #define DROPBEAR_SHA384 (DROPBEAR_ECC_384) /* LTC SHA384 depends on SHA512 */ #define DROPBEAR_SHA512 ((DROPBEAR_SHA2_512_HMAC) || (DROPBEAR_ECC_521) \ -- cgit v1.2.3 From 972d723484d89c71e73ed63cc17bb2a6ce8cca5a Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Sun, 17 May 2020 23:58:31 +0800 Subject: split signkey_type and signature_type for RSA sha1 vs sha256 --- cli-authpubkey.c | 36 ++++++++++++++++++++------------ common-algo.c | 16 +++++++------- fuzzer-verify.c | 18 ++++++++++++---- rsa.c | 12 +++++------ rsa.h | 4 ++-- session.h | 12 +++++++++-- signkey.c | 63 ++++++++++++++++++++++++++++++++++++++++---------------- signkey.h | 36 +++++++++++++++++++++++++------- ssh.h | 2 +- svr-authpubkey.c | 17 ++++++++++----- 10 files changed, 148 insertions(+), 68 deletions(-) (limited to 'common-algo.c') diff --git a/cli-authpubkey.c b/cli-authpubkey.c index d1cc1f6..0ad80ea 100644 --- a/cli-authpubkey.c +++ b/cli-authpubkey.c @@ -33,7 +33,7 @@ #include "agentfwd.h" #if DROPBEAR_CLI_PUBKEY_AUTH -static void send_msg_userauth_pubkey(sign_key *key, enum signkey_type sigtype, int realsign); +static void send_msg_userauth_pubkey(sign_key *key, enum signature_type sigtype, int realsign); /* Called when we receive a SSH_MSG_USERAUTH_FAILURE for a pubkey request. * We use it to remove the key we tried from the list */ @@ -58,7 +58,8 @@ void recv_msg_userauth_pk_ok() { buffer* keybuf = NULL; char* algotype = NULL; unsigned int algolen; - enum signkey_type sigtype, keytype; + enum signkey_type keytype; + enum signature_type sigtype; unsigned int remotelen; TRACE(("enter recv_msg_userauth_pk_ok")) @@ -113,7 +114,7 @@ void recv_msg_userauth_pk_ok() { TRACE(("matching key")) /* XXX TODO: if it's an encrypted key, here we ask for their * password */ - send_msg_userauth_pubkey((sign_key*)iter->item, keytype, 1); + send_msg_userauth_pubkey((sign_key*)iter->item, sigtype, 1); } else { TRACE(("That was whacky. We got told that a key was valid, but it didn't match our list. Sounds like dodgy code on Dropbear's part")) } @@ -121,7 +122,7 @@ void recv_msg_userauth_pk_ok() { TRACE(("leave recv_msg_userauth_pk_ok")) } -static void cli_buf_put_sign(buffer* buf, sign_key *key, enum signkey_type sigtype, +static void cli_buf_put_sign(buffer* buf, sign_key *key, enum signature_type sigtype, const buffer *data_buf) { #if DROPBEAR_CLI_AGENTFWD // TODO: rsa-sha256 agent @@ -139,14 +140,14 @@ static void cli_buf_put_sign(buffer* buf, sign_key *key, enum signkey_type sigty } } -static void send_msg_userauth_pubkey(sign_key *key, enum signkey_type sigtype, int realsign) { +static void send_msg_userauth_pubkey(sign_key *key, enum signature_type sigtype, int realsign) { const char *algoname = NULL; unsigned int algolen; buffer* sigbuf = NULL; enum signkey_type keytype = signkey_type_from_signature(sigtype); - TRACE(("enter send_msg_userauth_pubkey")) + TRACE(("enter send_msg_userauth_pubkey sigtype %d", sigtype)) CHECKCLEARTOWRITE(); buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_REQUEST); @@ -183,7 +184,6 @@ static void send_msg_userauth_pubkey(sign_key *key, enum signkey_type sigtype, i /* Returns 1 if a key was tried */ int cli_auth_pubkey() { - TRACE(("enter cli_auth_pubkey")) #if DROPBEAR_CLI_AGENTFWD @@ -194,16 +194,26 @@ int cli_auth_pubkey() { } #endif + /* TODO iterate through privkeys to skip ones not in server-sig-algs */ + + /* TODO: testing */ +#if DROPBEAR_RSA_SHA256 + cli_ses.preferred_rsa_sigtype = DROPBEAR_SIGNATURE_RSA_SHA256; +#elif DROPBEAR_RSA_SHA1 + cli_ses.preferred_rsa_sigtype = DROPBEAR_SIGNATURE_RSA_SHA1; +#endif + if (cli_opts.privkeys->first) { sign_key * key = (sign_key*)cli_opts.privkeys->first->item; - enum signkey_type sigtype = key->type; - /* Send a trial request */ -#if DROPBEAR_RSA && DROPBEAR_RSA_SHA256 - // TODO: use ext-info to choose rsa kind - if (sigtype == DROPBEAR_SIGNKEY_RSA) { - sigtype = DROPBEAR_SIGNKEY_RSA_SHA256; + /* Determine the signature type to use */ + enum signature_type sigtype = (enum signature_type)key->type; +#if DROPBEAR_RSA + if (key->type == DROPBEAR_SIGNKEY_RSA) { + sigtype = cli_ses.preferred_rsa_sigtype; } #endif + + /* Send a trial request */ send_msg_userauth_pubkey(key, sigtype, 0); cli_ses.lastprivkey = key; TRACE(("leave cli_auth_pubkey-success")) diff --git a/common-algo.c b/common-algo.c index 28f6744..2a1047e 100644 --- a/common-algo.c +++ b/common-algo.c @@ -223,29 +223,29 @@ algo_type ssh_nocompress[] = { algo_type sshhostkey[] = { #if DROPBEAR_ED25519 - {"ssh-ed25519", DROPBEAR_SIGNKEY_ED25519, NULL, 1, NULL}, + {"ssh-ed25519", DROPBEAR_SIGNATURE_ED25519, NULL, 1, NULL}, #endif #if DROPBEAR_ECDSA #if DROPBEAR_ECC_256 - {"ecdsa-sha2-nistp256", DROPBEAR_SIGNKEY_ECDSA_NISTP256, NULL, 1, NULL}, + {"ecdsa-sha2-nistp256", DROPBEAR_SIGNATURE_ECDSA_NISTP256, NULL, 1, NULL}, #endif #if DROPBEAR_ECC_384 - {"ecdsa-sha2-nistp384", DROPBEAR_SIGNKEY_ECDSA_NISTP384, NULL, 1, NULL}, + {"ecdsa-sha2-nistp384", DROPBEAR_SIGNATURE_ECDSA_NISTP384, NULL, 1, NULL}, #endif #if DROPBEAR_ECC_521 - {"ecdsa-sha2-nistp521", DROPBEAR_SIGNKEY_ECDSA_NISTP521, NULL, 1, NULL}, + {"ecdsa-sha2-nistp521", DROPBEAR_SIGNATURE_ECDSA_NISTP521, NULL, 1, NULL}, #endif #endif #if DROPBEAR_RSA #if DROPBEAR_RSA_SHA256 - {"rsa-sha2-256", DROPBEAR_SIGNKEY_RSA_SHA256, NULL, 1, NULL}, + {"rsa-sha2-256", DROPBEAR_SIGNATURE_RSA_SHA256, NULL, 1, NULL}, #endif #if DROPBEAR_RSA_SHA1 - {"ssh-rsa", DROPBEAR_SIGNKEY_RSA, NULL, 1, NULL}, + {"ssh-rsa", DROPBEAR_SIGNATURE_RSA_SHA1, NULL, 1, NULL}, #endif #endif #if DROPBEAR_DSS - {"ssh-dss", DROPBEAR_SIGNKEY_DSS, NULL, 1, NULL}, + {"ssh-dss", DROPBEAR_SIGNATURE_DSS, NULL, 1, NULL}, #endif {NULL, 0, NULL, 0, NULL} }; @@ -263,8 +263,6 @@ static const struct dropbear_kex kex_dh_group14_sha256 = {DROPBEAR_KEX_NORMAL_DH static const struct dropbear_kex kex_dh_group16_sha512 = {DROPBEAR_KEX_NORMAL_DH, dh_p_16, DH_P_16_LEN, NULL, &sha512_desc }; #endif -/* These can't be const since dropbear_ecc_fill_dp() fills out - ecc_curve at runtime */ #if DROPBEAR_ECDH #if DROPBEAR_ECC_256 static const struct dropbear_kex kex_ecdh_nistp256 = {DROPBEAR_KEX_ECDH, NULL, 0, &ecc_curve_nistp256, &sha256_desc }; diff --git a/fuzzer-verify.c b/fuzzer-verify.c index 8ecbcdb..cda7723 100644 --- a/fuzzer-verify.c +++ b/fuzzer-verify.c @@ -27,15 +27,25 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { if (setjmp(fuzz.jmp) == 0) { sign_key *key = new_sign_key(); - enum signkey_type type = DROPBEAR_SIGNKEY_ANY; - if (buf_get_pub_key(fuzz.input, key, &type) == DROPBEAR_SUCCESS) { - if (buf_verify(fuzz.input, key, verifydata) == DROPBEAR_SUCCESS) { + enum signkey_type keytype = DROPBEAR_SIGNKEY_ANY; + if (buf_get_pub_key(fuzz.input, key, &keytype) == DROPBEAR_SUCCESS) { + enum signature_type sigtype = (enum signature_type)keytype; + if (keytype == DROPBEAR_SIGNKEY_RSA) { + /* Flip a coin to decide rsa signature type */ + int flag = buf_getbyte(fuzz_input); + if (flag & 0x01) { + sigtype = DROPBEAR_SIGNATURE_RSA_SHA256; + } else { + sigtype = DROPBEAR_SIGNATURE_RSA_SHA1; + } + } + if (buf_verify(fuzz.input, key, sigtype, verifydata) == DROPBEAR_SUCCESS) { /* The fuzzer is capable of generating keys with a signature to match. We don't want false positives if the key is bogus, since a client/server wouldn't be trusting a bogus key anyway */ int boguskey = 0; - if (type == DROPBEAR_SIGNKEY_DSS) { + if (keytype == DROPBEAR_SIGNKEY_DSS) { /* So far have seen dss keys with bad p/q/g domain parameters */ int pprime, qprime, trials; trials = mp_prime_rabin_miller_trials(mp_count_bits(key->dsskey->p)); diff --git a/rsa.c b/rsa.c index ef737c8..39e2d46 100644 --- a/rsa.c +++ b/rsa.c @@ -44,7 +44,7 @@ #endif static void rsa_pad_em(const dropbear_rsa_key * key, - const buffer *data_buf, mp_int * rsa_em, enum signkey_type sigtype); + const buffer *data_buf, mp_int * rsa_em, enum signature_type sigtype); /* Load a public rsa key from a buffer, initialising the values. * The key will have the same format as buf_put_rsa_key. @@ -197,7 +197,7 @@ void buf_put_rsa_priv_key(buffer* buf, const dropbear_rsa_key *key) { /* Verify a signature in buf, made on data by the key given. * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ int buf_rsa_verify(buffer * buf, const dropbear_rsa_key *key, - enum signkey_type sigtype, const buffer *data_buf) { + enum signature_type sigtype, const buffer *data_buf) { unsigned int slen; DEF_MP_INT(rsa_s); DEF_MP_INT(rsa_mdash); @@ -253,7 +253,7 @@ out: /* Sign the data presented with key, writing the signature contents * to the buffer */ void buf_put_rsa_sign(buffer* buf, const dropbear_rsa_key *key, - enum signkey_type sigtype, const buffer *data_buf) { + enum signature_type sigtype, const buffer *data_buf) { const char *name = NULL; unsigned int nsize, ssize, namelen = 0; unsigned int i; @@ -352,7 +352,7 @@ void buf_put_rsa_sign(buffer* buf, const dropbear_rsa_key *key, /* Creates the message value as expected by PKCS, see rfc8017 section 9.2 */ static void rsa_pad_em(const dropbear_rsa_key * key, - const buffer *data_buf, mp_int * rsa_em, enum signkey_type sigtype) { + const buffer *data_buf, mp_int * rsa_em, enum signature_type sigtype) { /* EM = 0x00 || 0x01 || PS || 0x00 || T PS is padding of 0xff to make EM the size of key->n @@ -380,14 +380,14 @@ static void rsa_pad_em(const dropbear_rsa_key * key, switch (sigtype) { #if DROPBEAR_RSA_SHA1 - case DROPBEAR_SIGNKEY_RSA: + case DROPBEAR_SIGNATURE_RSA_SHA1: Tlen = sizeof(T_sha1); T = T_sha1; hash_desc = &sha1_desc; break; #endif #if DROPBEAR_RSA_SHA256 - case DROPBEAR_SIGNKEY_RSA_SHA256: + case DROPBEAR_SIGNATURE_RSA_SHA256: Tlen = sizeof(T_sha256); T = T_sha256; hash_desc = &sha256_desc; diff --git a/rsa.h b/rsa.h index 39e6004..a8bbf41 100644 --- a/rsa.h +++ b/rsa.h @@ -43,10 +43,10 @@ typedef struct dropbear_RSA_Key { } dropbear_rsa_key; void buf_put_rsa_sign(buffer* buf, const dropbear_rsa_key *key, - enum signkey_type sigtype, const buffer *data_buf); + enum signature_type sigtype, const buffer *data_buf); #if DROPBEAR_SIGNKEY_VERIFY int buf_rsa_verify(buffer * buf, const dropbear_rsa_key *key, - enum signkey_type sigtype, const buffer *data_buf); + enum signature_type sigtype, const buffer *data_buf); #endif int buf_get_rsa_pub_key(buffer* buf, dropbear_rsa_key *key); int buf_get_rsa_priv_key(buffer* buf, dropbear_rsa_key *key); diff --git a/session.h b/session.h index ebfc915..cbe9cb5 100644 --- a/session.h +++ b/session.h @@ -92,8 +92,8 @@ struct key_context { struct key_context_directional trans; const struct dropbear_kex *algo_kex; - int algo_hostkey; /* server key type */ - int algo_signature; /* server signature type */ + enum signkey_type algo_hostkey; /* server key type */ + enum signature_type algo_signature; /* server signature type */ int allow_compress; /* whether compression has started (useful in zlib@openssh.com delayed compression case) */ @@ -313,6 +313,14 @@ struct clientsession { #endif sign_key *lastprivkey; + enum signature_type server_sig_algs[DROPBEAR_SIGNKEY_NUM_NAMED+1]; + int server_sig_algs_count; +#if DROPBEAR_RSA + /* Set to DROPBEAR_SIGNATURE_RSA_SHA256 or DROPBEAR_SIGNATURE_RSA_SHA1 + if depending which the server accepts */ + enum signature_type preferred_rsa_sigtype; +#endif + int retval; /* What the command exit status was - we emulate it */ #if 0 TODO diff --git a/signkey.c b/signkey.c index 27b09a3..47c8c8e 100644 --- a/signkey.c +++ b/signkey.c @@ -111,34 +111,52 @@ enum signkey_type signkey_type_from_name(const char* name, unsigned int namelen) /* Special case for rsa-sha2-256. This could be generalised if more signature names are added that aren't 1-1 with public key names */ -const char* signature_name_from_type(enum signkey_type type, unsigned int *namelen) { +const char* signature_name_from_type(enum signature_type type, unsigned int *namelen) { #if DROPBEAR_RSA_SHA256 - if (type == DROPBEAR_SIGNKEY_RSA_SHA256) { - *namelen = strlen(SSH_SIGNKEY_RSA_SHA256); - return SSH_SIGNKEY_RSA_SHA256; + if (type == DROPBEAR_SIGNATURE_RSA_SHA256) { + *namelen = strlen(SSH_SIGNATURE_RSA_SHA256); + return SSH_SIGNATURE_RSA_SHA256; } #endif - return signkey_name_from_type(type, namelen); +#if DROPBEAR_RSA_SHA1 + if (type == DROPBEAR_SIGNATURE_RSA_SHA1) { + *namelen = strlen(SSH_SIGNKEY_RSA); + return SSH_SIGNKEY_RSA; + } +#endif + return signkey_name_from_type((enum signkey_type)type, namelen); } -enum signkey_type signature_type_from_name(const char* name, unsigned int namelen) { +/* Returns DROPBEAR_SIGNATURE_NONE if none match */ +enum signature_type signature_type_from_name(const char* name, unsigned int namelen) { #if DROPBEAR_RSA_SHA256 - if (namelen == strlen(SSH_SIGNKEY_RSA_SHA256) - && memcmp(name, SSH_SIGNKEY_RSA_SHA256, namelen) == 0) { - return DROPBEAR_SIGNKEY_RSA_SHA256; + if (namelen == strlen(SSH_SIGNATURE_RSA_SHA256) + && memcmp(name, SSH_SIGNATURE_RSA_SHA256, namelen) == 0) { + return DROPBEAR_SIGNATURE_RSA_SHA256; } #endif - return signkey_type_from_name(name, namelen); +#if DROPBEAR_RSA_SHA256 + if (namelen == strlen(SSH_SIGNKEY_RSA) + && memcmp(name, SSH_SIGNKEY_RSA, namelen) == 0) { + return DROPBEAR_SIGNATURE_RSA_SHA1; + } +#endif + return (enum signature_type)signkey_type_from_name(name, namelen); } -enum signkey_type signkey_type_from_signature(enum signkey_type sigtype) { +enum signkey_type signkey_type_from_signature(enum signature_type sigtype) { #if DROPBEAR_RSA_SHA256 - if (sigtype == DROPBEAR_SIGNKEY_RSA_SHA256) { + if (sigtype == DROPBEAR_SIGNATURE_RSA_SHA256) { + return DROPBEAR_SIGNKEY_RSA; + } +#endif +#if DROPBEAR_RSA_SHA1 + if (sigtype == DROPBEAR_SIGNATURE_RSA_SHA1) { return DROPBEAR_SIGNKEY_RSA; } #endif assert(sigtype < DROPBEAR_SIGNKEY_NUM_NAMED); - return sigtype; + return (enum signkey_type)sigtype; } /* Returns a pointer to the key part specific to "type". @@ -562,11 +580,20 @@ char * sign_key_fingerprint(const unsigned char* keyblob, unsigned int keybloble #endif } -void buf_put_sign(buffer* buf, sign_key *key, enum signkey_type sigtype, +void buf_put_sign(buffer* buf, sign_key *key, enum signature_type sigtype, const buffer *data_buf) { buffer *sigblob = buf_new(MAX_PUBKEY_SIZE); enum signkey_type keytype = signkey_type_from_signature(sigtype); +#if DEBUG_TRACE + { + int namelen; + const char* signame = signature_name_from_type(sigtype, &namelen); + TRACE(("buf_put_sign type %d %s", sigtype, signame)); + } +#endif + + #if DROPBEAR_DSS if (keytype == DROPBEAR_SIGNKEY_DSS) { buf_put_dss_sign(sigblob, key->dsskey, data_buf); @@ -603,11 +630,12 @@ void buf_put_sign(buffer* buf, sign_key *key, enum signkey_type sigtype, * If FAILURE is returned, the position of * buf is undefined. If SUCCESS is returned, buf will be positioned after the * signature blob */ -int buf_verify(buffer * buf, sign_key *key, enum signkey_type expect_sigtype, const buffer *data_buf) { +int buf_verify(buffer * buf, sign_key *key, enum signature_type expect_sigtype, const buffer *data_buf) { char *type_name = NULL; unsigned int type_name_len = 0; - enum signkey_type sigtype, keytype; + enum signature_type sigtype; + enum signkey_type keytype; TRACE(("enter buf_verify")) @@ -616,8 +644,7 @@ int buf_verify(buffer * buf, sign_key *key, enum signkey_type expect_sigtype, co sigtype = signature_type_from_name(type_name, type_name_len); m_free(type_name); - if (expect_sigtype != DROPBEAR_SIGNKEY_ANY - && expect_sigtype != sigtype) { + if (expect_sigtype != sigtype) { dropbear_exit("Non-matching signing type"); } diff --git a/signkey.h b/signkey.h index 78b237b..9468509 100644 --- a/signkey.h +++ b/signkey.h @@ -32,6 +32,7 @@ struct dropbear_DSS_Key; struct dropbear_RSA_Key; struct dropbear_ED25519_Key; +/* Must match with signature_type below */ enum signkey_type { #if DROPBEAR_RSA DROPBEAR_SIGNKEY_RSA, @@ -49,13 +50,32 @@ enum signkey_type { #endif DROPBEAR_SIGNKEY_NUM_NAMED, DROPBEAR_SIGNKEY_ECDSA_KEYGEN = 70, /* just "ecdsa" for keygen */ -#if DROPBEAR_RSA_SHA256 - DROPBEAR_SIGNKEY_RSA_SHA256, /* rsa-sha2-256 signature. has a ssh-rsa key */ -#endif DROPBEAR_SIGNKEY_ANY = 80, DROPBEAR_SIGNKEY_NONE = 90, }; +/* Must match with signkey_type above, apart from rsa */ +enum signature_type { +#if DROPBEAR_DSS + DROPBEAR_SIGNATURE_DSS = DROPBEAR_SIGNKEY_DSS, +#endif +#if DROPBEAR_ECDSA + DROPBEAR_SIGNATURE_ECDSA_NISTP256 = DROPBEAR_SIGNKEY_ECDSA_NISTP256, + DROPBEAR_SIGNATURE_ECDSA_NISTP384 = DROPBEAR_SIGNKEY_ECDSA_NISTP384, + DROPBEAR_SIGNATURE_ECDSA_NISTP521 = DROPBEAR_SIGNKEY_ECDSA_NISTP521, +#endif /* DROPBEAR_ECDSA */ +#if DROPBEAR_ED25519 + DROPBEAR_SIGNATURE_ED25519 = DROPBEAR_SIGNKEY_ED25519, +#endif +#if DROPBEAR_RSA_SHA1 + DROPBEAR_SIGNATURE_RSA_SHA1 = 100, /* ssh-rsa signature (sha1) */ +#endif +#if DROPBEAR_RSA_SHA256 + DROPBEAR_SIGNATURE_RSA_SHA256 = 101, /* rsa-sha2-256 signature. has a ssh-rsa key */ +#endif + DROPBEAR_SIGNATURE_NONE = DROPBEAR_SIGNKEY_NONE, +}; + /* Sources for signing keys */ typedef enum { @@ -97,17 +117,17 @@ typedef struct SIGN_key sign_key; sign_key * new_sign_key(void); const char* signkey_name_from_type(enum signkey_type type, unsigned int *namelen); enum signkey_type signkey_type_from_name(const char* name, unsigned int namelen); -const char* signature_name_from_type(enum signkey_type type, unsigned int *namelen); -enum signkey_type signature_type_from_name(const char* name, unsigned int namelen); -enum signkey_type signkey_type_from_signature(enum signkey_type sigtype); +const char* signature_name_from_type(enum signature_type type, unsigned int *namelen); +enum signature_type signature_type_from_name(const char* name, unsigned int namelen); +enum signkey_type signkey_type_from_signature(enum signature_type sigtype); int buf_get_pub_key(buffer *buf, sign_key *key, enum signkey_type *type); int buf_get_priv_key(buffer* buf, sign_key *key, enum signkey_type *type); void buf_put_pub_key(buffer* buf, sign_key *key, enum signkey_type type); void buf_put_priv_key(buffer* buf, sign_key *key, enum signkey_type type); void sign_key_free(sign_key *key); -void buf_put_sign(buffer* buf, sign_key *key, enum signkey_type sigtype, const buffer *data_buf); +void buf_put_sign(buffer* buf, sign_key *key, enum signature_type sigtype, const buffer *data_buf); #if DROPBEAR_SIGNKEY_VERIFY -int buf_verify(buffer * buf, sign_key *key, enum signkey_type type, const buffer *data_buf); +int buf_verify(buffer * buf, sign_key *key, enum signature_type expect_sigtype, const buffer *data_buf); char * sign_key_fingerprint(const unsigned char* keyblob, unsigned int keybloblen); #endif int cmp_base64_key(const unsigned char* keyblob, unsigned int keybloblen, diff --git a/ssh.h b/ssh.h index fb7f9f2..5030fe9 100644 --- a/ssh.h +++ b/ssh.h @@ -108,7 +108,7 @@ #define SSH_SIGNKEY_ED25519 "ssh-ed25519" #define SSH_SIGNKEY_ED25519_LEN 11 /* signature type */ -#define SSH_SIGNKEY_RSA_SHA256 "rsa-sha2-256" +#define SSH_SIGNATURE_RSA_SHA256 "rsa-sha2-256" /* Agent commands. These aren't part of the spec, and are defined * only on the openssh implementation. */ diff --git a/svr-authpubkey.c b/svr-authpubkey.c index a52667c..46237b7 100644 --- a/svr-authpubkey.c +++ b/svr-authpubkey.c @@ -92,7 +92,8 @@ void svr_auth_pubkey(int valid_user) { buffer * signbuf = NULL; sign_key * key = NULL; char* fp = NULL; - enum signkey_type sigtype, keytype; + enum signature_type sigtype; + enum signkey_type keytype; int auth_failure = 1; TRACE(("enter pubkeyauth")) @@ -102,10 +103,6 @@ void svr_auth_pubkey(int valid_user) { testkey = (buf_getbool(ses.payload) == 0); sigalgo = buf_getstring(ses.payload, &sigalgolen); - sigtype = signature_type_from_name(sigalgo, sigalgolen); - keytype = signkey_type_from_signature(sigtype); - keyalgo = signkey_name_from_type(keytype, &keyalgolen); - keybloblen = buf_getint(ses.payload); keyblob = buf_getptr(ses.payload, keybloblen); @@ -117,6 +114,16 @@ void svr_auth_pubkey(int valid_user) { send_msg_userauth_failure(0, 0); goto out; } + + sigtype = signature_type_from_name(sigalgo, sigalgolen); + if (sigtype == DROPBEAR_SIGNATURE_NONE) { + send_msg_userauth_failure(0, 0); + goto out; + } + + keytype = signkey_type_from_signature(sigtype); + keyalgo = signkey_name_from_type(keytype, &keyalgolen); + #if DROPBEAR_PLUGIN if (svr_ses.plugin_instance != NULL) { char *options_buf; -- cgit v1.2.3 From 5acee497bf3e44d86bdecf5bfa35042c363ab753 Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Tue, 19 May 2020 00:31:41 +0800 Subject: ext-info handling for server-sig-algs only client side is handled --- algo.h | 11 +---- buffer.c | 22 +++++++++- buffer.h | 1 + cli-authpubkey.c | 78 +++++++++++++++++++++++++++++------- cli-kex.c | 25 ++++++++++++ cli-main.c | 1 + cli-session.c | 5 ++- common-algo.c | 119 ++++++++++++++++++++++++++++++++++++------------------- common-kex.c | 30 +++++++++----- fuzzer-verify.c | 4 +- kex.h | 2 + session.h | 11 ++--- signkey.c | 21 ++++++++-- signkey.h | 2 + ssh.h | 6 +++ svr-session.c | 7 +++- 16 files changed, 255 insertions(+), 90 deletions(-) (limited to 'common-algo.c') diff --git a/algo.h b/algo.h index 1a6d384..1565665 100644 --- a/algo.h +++ b/algo.h @@ -114,18 +114,11 @@ struct dropbear_kex { void buf_put_algolist(buffer * buf, const algo_type localalgos[]); -enum kexguess2_used { - KEXGUESS2_LOOK, - KEXGUESS2_NO, - KEXGUESS2_YES, -}; - #define KEXGUESS2_ALGO_NAME "kexguess2@matt.ucc.asn.au" -#define KEXGUESS2_ALGO_ID 99 - +int buf_has_algo(buffer *buf, const char *algo); algo_type * buf_match_algo(buffer* buf, algo_type localalgos[], - enum kexguess2_used *kexguess2, int *goodguess); + int kexguess2, int *goodguess); #if DROPBEAR_USER_ALGO_LIST int check_user_algos(const char* user_algo_list, algo_type * algos, diff --git a/buffer.c b/buffer.c index e70ec29..dc8b909 100644 --- a/buffer.c +++ b/buffer.c @@ -228,19 +228,37 @@ char* buf_getstring(buffer* buf, unsigned int *retlen) { } /* Return a string as a newly allocated buffer */ -buffer * buf_getstringbuf(buffer *buf) { +static buffer * buf_getstringbuf_int(buffer *buf, int incllen) { buffer *ret = NULL; unsigned int len = buf_getint(buf); + int extra = 0; if (len > MAX_STRING_LEN) { dropbear_exit("String too long"); } - ret = buf_new(len); + if (incllen) { + extra = 4; + } + ret = buf_new(len+extra); + if (incllen) { + buf_putint(ret, len); + } memcpy(buf_getwriteptr(ret, len), buf_getptr(buf, len), len); buf_incrpos(buf, len); buf_incrlen(ret, len); + buf_setpos(ret, 0); return ret; } +/* Return a string as a newly allocated buffer */ +buffer * buf_getstringbuf(buffer *buf) { + return buf_getstringbuf_int(buf, 0); +} + +/* Returns a string in a new buffer, including the length */ +buffer * buf_getbuf(buffer *buf) { + return buf_getstringbuf_int(buf, 1); +} + /* Just increment the buffer position the same as if we'd used buf_getstring, * but don't bother copying/malloc()ing for it */ void buf_eatstring(buffer *buf) { diff --git a/buffer.h b/buffer.h index befe1c7..85f913d 100644 --- a/buffer.h +++ b/buffer.h @@ -58,6 +58,7 @@ unsigned char* buf_getptr(const buffer* buf, unsigned int len); unsigned char* buf_getwriteptr(const buffer* buf, unsigned int len); char* buf_getstring(buffer* buf, unsigned int *retlen); buffer * buf_getstringbuf(buffer *buf); +buffer * buf_getbuf(buffer *buf); void buf_eatstring(buffer *buf); void buf_putint(buffer* buf, unsigned int val); void buf_putstring(buffer* buf, const char* str, unsigned int len); diff --git a/cli-authpubkey.c b/cli-authpubkey.c index 0ad80ea..8426e84 100644 --- a/cli-authpubkey.c +++ b/cli-authpubkey.c @@ -184,6 +184,7 @@ static void send_msg_userauth_pubkey(sign_key *key, enum signature_type sigtype, /* Returns 1 if a key was tried */ int cli_auth_pubkey() { + enum signature_type sigtype; TRACE(("enter cli_auth_pubkey")) #if DROPBEAR_CLI_AGENTFWD @@ -191,28 +192,77 @@ int cli_auth_pubkey() { /* get the list of available keys from the agent */ cli_load_agent_keys(cli_opts.privkeys); cli_opts.agent_keys_loaded = 1; + TRACE(("cli_auth_pubkey: agent keys loaded")) } #endif - /* TODO iterate through privkeys to skip ones not in server-sig-algs */ - - /* TODO: testing */ + /* iterate through privkeys to remove ones not allowed in server-sig-algs */ + while (cli_opts.privkeys->first) { + sign_key * key = (sign_key*)cli_opts.privkeys->first->item; + if (cli_ses.server_sig_algs) { +#ifdef DROPBEAR_RSA + if (key->type == DROPBEAR_SIGNKEY_RSA) { #if DROPBEAR_RSA_SHA256 - cli_ses.preferred_rsa_sigtype = DROPBEAR_SIGNATURE_RSA_SHA256; -#elif DROPBEAR_RSA_SHA1 - cli_ses.preferred_rsa_sigtype = DROPBEAR_SIGNATURE_RSA_SHA1; + if (buf_has_algo(cli_ses.server_sig_algs, SSH_SIGNATURE_RSA_SHA256) + == DROPBEAR_SUCCESS) { + sigtype = DROPBEAR_SIGNATURE_RSA_SHA256; + TRACE(("server-sig-algs allows rsa sha256")) + break; + } +#endif /* DROPBEAR_RSA_SHA256 */ +#if DROPBEAR_RSA_SHA1 + if (buf_has_algo(cli_ses.server_sig_algs, SSH_SIGNKEY_RSA) + == DROPBEAR_SUCCESS) { + sigtype = DROPBEAR_SIGNATURE_RSA_SHA1; + TRACE(("server-sig-algs allows rsa sha1")) + break; + } +#endif /* DROPBEAR_RSA_SHA256 */ + } else +#endif /* DROPBEAR_RSA */ + { + /* Not RSA */ + const char *name = NULL; + sigtype = signature_type_from_signkey(key->type); + name = signature_name_from_type(sigtype, NULL); + if (buf_has_algo(cli_ses.server_sig_algs, name) + == DROPBEAR_SUCCESS) { + TRACE(("server-sig-algs allows %s", name)) + break; + } + } + + /* No match, skip this key */ + TRACE(("server-sig-algs no match keytype %d, skipping", key->type)) + key = list_remove(cli_opts.privkeys->first); + sign_key_free(key); + continue; + } else { + /* Server didn't provide a server-sig-algs list, we'll + assume all except rsa-sha256 are OK. */ +#if DROPBEAR_RSA + if (key->type == DROPBEAR_SIGNKEY_RSA) { +#ifdef DROPBEAR_RSA_SHA1 + sigtype = DROPBEAR_SIGNATURE_RSA_SHA1; + TRACE(("no server-sig-algs, using rsa sha1")) + break; +#else + /* only support rsa-sha256, skip this key */ + TRACE(("no server-sig-algs, skipping rsa sha256")) + key = list_remove(cli_opts.privkeys->first); + sign_key_free(key); + continue; #endif + } /* key->type == DROPBEAR_SIGNKEY_RSA */ +#endif /* DROPBEAR_RSA */ + sigtype = signature_type_from_signkey(key->type); + TRACE(("no server-sig-algs, using key")) + break; + } + } if (cli_opts.privkeys->first) { sign_key * key = (sign_key*)cli_opts.privkeys->first->item; - /* Determine the signature type to use */ - enum signature_type sigtype = (enum signature_type)key->type; -#if DROPBEAR_RSA - if (key->type == DROPBEAR_SIGNKEY_RSA) { - sigtype = cli_ses.preferred_rsa_sigtype; - } -#endif - /* Send a trial request */ send_msg_userauth_pubkey(key, sigtype, 0); cli_ses.lastprivkey = key; diff --git a/cli-kex.c b/cli-kex.c index 7337ffd..99370eb 100644 --- a/cli-kex.c +++ b/cli-kex.c @@ -411,3 +411,28 @@ out: } m_free(fingerprint); } + +void recv_msg_ext_info(void) { + /* This message is not client-specific in the protocol but Dropbear only handles + a server-sent message at present. */ + unsigned int num_ext; + unsigned int i; + + num_ext = buf_getint(ses.payload); + TRACE(("received SSH_MSG_EXT_INFO with %d items", num_ext)) + + for (i = 0; i < num_ext; i++) { + unsigned int name_len; + char *ext_name = buf_getstring(ses.payload, &name_len); + TRACE(("extension %d name '%s'", i, ext_name)) + if (cli_ses.server_sig_algs == NULL + && name_len == strlen(SSH_SERVER_SIG_ALGS) + && strcmp(ext_name, SSH_SERVER_SIG_ALGS) == 0) { + cli_ses.server_sig_algs = buf_getbuf(ses.payload); + } else { + /* valid extension values could be >MAX_STRING_LEN */ + buf_eatstring(ses.payload); + } + m_free(ext_name); + } +} diff --git a/cli-main.c b/cli-main.c index 713cb09..b43b1e2 100644 --- a/cli-main.c +++ b/cli-main.c @@ -106,6 +106,7 @@ static void cli_dropbear_exit(int exitcode, const char* format, va_list param) { /* Render the formatted exit message */ vsnprintf(exitmsg, sizeof(exitmsg), format, param); + TRACE(("Exited, cleaning up: %s", exitmsg)) /* Add the prefix depending on session/auth state */ if (!ses.init_done) { diff --git a/cli-session.c b/cli-session.c index 56dd4af..fc81e2d 100644 --- a/cli-session.c +++ b/cli-session.c @@ -81,6 +81,7 @@ static const packettype cli_packettypes[] = { {SSH_MSG_REQUEST_SUCCESS, ignore_recv_response}, {SSH_MSG_REQUEST_FAILURE, ignore_recv_response}, #endif + {SSH_MSG_EXT_INFO, recv_msg_ext_info}, {0, NULL} /* End */ }; @@ -352,7 +353,9 @@ static void cli_session_cleanup(void) { (void)fcntl(cli_ses.stderrcopy, F_SETFL, cli_ses.stderrflags); cli_tty_cleanup(); - + if (cli_ses.server_sig_algs) { + buf_free(cli_ses.server_sig_algs); + } } static void cli_finished() { diff --git a/common-algo.c b/common-algo.c index 2a1047e..938695d 100644 --- a/common-algo.c +++ b/common-algo.c @@ -30,6 +30,7 @@ #include "dh_groups.h" #include "ltc_prng.h" #include "ecc.h" +#include "ssh.h" /* This file (algo.c) organises the ciphers which can be used, and is used to * decide which ciphers/hashes/compression/signing to use during key exchange*/ @@ -280,6 +281,7 @@ static const struct dropbear_kex kex_ecdh_nistp521 = {DROPBEAR_KEX_ECDH, NULL, 0 static const struct dropbear_kex kex_curve25519 = {DROPBEAR_KEX_CURVE25519, NULL, 0, NULL, &sha256_desc }; #endif +/* data == NULL for non-kex algorithm identifiers */ algo_type sshkex[] = { #if DROPBEAR_CURVE25519 {"curve25519-sha256", 0, &kex_curve25519, 1, NULL}, @@ -309,7 +311,11 @@ algo_type sshkex[] = { {"diffie-hellman-group16-sha512", 0, &kex_dh_group16_sha512, 1, NULL}, #endif #if DROPBEAR_KEXGUESS2 - {KEXGUESS2_ALGO_NAME, KEXGUESS2_ALGO_ID, NULL, 1, NULL}, + {KEXGUESS2_ALGO_NAME, 0, NULL, 1, NULL}, +#endif +#if DROPBEAR_CLIENT + /* Set unusable by svr_algos_initialise() */ + {SSH_EXT_INFO_C, 0, NULL, 1, NULL}, #endif {NULL, 0, NULL, 0, NULL} }; @@ -336,15 +342,79 @@ void buf_put_algolist(buffer * buf, const algo_type localalgos[]) { buf_free(algolist); } +/* returns a list of pointers into algolist, of null-terminated names. + ret_list should be passed in with space for *ret_count elements, + on return *ret_count has the number of names filled. + algolist is modified. */ +static void get_algolist(char* algolist, unsigned int algolist_len, + const char* *ret_list, unsigned int *ret_count) { + unsigned int max_count = *ret_count; + unsigned int i; + + if (*ret_count == 0) { + return; + } + if (algolist_len > MAX_PROPOSED_ALGO*(MAX_NAME_LEN+1)) { + *ret_count = 0; + } + + /* ret_list will contain a list of the strings parsed out. + We will have at least one string (even if it's just "") */ + ret_list[0] = algolist; + *ret_count = 1; + for (i = 0; i < algolist_len; i++) { + if (algolist[i] == '\0') { + /* someone is trying something strange */ + *ret_count = 0; + return; + } + + if (algolist[i] == ',') { + if (*ret_count >= max_count) { + /* Too many */ + *ret_count = 0; + return; + } + algolist[i] = '\0'; + ret_list[*ret_count] = &algolist[i+1]; + (*ret_count)++; + } + } +} + +/* Return DROPBEAR_SUCCESS if the namelist contains algo, +DROPBEAR_FAILURE otherwise. buf position is not incremented. */ +int buf_has_algo(buffer *buf, const char *algo) { + unsigned char* algolist = NULL; + unsigned int orig_pos = buf->pos; + unsigned int len, remotecount, i; + const char *remotenames[MAX_PROPOSED_ALGO]; + int ret = DROPBEAR_FAILURE; + + algolist = buf_getstring(buf, &len); + remotecount = MAX_PROPOSED_ALGO; + get_algolist(algolist, len, remotenames, &remotecount); + for (i = 0; i < remotecount; i++) + { + if (strcmp(remotenames[i], algo) == 0) { + ret = DROPBEAR_SUCCESS; + break; + } + } + if (algolist) { + m_free(algolist); + } + buf_setpos(buf, orig_pos); + return ret; +} + /* match the first algorithm in the comma-separated list in buf which is * also in localalgos[], or return NULL on failure. * (*goodguess) is set to 1 if the preferred client/server algos match, * 0 otherwise. This is used for checking if the kexalgo/hostkeyalgos are * guessed correctly */ algo_type * buf_match_algo(buffer* buf, algo_type localalgos[], - enum kexguess2_used *kexguess2, int *goodguess) -{ - + int kexguess2, int *goodguess) { char * algolist = NULL; const char *remotenames[MAX_PROPOSED_ALGO], *localnames[MAX_PROPOSED_ALGO]; unsigned int len; @@ -359,40 +429,8 @@ algo_type * buf_match_algo(buffer* buf, algo_type localalgos[], /* get the comma-separated list from the buffer ie "algo1,algo2,algo3" */ algolist = buf_getstring(buf, &len); TRACE(("buf_match_algo: %s", algolist)) - if (len > MAX_PROPOSED_ALGO*(MAX_NAME_LEN+1)) { - goto out; - } - - /* remotenames will contain a list of the strings parsed out */ - /* We will have at least one string (even if it's just "") */ - remotenames[0] = algolist; - remotecount = 1; - for (i = 0; i < len; i++) { - if (algolist[i] == '\0') { - /* someone is trying something strange */ - goto out; - } - if (algolist[i] == ',') { - algolist[i] = '\0'; - remotenames[remotecount] = &algolist[i+1]; - remotecount++; - } - if (remotecount >= MAX_PROPOSED_ALGO) { - break; - } - } - if (kexguess2 && *kexguess2 == KEXGUESS2_LOOK) { - for (i = 0; i < remotecount; i++) - { - if (strcmp(remotenames[i], KEXGUESS2_ALGO_NAME) == 0) { - *kexguess2 = KEXGUESS2_YES; - break; - } - } - if (*kexguess2 == KEXGUESS2_LOOK) { - *kexguess2 = KEXGUESS2_NO; - } - } + remotecount = MAX_PROPOSED_ALGO; + get_algolist(algolist, len, remotenames, &remotecount); for (i = 0; localalgos[i].name != NULL; i++) { if (localalgos[i].usable) { @@ -424,12 +462,11 @@ algo_type * buf_match_algo(buffer* buf, algo_type localalgos[], } if (strcmp(servnames[j], clinames[i]) == 0) { /* set if it was a good guess */ - if (goodguess && kexguess2) { - if (*kexguess2 == KEXGUESS2_YES) { + if (goodguess != NULL) { + if (kexguess2) { if (i == 0) { *goodguess = 1; } - } else { if (i == 0 && j == 0) { *goodguess = 1; diff --git a/common-kex.c b/common-kex.c index c1392c1..8172796 100644 --- a/common-kex.c +++ b/common-kex.c @@ -820,21 +820,33 @@ static void read_kex_algos() { int goodguess = 0; int allgood = 1; /* we AND this with each goodguess and see if its still true after */ - -#if DROPBEAR_KEXGUESS2 - enum kexguess2_used kexguess2 = KEXGUESS2_LOOK; -#else - enum kexguess2_used kexguess2 = KEXGUESS2_NO; -#endif + int kexguess2 = 0; buf_incrpos(ses.payload, 16); /* start after the cookie */ memset(ses.newkeys, 0x0, sizeof(*ses.newkeys)); /* kex_algorithms */ - algo = buf_match_algo(ses.payload, sshkex, &kexguess2, &goodguess); +#if DROPBEAR_KEXGUESS2 + if (buf_has_algo(ses.payload, KEXGUESS2_ALGO_NAME) == DROPBEAR_SUCCESS) { + kexguess2 = 1; + } +#endif + + /* Determine if SSH_MSG_EXT_INFO messages should be sent. + Should be done for the first key exchange. */ + if (!ses.kexstate.donefirstkex) { + if (IS_DROPBEAR_SERVER) { + if (buf_has_algo(ses.payload, SSH_EXT_INFO_C) == DROPBEAR_SUCCESS) { + ses.allow_ext_info = 1; + } + } + } + + algo = buf_match_algo(ses.payload, sshkex, kexguess2, &goodguess); allgood &= goodguess; - if (algo == NULL || algo->val == KEXGUESS2_ALGO_ID) { + if (algo == NULL || algo->data == NULL) { + /* kexguess2, ext-info-c, ext-info-s should not match negotiation */ erralgo = "kex"; goto error; } @@ -843,7 +855,7 @@ static void read_kex_algos() { ses.newkeys->algo_kex = algo->data; /* server_host_key_algorithms */ - algo = buf_match_algo(ses.payload, sshhostkey, &kexguess2, &goodguess); + algo = buf_match_algo(ses.payload, sshhostkey, kexguess2, &goodguess); allgood &= goodguess; if (algo == NULL) { erralgo = "hostkey"; diff --git a/fuzzer-verify.c b/fuzzer-verify.c index cda7723..cdf7804 100644 --- a/fuzzer-verify.c +++ b/fuzzer-verify.c @@ -29,7 +29,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { sign_key *key = new_sign_key(); enum signkey_type keytype = DROPBEAR_SIGNKEY_ANY; if (buf_get_pub_key(fuzz.input, key, &keytype) == DROPBEAR_SUCCESS) { - enum signature_type sigtype = (enum signature_type)keytype; + enum signature_type sigtype; if (keytype == DROPBEAR_SIGNKEY_RSA) { /* Flip a coin to decide rsa signature type */ int flag = buf_getbyte(fuzz_input); @@ -38,6 +38,8 @@ int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { } else { sigtype = DROPBEAR_SIGNATURE_RSA_SHA1; } + } else { + sigtype = signature_type_from_signkey(keytype); } if (buf_verify(fuzz.input, key, sigtype, verifydata) == DROPBEAR_SUCCESS) { /* The fuzzer is capable of generating keys with a signature to match. diff --git a/kex.h b/kex.h index 03dcbb1..3b0ec62 100644 --- a/kex.h +++ b/kex.h @@ -65,6 +65,8 @@ void recv_msg_kexdh_init(void); /* server */ void send_msg_kexdh_init(void); /* client */ void recv_msg_kexdh_reply(void); /* client */ +void recv_msg_ext_info(void); + struct KEXState { unsigned sentkexinit : 1; /*set when we've sent/recv kexinit packet */ diff --git a/session.h b/session.h index cbe9cb5..1059537 100644 --- a/session.h +++ b/session.h @@ -185,6 +185,9 @@ struct sshsession { /* Enables/disables compression */ algo_type *compress_algos; + + /* Other side allows SSH_MSG_EXT_INFO */ + int allow_ext_info; /* a list of queued replies that should be sent after a KEX has concluded (ie, while dataallowed was unset)*/ @@ -313,13 +316,7 @@ struct clientsession { #endif sign_key *lastprivkey; - enum signature_type server_sig_algs[DROPBEAR_SIGNKEY_NUM_NAMED+1]; - int server_sig_algs_count; -#if DROPBEAR_RSA - /* Set to DROPBEAR_SIGNATURE_RSA_SHA256 or DROPBEAR_SIGNATURE_RSA_SHA1 - if depending which the server accepts */ - enum signature_type preferred_rsa_sigtype; -#endif + buffer *server_sig_algs; int retval; /* What the command exit status was - we emulate it */ #if 0 diff --git a/signkey.c b/signkey.c index 47c8c8e..6ed1667 100644 --- a/signkey.c +++ b/signkey.c @@ -114,13 +114,17 @@ enum signkey_type signkey_type_from_name(const char* name, unsigned int namelen) const char* signature_name_from_type(enum signature_type type, unsigned int *namelen) { #if DROPBEAR_RSA_SHA256 if (type == DROPBEAR_SIGNATURE_RSA_SHA256) { - *namelen = strlen(SSH_SIGNATURE_RSA_SHA256); + if (namelen) { + *namelen = strlen(SSH_SIGNATURE_RSA_SHA256); + } return SSH_SIGNATURE_RSA_SHA256; } #endif #if DROPBEAR_RSA_SHA1 if (type == DROPBEAR_SIGNATURE_RSA_SHA1) { - *namelen = strlen(SSH_SIGNKEY_RSA); + if (namelen) { + *namelen = strlen(SSH_SIGNKEY_RSA); + } return SSH_SIGNKEY_RSA; } #endif @@ -144,6 +148,16 @@ enum signature_type signature_type_from_name(const char* name, unsigned int name return (enum signature_type)signkey_type_from_name(name, namelen); } +/* Returns the signature type from a key type. Must not be called + with RSA keytype */ +enum signature_type signature_type_from_signkey(enum signkey_type keytype) { +#if DROPBEAR_RSA + assert(keytype != DROPBEAR_SIGNKEY_RSA); +#endif + assert(keytype < DROPBEAR_SIGNKEY_NUM_NAMED); + return (enum signature_type)keytype; +} + enum signkey_type signkey_type_from_signature(enum signature_type sigtype) { #if DROPBEAR_RSA_SHA256 if (sigtype == DROPBEAR_SIGNATURE_RSA_SHA256) { @@ -587,8 +601,7 @@ void buf_put_sign(buffer* buf, sign_key *key, enum signature_type sigtype, #if DEBUG_TRACE { - int namelen; - const char* signame = signature_name_from_type(sigtype, &namelen); + const char* signame = signature_name_from_type(sigtype, NULL); TRACE(("buf_put_sign type %d %s", sigtype, signame)); } #endif diff --git a/signkey.h b/signkey.h index 9468509..2640171 100644 --- a/signkey.h +++ b/signkey.h @@ -120,6 +120,8 @@ enum signkey_type signkey_type_from_name(const char* name, unsigned int namelen) const char* signature_name_from_type(enum signature_type type, unsigned int *namelen); enum signature_type signature_type_from_name(const char* name, unsigned int namelen); enum signkey_type signkey_type_from_signature(enum signature_type sigtype); +enum signature_type signature_type_from_signkey(enum signkey_type keytype); + int buf_get_pub_key(buffer *buf, sign_key *key, enum signkey_type *type); int buf_get_priv_key(buffer* buf, sign_key *key, enum signkey_type *type); void buf_put_pub_key(buffer* buf, sign_key *key, enum signkey_type type); diff --git a/ssh.h b/ssh.h index 5030fe9..ee4a960 100644 --- a/ssh.h +++ b/ssh.h @@ -32,6 +32,7 @@ #define SSH_MSG_DEBUG 4 #define SSH_MSG_SERVICE_REQUEST 5 #define SSH_MSG_SERVICE_ACCEPT 6 +#define SSH_MSG_EXT_INFO 7 #define SSH_MSG_KEXINIT 20 #define SSH_MSG_NEWKEYS 21 #define SSH_MSG_KEXDH_INIT 30 @@ -94,6 +95,11 @@ #define SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 #define SSH_DISCONNECT_ILLEGAL_USER_NAME 15 +/* rfc8308 */ +#define SSH_EXT_INFO_S "ext-info-s" +#define SSH_EXT_INFO_C "ext-info-c" +#define SSH_SERVER_SIG_ALGS "server-sig-algs" + /* service types */ #define SSH_SERVICE_USERAUTH "ssh-userauth" #define SSH_SERVICE_USERAUTH_LEN 12 diff --git a/svr-session.c b/svr-session.c index 0b9eac7..8bc8744 100644 --- a/svr-session.c +++ b/svr-session.c @@ -330,13 +330,16 @@ static void svr_remoteclosed() { } static void svr_algos_initialise(void) { -#if DROPBEAR_DH_GROUP1 && DROPBEAR_DH_GROUP1_CLIENTONLY algo_type *algo; for (algo = sshkex; algo->name; algo++) { +#if DROPBEAR_DH_GROUP1 && DROPBEAR_DH_GROUP1_CLIENTONLY if (strcmp(algo->name, "diffie-hellman-group1-sha1") == 0) { algo->usable = 0; } - } #endif + if (strcmp(algo->name, SSH_EXT_INFO_C) == 0) { + algo->usable = 0; + } + } } -- cgit v1.2.3 From 331d4a714f65772e384e15ff55b850a6e9e6786b Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Thu, 21 May 2020 23:00:22 +0800 Subject: Make server send SSH_MSG_EXT_INFO Ensure that only valid hostkey algorithms are sent in the first kex guess --- algo.h | 6 +++++- common-algo.c | 43 +++++++++++++++++++++++++++++++------------ common-kex.c | 27 ++++++++++++++------------- kex.h | 1 + svr-kex.c | 21 +++++++++++++++++++++ svr-runopts.c | 6 +++--- 6 files changed, 75 insertions(+), 29 deletions(-) (limited to 'common-algo.c') diff --git a/algo.h b/algo.h index 1565665..6f6da34 100644 --- a/algo.h +++ b/algo.h @@ -47,7 +47,7 @@ typedef struct Algo_Type algo_type; /* lists mapping ssh types of algorithms to internal values */ extern algo_type sshkex[]; -extern algo_type sshhostkey[]; +extern algo_type sigalgs[]; extern algo_type sshciphers[]; extern algo_type sshhashes[]; extern algo_type ssh_compress[]; @@ -112,11 +112,15 @@ struct dropbear_kex { const struct ltc_hash_descriptor *hash_desc; }; +/* Includes all algorithms is useall is set */ +void buf_put_algolist_all(buffer * buf, const algo_type localalgos[], int useall); +/* Includes "usable" algorithms */ void buf_put_algolist(buffer * buf, const algo_type localalgos[]); #define KEXGUESS2_ALGO_NAME "kexguess2@matt.ucc.asn.au" int buf_has_algo(buffer *buf, const char *algo); +algo_type * first_usable_algo(algo_type algos[]); algo_type * buf_match_algo(buffer* buf, algo_type localalgos[], int kexguess2, int *goodguess); diff --git a/common-algo.c b/common-algo.c index 938695d..54bc559 100644 --- a/common-algo.c +++ b/common-algo.c @@ -222,7 +222,7 @@ algo_type ssh_nocompress[] = { {NULL, 0, NULL, 0, NULL} }; -algo_type sshhostkey[] = { +algo_type sigalgs[] = { #if DROPBEAR_ED25519 {"ssh-ed25519", DROPBEAR_SIGNATURE_ED25519, NULL, 1, NULL}, #endif @@ -321,25 +321,34 @@ algo_type sshkex[] = { }; /* Output a comma separated list of algorithms to a buffer */ -void buf_put_algolist(buffer * buf, const algo_type localalgos[]) { - +void buf_put_algolist_all(buffer * buf, const algo_type localalgos[], int useall) { unsigned int i, len; unsigned int donefirst = 0; - buffer *algolist = NULL; + unsigned int startpos; - algolist = buf_new(300); + startpos = buf->pos; + /* Placeholder for length */ + buf_putint(buf, 0); for (i = 0; localalgos[i].name != NULL; i++) { - if (localalgos[i].usable) { - if (donefirst) - buf_putbyte(algolist, ','); + if (localalgos[i].usable || useall) { + if (donefirst) { + buf_putbyte(buf, ','); + } donefirst = 1; len = strlen(localalgos[i].name); - buf_putbytes(algolist, (const unsigned char *) localalgos[i].name, len); + buf_putbytes(buf, (const unsigned char *) localalgos[i].name, len); } } - buf_putstring(buf, (const char*)algolist->data, algolist->len); - TRACE(("algolist add '%*s'", algolist->len, algolist->data)) - buf_free(algolist); + /* Fill out the length */ + len = buf->pos - startpos - 4; + buf_setpos(buf, startpos); + buf_putint(buf, len); + TRACE(("algolist add %d '%*s'", len, len, buf_getptr(buf, len))) + buf_incrwritepos(buf, len); +} + +void buf_put_algolist(buffer * buf, const algo_type localalgos[]) { + buf_put_algolist_all(buf, localalgos, 0); } /* returns a list of pointers into algolist, of null-terminated names. @@ -408,6 +417,16 @@ int buf_has_algo(buffer *buf, const char *algo) { return ret; } +algo_type * first_usable_algo(algo_type algos[]) { + int i; + for (i = 0; algos[i].name != NULL; i++) { + if (algos[i].usable) { + return &algos[i]; + } + } + return NULL; +} + /* match the first algorithm in the comma-separated list in buf which is * also in localalgos[], or return NULL on failure. * (*goodguess) is set to 1 if the preferred client/server algos match, diff --git a/common-kex.c b/common-kex.c index 8172796..7063c81 100644 --- a/common-kex.c +++ b/common-kex.c @@ -65,7 +65,7 @@ void send_msg_kexinit() { buf_put_algolist(ses.writepayload, sshkex); /* server_host_key_algorithms */ - buf_put_algolist(ses.writepayload, sshhostkey); + buf_put_algolist(ses.writepayload, sigalgs); /* encryption_algorithms_client_to_server */ buf_put_algolist(ses.writepayload, sshciphers); @@ -110,8 +110,8 @@ void send_msg_kexinit() { ses.newkeys = (struct key_context*)m_malloc(sizeof(struct key_context)); if (ses.send_kex_first_guess) { - ses.newkeys->algo_kex = sshkex[0].data; - ses.newkeys->algo_signature = sshhostkey[0].val; + ses.newkeys->algo_kex = first_usable_algo(sshkex)->data; + ses.newkeys->algo_signature = first_usable_algo(sigalgs)->val; ses.newkeys->algo_hostkey = signkey_type_from_signature(ses.newkeys->algo_signature); ses.send_kex_first_guess(); } @@ -834,9 +834,10 @@ static void read_kex_algos() { #endif /* Determine if SSH_MSG_EXT_INFO messages should be sent. - Should be done for the first key exchange. */ - if (!ses.kexstate.donefirstkex) { - if (IS_DROPBEAR_SERVER) { + Should be done for the first key exchange. Only required on server side + for server-sig-algs */ + if (IS_DROPBEAR_SERVER) { + if (!ses.kexstate.donefirstkex) { if (buf_has_algo(ses.payload, SSH_EXT_INFO_C) == DROPBEAR_SUCCESS) { ses.allow_ext_info = 1; } @@ -855,7 +856,7 @@ static void read_kex_algos() { ses.newkeys->algo_kex = algo->data; /* server_host_key_algorithms */ - algo = buf_match_algo(ses.payload, sshhostkey, kexguess2, &goodguess); + algo = buf_match_algo(ses.payload, sigalgs, kexguess2, &goodguess); allgood &= goodguess; if (algo == NULL) { erralgo = "hostkey"; @@ -866,7 +867,7 @@ static void read_kex_algos() { ses.newkeys->algo_hostkey = signkey_type_from_signature(ses.newkeys->algo_signature); /* encryption_algorithms_client_to_server */ - c2s_cipher_algo = buf_match_algo(ses.payload, sshciphers, NULL, NULL); + c2s_cipher_algo = buf_match_algo(ses.payload, sshciphers, 0, NULL); if (c2s_cipher_algo == NULL) { erralgo = "enc c->s"; goto error; @@ -874,7 +875,7 @@ static void read_kex_algos() { TRACE(("enc c2s is %s", c2s_cipher_algo->name)) /* encryption_algorithms_server_to_client */ - s2c_cipher_algo = buf_match_algo(ses.payload, sshciphers, NULL, NULL); + s2c_cipher_algo = buf_match_algo(ses.payload, sshciphers, 0, NULL); if (s2c_cipher_algo == NULL) { erralgo = "enc s->c"; goto error; @@ -882,7 +883,7 @@ static void read_kex_algos() { TRACE(("enc s2c is %s", s2c_cipher_algo->name)) /* mac_algorithms_client_to_server */ - c2s_hash_algo = buf_match_algo(ses.payload, sshhashes, NULL, NULL); + c2s_hash_algo = buf_match_algo(ses.payload, sshhashes, 0, NULL); if (c2s_hash_algo == NULL) { erralgo = "mac c->s"; goto error; @@ -890,7 +891,7 @@ static void read_kex_algos() { TRACE(("hash c2s is %s", c2s_hash_algo->name)) /* mac_algorithms_server_to_client */ - s2c_hash_algo = buf_match_algo(ses.payload, sshhashes, NULL, NULL); + s2c_hash_algo = buf_match_algo(ses.payload, sshhashes, 0, NULL); if (s2c_hash_algo == NULL) { erralgo = "mac s->c"; goto error; @@ -898,7 +899,7 @@ static void read_kex_algos() { TRACE(("hash s2c is %s", s2c_hash_algo->name)) /* compression_algorithms_client_to_server */ - c2s_comp_algo = buf_match_algo(ses.payload, ses.compress_algos, NULL, NULL); + c2s_comp_algo = buf_match_algo(ses.payload, ses.compress_algos, 0, NULL); if (c2s_comp_algo == NULL) { erralgo = "comp c->s"; goto error; @@ -906,7 +907,7 @@ static void read_kex_algos() { TRACE(("hash c2s is %s", c2s_comp_algo->name)) /* compression_algorithms_server_to_client */ - s2c_comp_algo = buf_match_algo(ses.payload, ses.compress_algos, NULL, NULL); + s2c_comp_algo = buf_match_algo(ses.payload, ses.compress_algos, 0, NULL); if (s2c_comp_algo == NULL) { erralgo = "comp s->c"; goto error; diff --git a/kex.h b/kex.h index 3b0ec62..cdec673 100644 --- a/kex.h +++ b/kex.h @@ -61,6 +61,7 @@ int is_compress_recv(void); #endif void recv_msg_kexdh_init(void); /* server */ +void send_msg_ext_info(void); /* server */ void send_msg_kexdh_init(void); /* client */ void recv_msg_kexdh_reply(void); /* client */ diff --git a/svr-kex.c b/svr-kex.c index e3e63d0..44e10ff 100644 --- a/svr-kex.c +++ b/svr-kex.c @@ -86,6 +86,11 @@ void recv_msg_kexdh_init() { } send_msg_newkeys(); + + if (ses.allow_ext_info) { + send_msg_ext_info(); + } + ses.requirenext = SSH_MSG_NEWKEYS; TRACE(("leave recv_msg_kexdh_init")) } @@ -242,3 +247,19 @@ static void send_msg_kexdh_reply(mp_int *dh_e, buffer *ecdh_qs) { TRACE(("leave send_msg_kexdh_reply")) } +/* Only used for server-sig-algs on the server side */ +void send_msg_ext_info(void) { + TRACE(("enter send_msg_ext_info")) + + buf_putbyte(ses.writepayload, SSH_MSG_EXT_INFO); + /* nr-extensions */ + buf_putint(ses.writepayload, 1); + + buf_putstring(ses.writepayload, SSH_SERVER_SIG_ALGS, strlen(SSH_SERVER_SIG_ALGS)); + buf_put_algolist_all(ses.writepayload, sigalgs, 1); + + encrypt_packet(); + + TRACE(("leave send_msg_ext_info")) + +} diff --git a/svr-runopts.c b/svr-runopts.c index d430825..770f70a 100644 --- a/svr-runopts.c +++ b/svr-runopts.c @@ -485,9 +485,9 @@ static void addportandaddress(const char* spec) { static void disablekey(int type) { int i; TRACE(("Disabling key type %d", type)) - for (i = 0; sshhostkey[i].name != NULL; i++) { - if (sshhostkey[i].val == type) { - sshhostkey[i].usable = 0; + for (i = 0; sigalgs[i].name != NULL; i++) { + if (sigalgs[i].val == type) { + sigalgs[i].usable = 0; break; } } -- cgit v1.2.3 From 701d43b8594365b55421e8bc4c53efa920c09ed5 Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Sun, 24 May 2020 14:16:58 +0800 Subject: send and handle SSH_MSG_EXT_INFO only at the correct point - other fixes for rsa pubkey auth - only include ext-info handling when rsa pubkey auth is compiled --- cli-authpubkey.c | 4 ++-- cli-kex.c | 10 ++++++++++ common-algo.c | 2 ++ common-kex.c | 7 +++++-- dropbearkey.c | 2 +- kex.h | 4 ++-- process-packet.c | 3 +-- session.h | 12 +++++------- signkey.c | 2 +- svr-kex.c | 14 ++++++++++---- svr-runopts.c | 2 +- svr-session.c | 2 ++ sysoptions.h | 4 ++++ 13 files changed, 46 insertions(+), 22 deletions(-) (limited to 'common-algo.c') diff --git a/cli-authpubkey.c b/cli-authpubkey.c index 8426e84..49f79c3 100644 --- a/cli-authpubkey.c +++ b/cli-authpubkey.c @@ -200,7 +200,7 @@ int cli_auth_pubkey() { while (cli_opts.privkeys->first) { sign_key * key = (sign_key*)cli_opts.privkeys->first->item; if (cli_ses.server_sig_algs) { -#ifdef DROPBEAR_RSA +#if DROPBEAR_RSA if (key->type == DROPBEAR_SIGNKEY_RSA) { #if DROPBEAR_RSA_SHA256 if (buf_has_algo(cli_ses.server_sig_algs, SSH_SIGNATURE_RSA_SHA256) @@ -242,7 +242,7 @@ int cli_auth_pubkey() { assume all except rsa-sha256 are OK. */ #if DROPBEAR_RSA if (key->type == DROPBEAR_SIGNKEY_RSA) { -#ifdef DROPBEAR_RSA_SHA1 +#if DROPBEAR_RSA_SHA1 sigtype = DROPBEAR_SIGNATURE_RSA_SHA1; TRACE(("no server-sig-algs, using rsa sha1")) break; diff --git a/cli-kex.c b/cli-kex.c index 99370eb..98b0245 100644 --- a/cli-kex.c +++ b/cli-kex.c @@ -418,6 +418,15 @@ void recv_msg_ext_info(void) { unsigned int num_ext; unsigned int i; + TRACE(("enter recv_msg_ext_info")) + + /* Must be after the first SSH_MSG_NEWKEYS */ + TRACE(("last %d, donefirst %d, donescond %d", ses.lastpacket, ses.kexstate.donefirstkex, ses.kexstate.donesecondkex)) + if (!(ses.lastpacket == SSH_MSG_NEWKEYS && !ses.kexstate.donesecondkex)) { + TRACE(("leave recv_msg_ext_info: ignoring packet received at the wrong time")) + return; + } + num_ext = buf_getint(ses.payload); TRACE(("received SSH_MSG_EXT_INFO with %d items", num_ext)) @@ -435,4 +444,5 @@ void recv_msg_ext_info(void) { } m_free(ext_name); } + TRACE(("leave recv_msg_ext_info")) } diff --git a/common-algo.c b/common-algo.c index 54bc559..8596831 100644 --- a/common-algo.c +++ b/common-algo.c @@ -313,9 +313,11 @@ algo_type sshkex[] = { #if DROPBEAR_KEXGUESS2 {KEXGUESS2_ALGO_NAME, 0, NULL, 1, NULL}, #endif +#if DROPBEAR_EXT_INFO #if DROPBEAR_CLIENT /* Set unusable by svr_algos_initialise() */ {SSH_EXT_INFO_C, 0, NULL, 1, NULL}, +#endif #endif {NULL, 0, NULL, 0, NULL} }; diff --git a/common-kex.c b/common-kex.c index 7063c81..1ef1946 100644 --- a/common-kex.c +++ b/common-kex.c @@ -175,6 +175,9 @@ void send_msg_newkeys() { /* set up our state */ ses.kexstate.sentnewkeys = 1; + if (ses.kexstate.donefirstkex) { + ses.kexstate.donesecondkex = 1; + } ses.kexstate.donefirstkex = 1; ses.dataallowed = 1; /* we can send other packets again now */ gen_new_keys(); @@ -197,8 +200,6 @@ void recv_msg_newkeys() { /* Set up the kex for the first time */ void kexfirstinitialise() { - ses.kexstate.donefirstkex = 0; - #ifdef DISABLE_ZLIB ses.compress_algos = ssh_nocompress; #else @@ -833,6 +834,7 @@ static void read_kex_algos() { } #endif +#if DROPBEAR_EXT_INFO /* Determine if SSH_MSG_EXT_INFO messages should be sent. Should be done for the first key exchange. Only required on server side for server-sig-algs */ @@ -843,6 +845,7 @@ static void read_kex_algos() { } } } +#endif algo = buf_match_algo(ses.payload, sshkex, kexguess2, &goodguess); allgood &= goodguess; diff --git a/dropbearkey.c b/dropbearkey.c index f881855..8d8c7c2 100644 --- a/dropbearkey.c +++ b/dropbearkey.c @@ -133,7 +133,7 @@ static void check_signkey_bits(enum signkey_type type, int bits) } break; #endif -#ifdef DROPEAR_DSS +#if DROPEAR_DSS case DROPBEAR_SIGNKEY_DSS: if (bits != 1024) { dropbear_exit("DSS keys have a fixed size of 1024 bits\n"); diff --git a/kex.h b/kex.h index cdec673..7406f65 100644 --- a/kex.h +++ b/kex.h @@ -61,7 +61,6 @@ int is_compress_recv(void); #endif void recv_msg_kexdh_init(void); /* server */ -void send_msg_ext_info(void); /* server */ void send_msg_kexdh_init(void); /* client */ void recv_msg_kexdh_reply(void); /* client */ @@ -76,8 +75,9 @@ struct KEXState { unsigned sentnewkeys : 1; /* set once we've send MSG_NEWKEYS (will be cleared once we have also received */ unsigned recvnewkeys : 1; /* set once we've received MSG_NEWKEYS (cleared once we have also sent */ - unsigned donefirstkex : 1; /* Set to 1 after the first kex has completed, + unsigned int donefirstkex; /* Set to 1 after the first kex has completed, ie the transport layer has been set up */ + unsigned int donesecondkex; /* Set to 1 after the second kex has completed */ unsigned our_first_follows_matches : 1; diff --git a/process-packet.c b/process-packet.c index 4953cf2..9c58c7c 100644 --- a/process-packet.c +++ b/process-packet.c @@ -51,8 +51,6 @@ void process_packet() { type = buf_getbyte(ses.payload); TRACE(("process_packet: packet type = %d, len %d", type, ses.payload->len)) - ses.lastpacket = type; - now = monotonic_now(); ses.last_packet_time_keepalive_recv = now; @@ -154,6 +152,7 @@ void process_packet() { recv_unimplemented(); out: + ses.lastpacket = type; buf_free(ses.payload); ses.payload = NULL; diff --git a/session.h b/session.h index 1059537..a91401e 100644 --- a/session.h +++ b/session.h @@ -186,7 +186,7 @@ struct sshsession { /* Enables/disables compression */ algo_type *compress_algos; - /* Other side allows SSH_MSG_EXT_INFO */ + /* Other side allows SSH_MSG_EXT_INFO. Currently only set for server */ int allow_ext_info; /* a list of queued replies that should be sent after a KEX has @@ -253,13 +253,12 @@ struct serversession { #endif #if DROPBEAR_PLUGIN - /* The shared library handle */ - void *plugin_handle; + /* The shared library handle */ + void *plugin_handle; - /* The instance created by the plugin_new function */ - struct PluginInstance *plugin_instance; + /* The instance created by the plugin_new function */ + struct PluginInstance *plugin_instance; #endif - }; typedef enum { @@ -288,7 +287,6 @@ struct clientsession { cli_kex_state kex_state; /* Used for progressing KEX */ cli_state state; /* Used to progress auth/channelsession etc */ - unsigned donefirstkex : 1; /* Set when we set sentnewkeys, never reset */ int tty_raw_mode; /* Whether we're in raw mode (and have to clean up) */ struct termios saved_tio; diff --git a/signkey.c b/signkey.c index 6ed1667..49801f3 100644 --- a/signkey.c +++ b/signkey.c @@ -139,7 +139,7 @@ enum signature_type signature_type_from_name(const char* name, unsigned int name return DROPBEAR_SIGNATURE_RSA_SHA256; } #endif -#if DROPBEAR_RSA_SHA256 +#if DROPBEAR_RSA_SHA1 if (namelen == strlen(SSH_SIGNKEY_RSA) && memcmp(name, SSH_SIGNKEY_RSA, namelen) == 0) { return DROPBEAR_SIGNATURE_RSA_SHA1; diff --git a/svr-kex.c b/svr-kex.c index 44e10ff..df1008b 100644 --- a/svr-kex.c +++ b/svr-kex.c @@ -38,13 +38,15 @@ #include "gensignkey.h" static void send_msg_kexdh_reply(mp_int *dh_e, buffer *ecdh_qs); +#if DROPBEAR_EXT_INFO +static void send_msg_ext_info(void); +#endif /* Handle a diffie-hellman key exchange initialisation. This involves * calculating a session key reply value, and corresponding hash. These * are carried out by send_msg_kexdh_reply(). recv_msg_kexdh_init() calls * that function, then brings the new keys into use */ void recv_msg_kexdh_init() { - DEF_MP_INT(dh_e); buffer *ecdh_qs = NULL; @@ -87,9 +89,12 @@ void recv_msg_kexdh_init() { send_msg_newkeys(); - if (ses.allow_ext_info) { +#if DROPBEAR_EXT_INFO + /* Only send it following the first newkeys */ + if (!ses.kexstate.donesecondkex && ses.allow_ext_info) { send_msg_ext_info(); } +#endif ses.requirenext = SSH_MSG_NEWKEYS; TRACE(("leave recv_msg_kexdh_init")) @@ -247,8 +252,9 @@ static void send_msg_kexdh_reply(mp_int *dh_e, buffer *ecdh_qs) { TRACE(("leave send_msg_kexdh_reply")) } +#if DROPBEAR_EXT_INFO /* Only used for server-sig-algs on the server side */ -void send_msg_ext_info(void) { +static void send_msg_ext_info(void) { TRACE(("enter send_msg_ext_info")) buf_putbyte(ses.writepayload, SSH_MSG_EXT_INFO); @@ -261,5 +267,5 @@ void send_msg_ext_info(void) { encrypt_packet(); TRACE(("leave send_msg_ext_info")) - } +#endif diff --git a/svr-runopts.c b/svr-runopts.c index 770f70a..2c905dd 100644 --- a/svr-runopts.c +++ b/svr-runopts.c @@ -567,7 +567,7 @@ static void addhostkey(const char *keyfile) { void load_all_hostkeys() { int i; int any_keys = 0; -#ifdef DROPBEAR_ECDSA +#if DROPBEAR_ECDSA int loaded_any_ecdsa = 0; #endif diff --git a/svr-session.c b/svr-session.c index 8bc8744..6c3147f 100644 --- a/svr-session.c +++ b/svr-session.c @@ -337,9 +337,11 @@ static void svr_algos_initialise(void) { algo->usable = 0; } #endif +#if DROPBEAR_EXT_INFO if (strcmp(algo->name, SSH_EXT_INFO_C) == 0) { algo->usable = 0; } +#endif } } diff --git a/sysoptions.h b/sysoptions.h index 0f52431..d5f8da8 100644 --- a/sysoptions.h +++ b/sysoptions.h @@ -161,6 +161,10 @@ If you test it please contact the Dropbear author */ #define DROPBEAR_NORMAL_DH ((DROPBEAR_DH_GROUP1) || (DROPBEAR_DH_GROUP14) || (DROPBEAR_DH_GROUP16)) +/* Dropbear only uses server-sig-algs, only needed if we have rsa-sha256 pubkey auth */ +#define DROPBEAR_EXT_INFO ((DROPBEAR_RSA_SHA256) \ + && ((DROPBEAR_CLI_PUBKEY_AUTH) || (DROPBEAR_SVR_PUBKEY_AUTH))) + /* roughly 2x 521 bits */ #define MAX_ECC_SIZE 140 -- cgit v1.2.3 From d3d0d60076dd598f1e7aafbe564ff714cc3c19a9 Mon Sep 17 00:00:00 2001 From: Vladislav Grishenko Date: Mon, 25 May 2020 20:50:25 +0500 Subject: Add Chacha20-Poly1305, AES128-GCM and AES256-GCM support (#93) * Add Chacha20-Poly1305 authenticated encryption * Add general AEAD approach. * Add chacha20-poly1305@openssh.com algo using LibTomCrypt chacha and poly1305 routines. Chacha20-Poly1305 is generally faster than AES256 on CPU w/o dedicated AES instructions, having the same key size. Compiling in will add ~5,5kB to binary size on x86-64. function old new delta chacha_crypt - 1397 +1397 _poly1305_block - 608 +608 poly1305_done - 595 +595 dropbear_chachapoly_crypt - 457 +457 .rodata 26976 27392 +416 poly1305_process - 290 +290 poly1305_init - 221 +221 chacha_setup - 218 +218 encrypt_packet 1068 1270 +202 dropbear_chachapoly_getlength - 147 +147 decrypt_packet 756 897 +141 chacha_ivctr64 - 137 +137 read_packet 543 637 +94 dropbear_chachapoly_start - 94 +94 read_kex_algos 792 880 +88 chacha_keystream - 69 +69 dropbear_mode_chachapoly - 48 +48 sshciphers 280 320 +40 dropbear_mode_none 24 48 +24 dropbear_mode_ctr 24 48 +24 dropbear_mode_cbc 24 48 +24 dropbear_chachapoly_mac - 24 +24 dropbear_chachapoly - 24 +24 gen_new_keys 848 854 +6 ------------------------------------------------------------------------------ (add/remove: 14/0 grow/shrink: 10/0 up/down: 5388/0) Total: 5388 bytes * Add AES128-GCM and AES256-GCM authenticated encryption * Add general AES-GCM mode. * Add aes128-gcm@openssh.com and aes256-gcm@openssh.com algo using LibTomCrypt gcm routines. AES-GCM is combination of AES CTR mode and GHASH, slower than AES-CTR on CPU w/o dedicated AES/GHASH instructions therefore disabled by default. Compiling in will add ~6kB to binary size on x86-64. function old new delta gcm_process - 1060 +1060 .rodata 26976 27808 +832 gcm_gf_mult - 820 +820 gcm_add_aad - 660 +660 gcm_shift_table - 512 +512 gcm_done - 471 +471 gcm_add_iv - 384 +384 gcm_init - 347 +347 dropbear_gcm_crypt - 309 +309 encrypt_packet 1068 1270 +202 decrypt_packet 756 897 +141 gcm_reset - 118 +118 read_packet 543 637 +94 read_kex_algos 792 880 +88 sshciphers 280 360 +80 gcm_mult_h - 80 +80 dropbear_gcm_start - 62 +62 dropbear_mode_gcm - 48 +48 dropbear_mode_none 24 48 +24 dropbear_mode_ctr 24 48 +24 dropbear_mode_cbc 24 48 +24 dropbear_ghash - 24 +24 dropbear_gcm_getlength - 24 +24 gen_new_keys 848 854 +6 ------------------------------------------------------------------------------ (add/remove: 14/0 grow/shrink: 10/0 up/down: 6434/0) Total: 6434 bytes --- Makefile.in | 2 +- algo.h | 8 ++ chachapoly.c | 148 ++++++++++++++++++++++++++++ chachapoly.h | 44 +++++++++ common-algo.c | 21 +++- common-kex.c | 44 +++++++-- default_options.h | 13 +++ gcm.c | 120 ++++++++++++++++++++++ gcm.h | 47 +++++++++ libtomcrypt/src/headers/tomcrypt_dropbear.h | 8 ++ packet.c | 145 +++++++++++++++++++-------- session.h | 8 ++ sysoptions.h | 8 +- 13 files changed, 560 insertions(+), 56 deletions(-) create mode 100644 chachapoly.c create mode 100644 chachapoly.h create mode 100644 gcm.c create mode 100644 gcm.h (limited to 'common-algo.c') diff --git a/Makefile.in b/Makefile.in index aaf7b3b..6db9f04 100644 --- a/Makefile.in +++ b/Makefile.in @@ -53,7 +53,7 @@ CLIOBJS=cli-main.o cli-auth.o cli-authpasswd.o cli-kex.o \ CLISVROBJS=common-session.o packet.o common-algo.o common-kex.o \ common-channel.o common-chansession.o termcodes.o loginrec.o \ tcp-accept.o listener.o process-packet.o dh_groups.o \ - common-runopts.o circbuffer.o list.o netio.o + common-runopts.o circbuffer.o list.o netio.o chachapoly.o gcm.o KEYOBJS=dropbearkey.o diff --git a/algo.h b/algo.h index b12fb94..efd0d73 100644 --- a/algo.h +++ b/algo.h @@ -72,6 +72,14 @@ struct dropbear_cipher_mode { unsigned long len, void *cipher_state); int (*decrypt)(const unsigned char *ct, unsigned char *pt, unsigned long len, void *cipher_state); + int (*aead_crypt)(unsigned int seq, + const unsigned char *in, unsigned char *out, + unsigned long len, unsigned long taglen, + void *cipher_state, int direction); + int (*aead_getlength)(unsigned int seq, + const unsigned char *in, unsigned int *outlen, + unsigned long len, void *cipher_state); + const struct dropbear_hash *aead_mac; }; struct dropbear_hash { diff --git a/chachapoly.c b/chachapoly.c new file mode 100644 index 0000000..8fb06c5 --- /dev/null +++ b/chachapoly.c @@ -0,0 +1,148 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2020 by Vladislav Grishenko + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "algo.h" +#include "dbutil.h" +#include "chachapoly.h" + +#if DROPBEAR_CHACHA20POLY1305 + +#define CHACHA20_KEY_LEN 32 +#define CHACHA20_BLOCKSIZE 8 +#define POLY1305_KEY_LEN 32 +#define POLY1305_TAG_LEN 16 + +static const struct ltc_cipher_descriptor dummy = {.name = NULL}; + +static const struct dropbear_hash dropbear_chachapoly_mac = + {NULL, POLY1305_KEY_LEN, POLY1305_TAG_LEN}; + +const struct dropbear_cipher dropbear_chachapoly = + {&dummy, CHACHA20_KEY_LEN*2, CHACHA20_BLOCKSIZE}; + +static int dropbear_chachapoly_start(int UNUSED(cipher), const unsigned char* UNUSED(IV), + const unsigned char *key, int keylen, + int UNUSED(num_rounds), dropbear_chachapoly_state *state) { + int err; + + TRACE2(("enter dropbear_chachapoly_start")) + + if (keylen != CHACHA20_KEY_LEN*2) { + return CRYPT_ERROR; + } + + if ((err = chacha_setup(&state->chacha, key, + CHACHA20_KEY_LEN, 20)) != CRYPT_OK) { + return err; + } + + if ((err = chacha_setup(&state->header, key + CHACHA20_KEY_LEN, + CHACHA20_KEY_LEN, 20) != CRYPT_OK)) { + return err; + } + + TRACE2(("leave dropbear_chachapoly_start")) + return CRYPT_OK; +} + +static int dropbear_chachapoly_crypt(unsigned int seq, + const unsigned char *in, unsigned char *out, + unsigned long len, unsigned long taglen, + dropbear_chachapoly_state *state, int direction) { + poly1305_state poly; + unsigned char seqbuf[8], key[POLY1305_KEY_LEN], tag[POLY1305_TAG_LEN]; + int err; + + TRACE2(("enter dropbear_chachapoly_crypt")) + + if (len < 4 || taglen != POLY1305_TAG_LEN) { + return CRYPT_ERROR; + } + + STORE64H(seq, seqbuf); + chacha_ivctr64(&state->chacha, seqbuf, sizeof(seqbuf), 0); + if ((err = chacha_keystream(&state->chacha, key, sizeof(key))) != CRYPT_OK) { + return err; + } + + poly1305_init(&poly, key, sizeof(key)); + if (direction == LTC_DECRYPT) { + poly1305_process(&poly, in, len); + poly1305_done(&poly, tag, &taglen); + if (constant_time_memcmp(in + len, tag, taglen) != 0) { + return CRYPT_ERROR; + } + } + + chacha_ivctr64(&state->header, seqbuf, sizeof(seqbuf), 0); + if ((err = chacha_crypt(&state->header, in, 4, out)) != CRYPT_OK) { + return err; + } + + chacha_ivctr64(&state->chacha, seqbuf, sizeof(seqbuf), 1); + if ((err = chacha_crypt(&state->chacha, in + 4, len - 4, out + 4)) != CRYPT_OK) { + return err; + } + + if (direction == LTC_ENCRYPT) { + poly1305_process(&poly, out, len); + poly1305_done(&poly, out + len, &taglen); + } + + TRACE2(("leave dropbear_chachapoly_crypt")) + return CRYPT_OK; +} + +static int dropbear_chachapoly_getlength(unsigned int seq, + const unsigned char *in, unsigned int *outlen, + unsigned long len, dropbear_chachapoly_state *state) { + unsigned char seqbuf[8], buf[4]; + int err; + + TRACE2(("enter dropbear_chachapoly_parse")) + + if (len < sizeof(buf)) { + return CRYPT_ERROR; + } + + STORE64H(seq, seqbuf); + chacha_ivctr64(&state->header, seqbuf, sizeof(seqbuf), 0); + if ((err = chacha_crypt(&state->header, in, sizeof(buf), buf)) != CRYPT_OK) { + return err; + } + + LOAD32H(*outlen, buf); + + TRACE2(("leave dropbear_chachapoly_parse")) + return CRYPT_OK; +} + +const struct dropbear_cipher_mode dropbear_mode_chachapoly = + {(void *)dropbear_chachapoly_start, NULL, NULL, + (void *)dropbear_chachapoly_crypt, + (void *)dropbear_chachapoly_getlength, &dropbear_chachapoly_mac}; + +#endif /* DROPBEAR_CHACHA20POLY1305 */ diff --git a/chachapoly.h b/chachapoly.h new file mode 100644 index 0000000..5a7c5b2 --- /dev/null +++ b/chachapoly.h @@ -0,0 +1,44 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2020 by Vladislav Grishenko + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_DROPBEAR_CHACHAPOLY_H_ +#define DROPBEAR_DROPBEAR_CHACHAPOLY_H_ + +#include "includes.h" +#include "algo.h" + +#if DROPBEAR_CHACHA20POLY1305 + +typedef struct { + chacha_state chacha; + chacha_state header; +} dropbear_chachapoly_state; + +extern const struct dropbear_cipher dropbear_chachapoly; +extern const struct dropbear_cipher_mode dropbear_mode_chachapoly; + +#endif /* DROPBEAR_CHACHA20POLY1305 */ + +#endif /* DROPBEAR_DROPBEAR_CHACHAPOLY_H_ */ diff --git a/common-algo.c b/common-algo.c index 558aad2..eb1323f 100644 --- a/common-algo.c +++ b/common-algo.c @@ -30,6 +30,8 @@ #include "dh_groups.h" #include "ltc_prng.h" #include "ecc.h" +#include "gcm.h" +#include "chachapoly.h" /* This file (algo.c) organises the ciphers which can be used, and is used to * decide which ciphers/hashes/compression/signing to use during key exchange*/ @@ -86,11 +88,11 @@ const struct dropbear_cipher dropbear_nocipher = * about the symmetric_CBC vs symmetric_CTR cipher_state pointer */ #if DROPBEAR_ENABLE_CBC_MODE const struct dropbear_cipher_mode dropbear_mode_cbc = - {(void*)cbc_start, (void*)cbc_encrypt, (void*)cbc_decrypt}; + {(void*)cbc_start, (void*)cbc_encrypt, (void*)cbc_decrypt, NULL, NULL, NULL}; #endif /* DROPBEAR_ENABLE_CBC_MODE */ const struct dropbear_cipher_mode dropbear_mode_none = - {void_start, void_cipher, void_cipher}; + {void_start, void_cipher, void_cipher, NULL, NULL, NULL}; #if DROPBEAR_ENABLE_CTR_MODE /* a wrapper to make ctr_start and cbc_start look the same */ @@ -101,7 +103,7 @@ static int dropbear_big_endian_ctr_start(int cipher, return ctr_start(cipher, IV, key, keylen, num_rounds, CTR_COUNTER_BIG_ENDIAN, ctr); } const struct dropbear_cipher_mode dropbear_mode_ctr = - {(void*)dropbear_big_endian_ctr_start, (void*)ctr_encrypt, (void*)ctr_decrypt}; + {(void*)dropbear_big_endian_ctr_start, (void*)ctr_encrypt, (void*)ctr_decrypt, NULL, NULL, NULL}; #endif /* DROPBEAR_ENABLE_CTR_MODE */ /* Mapping of ssh hashes to libtomcrypt hashes, including keysize etc. @@ -137,6 +139,19 @@ const struct dropbear_hash dropbear_nohash = * that is also supported by the server will get used. */ algo_type sshciphers[] = { +#if DROPBEAR_CHACHA20POLY1305 + {"chacha20-poly1305@openssh.com", 0, &dropbear_chachapoly, 1, &dropbear_mode_chachapoly}, +#endif + +#if DROPBEAR_ENABLE_GCM_MODE +#if DROPBEAR_AES128 + {"aes128-gcm@openssh.com", 0, &dropbear_aes128, 1, &dropbear_mode_gcm}, +#endif +#if DROPBEAR_AES256 + {"aes256-gcm@openssh.com", 0, &dropbear_aes256, 1, &dropbear_mode_gcm}, +#endif +#endif /* DROPBEAR_ENABLE_GCM_MODE */ + #if DROPBEAR_ENABLE_CTR_MODE #if DROPBEAR_AES128 {"aes128-ctr", 0, &dropbear_aes128, 1, &dropbear_mode_ctr}, diff --git a/common-kex.c b/common-kex.c index 16b7e27..5e2923f 100644 --- a/common-kex.c +++ b/common-kex.c @@ -329,9 +329,12 @@ static void gen_new_keys() { hashkeys(S2C_key, sizeof(S2C_key), &hs, 'D'); if (ses.newkeys->recv.algo_crypt->cipherdesc != NULL) { - int recv_cipher = find_cipher(ses.newkeys->recv.algo_crypt->cipherdesc->name); - if (recv_cipher < 0) - dropbear_exit("Crypto error"); + int recv_cipher = -1; + if (ses.newkeys->recv.algo_crypt->cipherdesc->name != NULL) { + recv_cipher = find_cipher(ses.newkeys->recv.algo_crypt->cipherdesc->name); + if (recv_cipher < 0) + dropbear_exit("Crypto error"); + } if (ses.newkeys->recv.crypt_mode->start(recv_cipher, recv_IV, recv_key, ses.newkeys->recv.algo_crypt->keysize, 0, @@ -341,9 +344,12 @@ static void gen_new_keys() { } if (ses.newkeys->trans.algo_crypt->cipherdesc != NULL) { - int trans_cipher = find_cipher(ses.newkeys->trans.algo_crypt->cipherdesc->name); - if (trans_cipher < 0) - dropbear_exit("Crypto error"); + int trans_cipher = -1; + if (ses.newkeys->trans.algo_crypt->cipherdesc->name != NULL) { + trans_cipher = find_cipher(ses.newkeys->trans.algo_crypt->cipherdesc->name); + if (trans_cipher < 0) + dropbear_exit("Crypto error"); + } if (ses.newkeys->trans.crypt_mode->start(trans_cipher, trans_IV, trans_key, ses.newkeys->trans.algo_crypt->keysize, 0, @@ -868,19 +874,29 @@ static void read_kex_algos() { /* mac_algorithms_client_to_server */ c2s_hash_algo = buf_match_algo(ses.payload, sshhashes, NULL, NULL); +#if DROPBEAR_AEAD_MODE + if (((struct dropbear_cipher_mode*)c2s_cipher_algo->mode)->aead_crypt != NULL) { + c2s_hash_algo = NULL; + } else +#endif if (c2s_hash_algo == NULL) { erralgo = "mac c->s"; goto error; } - TRACE(("hash c2s is %s", c2s_hash_algo->name)) + TRACE(("hash c2s is %s", c2s_hash_algo ? c2s_hash_algo->name : "")) /* mac_algorithms_server_to_client */ s2c_hash_algo = buf_match_algo(ses.payload, sshhashes, NULL, NULL); +#if DROPBEAR_AEAD_MODE + if (((struct dropbear_cipher_mode*)s2c_cipher_algo->mode)->aead_crypt != NULL) { + s2c_hash_algo = NULL; + } else +#endif if (s2c_hash_algo == NULL) { erralgo = "mac s->c"; goto error; } - TRACE(("hash s2c is %s", s2c_hash_algo->name)) + TRACE(("hash s2c is %s", s2c_hash_algo ? s2c_hash_algo->name : "")) /* compression_algorithms_client_to_server */ c2s_comp_algo = buf_match_algo(ses.payload, ses.compress_algos, NULL, NULL); @@ -925,8 +941,14 @@ static void read_kex_algos() { ses.newkeys->trans.crypt_mode = (struct dropbear_cipher_mode*)c2s_cipher_algo->mode; ses.newkeys->recv.algo_mac = +#if DROPBEAR_AEAD_MODE + s2c_hash_algo == NULL ? ses.newkeys->recv.crypt_mode->aead_mac : +#endif (struct dropbear_hash*)s2c_hash_algo->data; ses.newkeys->trans.algo_mac = +#if DROPBEAR_AEAD_MODE + c2s_hash_algo == NULL ? ses.newkeys->trans.crypt_mode->aead_mac : +#endif (struct dropbear_hash*)c2s_hash_algo->data; ses.newkeys->recv.algo_comp = s2c_comp_algo->val; ses.newkeys->trans.algo_comp = c2s_comp_algo->val; @@ -941,8 +963,14 @@ static void read_kex_algos() { ses.newkeys->trans.crypt_mode = (struct dropbear_cipher_mode*)s2c_cipher_algo->mode; ses.newkeys->recv.algo_mac = +#if DROPBEAR_AEAD_MODE + c2s_hash_algo == NULL ? ses.newkeys->recv.crypt_mode->aead_mac : +#endif (struct dropbear_hash*)c2s_hash_algo->data; ses.newkeys->trans.algo_mac = +#if DROPBEAR_AEAD_MODE + s2c_hash_algo == NULL ? ses.newkeys->trans.crypt_mode->aead_mac : +#endif (struct dropbear_hash*)s2c_hash_algo->data; ses.newkeys->recv.algo_comp = c2s_comp_algo->val; ses.newkeys->trans.algo_comp = s2c_comp_algo->val; diff --git a/default_options.h b/default_options.h index bafbb07..2ca01b3 100644 --- a/default_options.h +++ b/default_options.h @@ -99,6 +99,19 @@ IMPORTANT: Some options will require "make clean" after changes */ * and forwards compatibility */ #define DROPBEAR_ENABLE_CTR_MODE 1 +/* Enable "Galois/Counter Mode" for ciphers. This authenticated + * encryption mode is combination of CTR mode and GHASH. Recommended + * for security and forwards compatibility, but slower than CTR on + * CPU w/o dedicated AES/GHASH instructions. + * Compiling in will add ~6kB to binary size on x86-64 */ +#define DROPBEAR_ENABLE_GCM_MODE 0 + +/* Enable Chacha20-Poly1305 authenticated encryption mode. This is + * generally faster than AES256 on CPU w/o dedicated AES instructions, + * having the same key size. + * Compiling in will add ~5,5kB to binary size on x86-64 */ +#define DROPBEAR_CHACHA20POLY1305 1 + /* Message integrity. sha2-256 is recommended as a default, sha1 for compatibility */ #define DROPBEAR_SHA1_HMAC 1 diff --git a/gcm.c b/gcm.c new file mode 100644 index 0000000..8f5f3d9 --- /dev/null +++ b/gcm.c @@ -0,0 +1,120 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2020 by Vladislav Grishenko + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "algo.h" +#include "dbutil.h" +#include "gcm.h" + +#if DROPBEAR_ENABLE_GCM_MODE + +#define GHASH_LEN 16 + +static const struct dropbear_hash dropbear_ghash = + {NULL, 0, GHASH_LEN}; + +static int dropbear_gcm_start(int cipher, const unsigned char *IV, + const unsigned char *key, int keylen, + int UNUSED(num_rounds), dropbear_gcm_state *state) { + int err; + + TRACE2(("enter dropbear_gcm_start")) + + if ((err = gcm_init(&state->gcm, cipher, key, keylen)) != CRYPT_OK) { + return err; + } + memcpy(state->iv, IV, GCM_NONCE_LEN); + + TRACE2(("leave dropbear_gcm_start")) + return CRYPT_OK; +} + +static int dropbear_gcm_crypt(unsigned int UNUSED(seq), + const unsigned char *in, unsigned char *out, + unsigned long len, unsigned long taglen, + dropbear_gcm_state *state, int direction) { + unsigned char *iv, tag[GHASH_LEN]; + int i, err; + + TRACE2(("enter dropbear_gcm_crypt")) + + if (len < 4 || taglen != GHASH_LEN) { + return CRYPT_ERROR; + } + + gcm_reset(&state->gcm); + + if ((err = gcm_add_iv(&state->gcm, + state->iv, GCM_NONCE_LEN)) != CRYPT_OK) { + return err; + } + + if ((err = gcm_add_aad(&state->gcm, in, 4)) != CRYPT_OK) { + return err; + } + + if ((err = gcm_process(&state->gcm, (unsigned char *) in + 4, + len - 4, out + 4, direction)) != CRYPT_OK) { + return err; + } + + if (direction == LTC_ENCRYPT) { + gcm_done(&state->gcm, out + len, &taglen); + } else { + gcm_done(&state->gcm, tag, &taglen); + if (constant_time_memcmp(in + len, tag, taglen) != 0) { + return CRYPT_ERROR; + } + } + + /* increment invocation counter */ + iv = state->iv + GCM_IVFIX_LEN; + for (i = GCM_IVCTR_LEN - 1; i >= 0 && ++iv[i] == 0; i--); + + TRACE2(("leave dropbear_gcm_crypt")) + return CRYPT_OK; +} + +static int dropbear_gcm_getlength(unsigned int UNUSED(seq), + const unsigned char *in, unsigned int *outlen, + unsigned long len, dropbear_gcm_state* UNUSED(state)) { + TRACE2(("enter dropbear_gcm_parse")) + + if (len < 4) { + return CRYPT_ERROR; + } + + LOAD32H(*outlen, in); + + TRACE2(("leave dropbear_gcm_parse")) + return CRYPT_OK; +} + +const struct dropbear_cipher_mode dropbear_mode_gcm = + {(void *)dropbear_gcm_start, NULL, NULL, + (void *)dropbear_gcm_crypt, + (void *)dropbear_gcm_getlength, &dropbear_ghash}; + +#endif /* DROPBEAR_ENABLE_GCM_MODE */ diff --git a/gcm.h b/gcm.h new file mode 100644 index 0000000..58c530a --- /dev/null +++ b/gcm.h @@ -0,0 +1,47 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2002,2003 Matt Johnston + * Copyright (c) 2020 by Vladislav Grishenko + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#ifndef DROPBEAR_DROPBEAR_GCM_H_ +#define DROPBEAR_DROPBEAR_GCM_H_ + +#include "includes.h" +#include "algo.h" + +#if DROPBEAR_ENABLE_GCM_MODE + +#define GCM_IVFIX_LEN 4 +#define GCM_IVCTR_LEN 8 +#define GCM_NONCE_LEN (GCM_IVFIX_LEN + GCM_IVCTR_LEN) + +typedef struct { + gcm_state gcm; + unsigned char iv[GCM_NONCE_LEN]; +} dropbear_gcm_state; + +extern const struct dropbear_cipher_mode dropbear_mode_gcm; + +#endif /* DROPBEAR_ENABLE_GCM_MODE */ + +#endif /* DROPBEAR_DROPBEAR_GCM_H_ */ diff --git a/libtomcrypt/src/headers/tomcrypt_dropbear.h b/libtomcrypt/src/headers/tomcrypt_dropbear.h index b0ce45b..ae29475 100644 --- a/libtomcrypt/src/headers/tomcrypt_dropbear.h +++ b/libtomcrypt/src/headers/tomcrypt_dropbear.h @@ -35,6 +35,14 @@ #define LTC_CTR_MODE #endif +#if DROPBEAR_ENABLE_GCM_MODE +#define LTC_GCM_MODE +#endif + +#if DROPBEAR_CHACHA20POLY1305 +#define LTC_CHACHA +#define LTC_POLY1305 +#endif #if DROPBEAR_SHA512 #define LTC_SHA512 diff --git a/packet.c b/packet.c index 9fda0d6..0454726 100644 --- a/packet.c +++ b/packet.c @@ -215,7 +215,7 @@ static int read_packet_init() { unsigned int maxlen; int slen; - unsigned int len; + unsigned int len, plen; unsigned int blocksize; unsigned int macsize; @@ -254,21 +254,35 @@ static int read_packet_init() { /* now we have the first block, need to get packet length, so we decrypt * the first block (only need first 4 bytes) */ buf_setpos(ses.readbuf, 0); - if (ses.keys->recv.crypt_mode->decrypt(buf_getptr(ses.readbuf, blocksize), - buf_getwriteptr(ses.readbuf, blocksize), - blocksize, - &ses.keys->recv.cipher_state) != CRYPT_OK) { - dropbear_exit("Error decrypting"); +#if DROPBEAR_AEAD_MODE + if (ses.keys->recv.crypt_mode->aead_crypt) { + if (ses.keys->recv.crypt_mode->aead_getlength(ses.recvseq, + buf_getptr(ses.readbuf, blocksize), &plen, + blocksize, + &ses.keys->recv.cipher_state) != CRYPT_OK) { + dropbear_exit("Error decrypting"); + } + len = plen + 4 + macsize; + } else +#endif + { + if (ses.keys->recv.crypt_mode->decrypt(buf_getptr(ses.readbuf, blocksize), + buf_getwriteptr(ses.readbuf, blocksize), + blocksize, + &ses.keys->recv.cipher_state) != CRYPT_OK) { + dropbear_exit("Error decrypting"); + } + plen = buf_getint(ses.readbuf) + 4; + len = plen + macsize; } - len = buf_getint(ses.readbuf) + 4 + macsize; TRACE2(("packet size is %u, block %u mac %u", len, blocksize, macsize)) /* check packet length */ if ((len > RECV_MAX_PACKET_LEN) || - (len < MIN_PACKET_LEN + macsize) || - ((len - macsize) % blocksize != 0)) { + (plen < blocksize) || + (plen % blocksize != 0)) { dropbear_exit("Integrity error (bad packet size %u)", len); } @@ -294,23 +308,42 @@ void decrypt_packet() { ses.kexstate.datarecv += ses.readbuf->len; - /* we've already decrypted the first blocksize in read_packet_init */ - buf_setpos(ses.readbuf, blocksize); - - /* decrypt it in-place */ - len = ses.readbuf->len - macsize - ses.readbuf->pos; - if (ses.keys->recv.crypt_mode->decrypt( - buf_getptr(ses.readbuf, len), - buf_getwriteptr(ses.readbuf, len), - len, - &ses.keys->recv.cipher_state) != CRYPT_OK) { - dropbear_exit("Error decrypting"); - } - buf_incrpos(ses.readbuf, len); +#if DROPBEAR_AEAD_MODE + if (ses.keys->recv.crypt_mode->aead_crypt) { + /* first blocksize is not decrypted yet */ + buf_setpos(ses.readbuf, 0); + + /* decrypt it in-place */ + len = ses.readbuf->len - macsize - ses.readbuf->pos; + if (ses.keys->recv.crypt_mode->aead_crypt(ses.recvseq, + buf_getptr(ses.readbuf, len + macsize), + buf_getwriteptr(ses.readbuf, len), + len, macsize, + &ses.keys->recv.cipher_state, LTC_DECRYPT) != CRYPT_OK) { + dropbear_exit("Error decrypting"); + } + buf_incrpos(ses.readbuf, len); + } else +#endif + { + /* we've already decrypted the first blocksize in read_packet_init */ + buf_setpos(ses.readbuf, blocksize); + + /* decrypt it in-place */ + len = ses.readbuf->len - macsize - ses.readbuf->pos; + if (ses.keys->recv.crypt_mode->decrypt( + buf_getptr(ses.readbuf, len), + buf_getwriteptr(ses.readbuf, len), + len, + &ses.keys->recv.cipher_state) != CRYPT_OK) { + dropbear_exit("Error decrypting"); + } + buf_incrpos(ses.readbuf, len); - /* check the hmac */ - if (checkmac() != DROPBEAR_SUCCESS) { - dropbear_exit("Integrity error"); + /* check the hmac */ + if (checkmac() != DROPBEAR_SUCCESS) { + dropbear_exit("Integrity error"); + } } /* get padding length */ @@ -557,9 +590,16 @@ void encrypt_packet() { buf_setpos(ses.writepayload, 0); buf_setlen(ses.writepayload, 0); - /* length of padding - packet length must be a multiple of blocksize, - * with a minimum of 4 bytes of padding */ - padlen = blocksize - (writebuf->len) % blocksize; + /* length of padding - packet length excluding the packetlength uint32 + * field in aead mode must be a multiple of blocksize, with a minimum of + * 4 bytes of padding */ + len = writebuf->len; +#if DROPBEAR_AEAD_MODE + if (ses.keys->trans.crypt_mode->aead_crypt) { + len -= 4; + } +#endif + padlen = blocksize - len % blocksize; if (padlen < 4) { padlen += blocksize; } @@ -579,23 +619,42 @@ void encrypt_packet() { buf_incrlen(writebuf, padlen); genrandom(buf_getptr(writebuf, padlen), padlen); - make_mac(ses.transseq, &ses.keys->trans, writebuf, writebuf->len, mac_bytes); +#if DROPBEAR_AEAD_MODE + if (ses.keys->trans.crypt_mode->aead_crypt) { + /* do the actual encryption, in-place */ + buf_setpos(writebuf, 0); + /* encrypt it in-place*/ + len = writebuf->len; + buf_incrlen(writebuf, mac_size); + if (ses.keys->trans.crypt_mode->aead_crypt(ses.transseq, + buf_getptr(writebuf, len), + buf_getwriteptr(writebuf, len + mac_size), + len, mac_size, + &ses.keys->trans.cipher_state, LTC_ENCRYPT) != CRYPT_OK) { + dropbear_exit("Error encrypting"); + } + buf_incrpos(writebuf, len + mac_size); + } else +#endif + { + make_mac(ses.transseq, &ses.keys->trans, writebuf, writebuf->len, mac_bytes); + + /* do the actual encryption, in-place */ + buf_setpos(writebuf, 0); + /* encrypt it in-place*/ + len = writebuf->len; + if (ses.keys->trans.crypt_mode->encrypt( + buf_getptr(writebuf, len), + buf_getwriteptr(writebuf, len), + len, + &ses.keys->trans.cipher_state) != CRYPT_OK) { + dropbear_exit("Error encrypting"); + } + buf_incrpos(writebuf, len); - /* do the actual encryption, in-place */ - buf_setpos(writebuf, 0); - /* encrypt it in-place*/ - len = writebuf->len; - if (ses.keys->trans.crypt_mode->encrypt( - buf_getptr(writebuf, len), - buf_getwriteptr(writebuf, len), - len, - &ses.keys->trans.cipher_state) != CRYPT_OK) { - dropbear_exit("Error encrypting"); + /* stick the MAC on it */ + buf_putbytes(writebuf, mac_bytes, mac_size); } - buf_incrpos(writebuf, len); - - /* stick the MAC on it */ - buf_putbytes(writebuf, mac_bytes, mac_size); /* Update counts */ ses.kexstate.datatrans += writebuf->len; diff --git a/session.h b/session.h index e436882..01416c9 100644 --- a/session.h +++ b/session.h @@ -41,6 +41,8 @@ #if DROPBEAR_PLUGIN #include "pubkeyapi.h" #endif +#include "gcm.h" +#include "chachapoly.h" void common_session_init(int sock_in, int sock_out); void session_loop(void(*loophandler)(void)) ATTRIB_NORETURN; @@ -80,6 +82,12 @@ struct key_context_directional { symmetric_CBC cbc; #if DROPBEAR_ENABLE_CTR_MODE symmetric_CTR ctr; +#endif +#if DROPBEAR_ENABLE_GCM_MODE + dropbear_gcm_state gcm; +#endif +#if DROPBEAR_CHACHA20POLY1305 + dropbear_chachapoly_state chachapoly; #endif } cipher_state; unsigned char mackey[MAX_MAC_LEN]; diff --git a/sysoptions.h b/sysoptions.h index 2c27caf..2cfdef2 100644 --- a/sysoptions.h +++ b/sysoptions.h @@ -92,7 +92,11 @@ #define MD5_HASH_SIZE 16 #define MAX_HASH_SIZE 64 /* sha512 */ +#if DROPBEAR_CHACHA20POLY1305 +#define MAX_KEY_LEN 64 /* 2 x 256 bits for chacha20 */ +#else #define MAX_KEY_LEN 32 /* 256 bits for aes256 etc */ +#endif #define MAX_IV_LEN 20 /* must be same as max blocksize, */ #if DROPBEAR_SHA2_512_HMAC @@ -207,6 +211,8 @@ If you test it please contact the Dropbear author */ #define DROPBEAR_TWOFISH ((DROPBEAR_TWOFISH256) || (DROPBEAR_TWOFISH128)) +#define DROPBEAR_AEAD_MODE ((DROPBEAR_CHACHA20POLY1305) || (DROPBEAR_ENABLE_GCM_MODE)) + #define DROPBEAR_CLI_ANYTCPFWD ((DROPBEAR_CLI_REMOTETCPFWD) || (DROPBEAR_CLI_LOCALTCPFWD)) #define DROPBEAR_TCP_ACCEPT ((DROPBEAR_CLI_LOCALTCPFWD) || (DROPBEAR_SVR_REMOTETCPFWD)) @@ -249,7 +255,7 @@ If you test it please contact the Dropbear author */ #endif #if !(DROPBEAR_AES128 || DROPBEAR_3DES || DROPBEAR_AES256 || DROPBEAR_BLOWFISH \ - || DROPBEAR_TWOFISH256 || DROPBEAR_TWOFISH128) + || DROPBEAR_TWOFISH256 || DROPBEAR_TWOFISH128 || DROPBEAR_CHACHA20POLY1305) #error "At least one encryption algorithm must be enabled. AES128 is recommended." #endif -- cgit v1.2.3 From 61267f85035c0c77059992f50ef20f62e192ba04 Mon Sep 17 00:00:00 2001 From: Vladislav Grishenko Date: Mon, 25 May 2020 20:55:13 +0500 Subject: CBC mode cleanup (#95) * Fix CBC mode can't be fully disabled * Fix CBC mode can't be the only mode --- common-algo.c | 8 +++++++- libtomcrypt/src/headers/tomcrypt_dropbear.h | 2 +- session.h | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) (limited to 'common-algo.c') diff --git a/common-algo.c b/common-algo.c index eb1323f..21ccbe3 100644 --- a/common-algo.c +++ b/common-algo.c @@ -184,12 +184,18 @@ algo_type sshciphers[] = { #if DROPBEAR_TWOFISH128 {"twofish128-cbc", 0, &dropbear_twofish128, 1, &dropbear_mode_cbc}, #endif +#endif /* DROPBEAR_ENABLE_CBC_MODE */ + #if DROPBEAR_3DES +#if DROPBEAR_ENABLE_CTR_MODE {"3des-ctr", 0, &dropbear_3des, 1, &dropbear_mode_ctr}, #endif -#if DROPBEAR_3DES +#if DROPBEAR_ENABLE_CBC_MODE {"3des-cbc", 0, &dropbear_3des, 1, &dropbear_mode_cbc}, #endif +#endif /* DROPBEAR_3DES */ + +#if DROPBEAR_ENABLE_CBC_MODE #if DROPBEAR_BLOWFISH {"blowfish-cbc", 0, &dropbear_blowfish, 1, &dropbear_mode_cbc}, #endif diff --git a/libtomcrypt/src/headers/tomcrypt_dropbear.h b/libtomcrypt/src/headers/tomcrypt_dropbear.h index ae29475..323902b 100644 --- a/libtomcrypt/src/headers/tomcrypt_dropbear.h +++ b/libtomcrypt/src/headers/tomcrypt_dropbear.h @@ -27,7 +27,7 @@ #define LTC_DES #endif -#if DROPBEAR_ENABLE_CTR_MODE +#if DROPBEAR_ENABLE_CBC_MODE #define LTC_CBC_MODE #endif diff --git a/session.h b/session.h index 01416c9..0cf4280 100644 --- a/session.h +++ b/session.h @@ -79,7 +79,9 @@ struct key_context_directional { #endif /* actual keys */ union { +#if DROPBEAR_ENABLE_CBC_MODE symmetric_CBC cbc; +#endif #if DROPBEAR_ENABLE_CTR_MODE symmetric_CTR ctr; #endif -- cgit v1.2.3 From 6145289e0d6de9bad520c64316672c8f583cdb1e Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Wed, 10 Jun 2020 23:42:42 +0800 Subject: Remove blowfish --- common-algo.c | 7 ------- default_options.h | 2 -- libtomcrypt/src/headers/tomcrypt_dropbear.h | 3 --- 3 files changed, 12 deletions(-) (limited to 'common-algo.c') diff --git a/common-algo.c b/common-algo.c index a710171..8b39892 100644 --- a/common-algo.c +++ b/common-algo.c @@ -64,10 +64,6 @@ static const struct dropbear_cipher dropbear_aes256 = static const struct dropbear_cipher dropbear_aes128 = {&aes_desc, 16, 16}; #endif -#if DROPBEAR_BLOWFISH -static const struct dropbear_cipher dropbear_blowfish = - {&blowfish_desc, 16, 8}; -#endif #if DROPBEAR_TWOFISH256 static const struct dropbear_cipher dropbear_twofish256 = {&twofish_desc, 32, 16}; @@ -197,9 +193,6 @@ algo_type sshciphers[] = { #endif /* DROPBEAR_3DES */ #if DROPBEAR_ENABLE_CBC_MODE -#if DROPBEAR_BLOWFISH - {"blowfish-cbc", 0, &dropbear_blowfish, 1, &dropbear_mode_cbc}, -#endif #endif /* DROPBEAR_ENABLE_CBC_MODE */ {NULL, 0, NULL, 0, NULL} }; diff --git a/default_options.h b/default_options.h index 2ca01b3..db2ea13 100644 --- a/default_options.h +++ b/default_options.h @@ -87,8 +87,6 @@ IMPORTANT: Some options will require "make clean" after changes */ #define DROPBEAR_AES256 1 #define DROPBEAR_TWOFISH256 0 #define DROPBEAR_TWOFISH128 0 -/* Compiling in Blowfish will add ~6kB to runtime heap memory usage */ -#define DROPBEAR_BLOWFISH 0 /* Enable CBC mode for ciphers. This has security issues though * is the most compatible with older SSH implementations */ diff --git a/libtomcrypt/src/headers/tomcrypt_dropbear.h b/libtomcrypt/src/headers/tomcrypt_dropbear.h index 36956da..3e24ea2 100644 --- a/libtomcrypt/src/headers/tomcrypt_dropbear.h +++ b/libtomcrypt/src/headers/tomcrypt_dropbear.h @@ -13,9 +13,6 @@ /* Fewer entries needed */ #define TAB_SIZE 5 -#if DROPBEAR_BLOWFISH -#define LTC_BLOWFISH -#endif #if DROPBEAR_AES #define LTC_RIJNDAEL #endif -- cgit v1.2.3