From c8fcc08fe08ef5a5a7e81ec15bfbd0301367f8d6 Mon Sep 17 00:00:00 2001 From: Egor Duda Date: Thu, 23 Dec 2021 08:44:31 +0300 Subject: [PATCH] Implement server-side support for sk-ecdsa U2F-backed keys --- Makefile.in | 2 +- common-algo.c | 3 + default_options.h | 1 + ecdsa.c | 8 +- ecdsa.h | 2 + signkey.c | 44 ++++++++++- signkey.h | 3 + sk-ecdsa.c | 181 ++++++++++++++++++++++++++++++++++++++++++++++ sk-ecdsa.h | 15 ++++ svr-authpubkey.c | 30 +++++++- sysoptions.h | 1 + 11 files changed, 283 insertions(+), 7 deletions(-) create mode 100644 sk-ecdsa.c create mode 100644 sk-ecdsa.h diff --git a/Makefile.in b/Makefile.in index 8239d25..cab73d8 100644 --- a/Makefile.in +++ b/Makefile.in @@ -35,7 +35,7 @@ COMMONOBJS=dbutil.o buffer.o dbhelpers.o \ signkey.o rsa.o dbrandom.o \ queue.o \ atomicio.o compat.o fake-rfc2553.o \ - ltc_prng.o ecc.o ecdsa.o crypto_desc.o \ + ltc_prng.o ecc.o ecdsa.o sk-ecdsa.o crypto_desc.o \ curve25519.o ed25519.o \ dbmalloc.o \ gensignkey.o gendss.o genrsa.o gened25519.o diff --git a/common-algo.c b/common-algo.c index f3961c2..511ae01 100644 --- a/common-algo.c +++ b/common-algo.c @@ -250,6 +250,9 @@ algo_type sigalgs[] = { #if DROPBEAR_ECC_521 {"ecdsa-sha2-nistp521", DROPBEAR_SIGNATURE_ECDSA_NISTP521, NULL, 1, NULL}, #endif +#if DROPBEAR_SK_ECDSA + {"sk-ecdsa-sha2-nistp256@openssh.com", DROPBEAR_SIGNATURE_SK_ECDSA_NISTP256, NULL, 1, NULL}, +#endif #endif #if DROPBEAR_RSA #if DROPBEAR_RSA_SHA256 diff --git a/default_options.h b/default_options.h index 5cbffce..212e659 100644 --- a/default_options.h +++ b/default_options.h @@ -126,6 +126,7 @@ IMPORTANT: Some options will require "make clean" after changes */ * code (either ECDSA or ECDH) increases binary size - around 30kB * on x86-64 */ #define DROPBEAR_ECDSA 1 +#define DROPBEAR_SK_ECDSA 1 /* Ed25519 is faster than ECDSA. Compiling in Ed25519 code increases binary size - around 7,5kB on x86-64 */ #define DROPBEAR_ED25519 1 diff --git a/ecdsa.c b/ecdsa.c index 56e5355..38dc942 100644 --- a/ecdsa.c +++ b/ecdsa.c @@ -86,11 +86,13 @@ ecc_key *buf_get_ecdsa_pub_key(buffer* buf) { /* string "[identifier]" */ identifier = (unsigned char*)buf_getstring(buf, &identifier_len); - if (key_ident_len != identifier_len + strlen("ecdsa-sha2-")) { + if (key_ident_len != identifier_len + strlen ("sk-") + strlen ("@openssh.com") + strlen("ecdsa-sha2-") && + key_ident_len != identifier_len + strlen("ecdsa-sha2-")) { TRACE(("Bad identifier lengths")) goto out; } - if (memcmp(&key_ident[strlen("ecdsa-sha2-")], identifier, identifier_len) != 0) { + if (memcmp(&key_ident[strlen("sk-ecdsa-sha2-")], identifier, identifier_len) != 0 && + memcmp(&key_ident[strlen("ecdsa-sha2-")], identifier, identifier_len) != 0) { TRACE(("mismatching identifiers")) goto out; } @@ -247,7 +249,7 @@ out: /* returns values in s and r returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */ -static int buf_get_ecdsa_verify_params(buffer *buf, +int buf_get_ecdsa_verify_params(buffer *buf, void *r, void* s) { int ret = DROPBEAR_FAILURE; unsigned int sig_len; diff --git a/ecdsa.h b/ecdsa.h index 01cb134..fa082ad 100644 --- a/ecdsa.h +++ b/ecdsa.h @@ -26,6 +26,8 @@ void buf_put_ecdsa_pub_key(buffer *buf, ecc_key *key); void buf_put_ecdsa_priv_key(buffer *buf, ecc_key *key); enum signkey_type ecdsa_signkey_type(const ecc_key * key); +int buf_get_ecdsa_verify_params(buffer *buf, void *r, void* s); + void buf_put_ecdsa_sign(buffer *buf, const ecc_key *key, const buffer *data_buf); int buf_ecdsa_verify(buffer *buf, const ecc_key *key, const buffer *data_buf); /* Returns 1 on success */ diff --git a/signkey.c b/signkey.c index 96446c8..2035635 100644 --- a/signkey.c +++ b/signkey.c @@ -28,6 +28,7 @@ #include "buffer.h" #include "ssh.h" #include "ecdsa.h" +#include "sk-ecdsa.h" #include "rsa.h" #include "dss.h" #include "ed25519.h" @@ -43,6 +44,7 @@ static const char * const signkey_names[DROPBEAR_SIGNKEY_NUM_NAMED] = { "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", + "sk-ecdsa-sha2-nistp256@openssh.com", #endif /* DROPBEAR_ECDSA */ #if DROPBEAR_ED25519 "ssh-ed25519", @@ -185,6 +187,7 @@ signkey_key_ptr(sign_key *key, enum signkey_type type) { #if DROPBEAR_ECDSA #if DROPBEAR_ECC_256 case DROPBEAR_SIGNKEY_ECDSA_NISTP256: + case DROPBEAR_SIGNKEY_SK_ECDSA_NISTP256: return (void**)&key->ecckey256; #endif #if DROPBEAR_ECC_384 @@ -260,7 +263,11 @@ int buf_get_pub_key(buffer *buf, sign_key *key, enum signkey_type *type) { } #endif #if DROPBEAR_ECDSA - if (signkey_is_ecdsa(keytype)) { + if (signkey_is_ecdsa(keytype) +#if DROPBEAR_SK_ECDSA + || signkey_is_sk_ecdsa(keytype) +#endif + ) { ecc_key **eck = (ecc_key**)signkey_key_ptr(key, keytype); if (eck) { if (*eck) { @@ -639,6 +646,41 @@ void buf_put_sign(buffer* buf, sign_key *key, enum signature_type sigtype, } #if DROPBEAR_SIGNKEY_VERIFY + +#if DROPBEAR_SK_ECDSA + +int sk_buf_verify(buffer * buf, sign_key *key, enum signature_type expect_sigtype, const buffer *data_buf, char* app, unsigned int applen) { + + char *type_name = NULL; + unsigned int type_name_len = 0; + enum signature_type sigtype; + enum signkey_type keytype; + + TRACE(("enter sk_buf_verify")) + + buf_getint(buf); /* blob length */ + type_name = buf_getstring(buf, &type_name_len); + sigtype = signature_type_from_name(type_name, type_name_len); + m_free(type_name); + + if (expect_sigtype != sigtype) { + dropbear_exit("Non-matching signing type"); + } + + keytype = signkey_type_from_signature(sigtype); + + if (signkey_is_sk_ecdsa(keytype)) { + ecc_key **eck = (ecc_key**)signkey_key_ptr(key, keytype); + if (eck && *eck) { + return buf_sk_ecdsa_verify(buf, *eck, data_buf, app, applen); + } + } + dropbear_exit("Non-matching signing type"); + return DROPBEAR_FAILURE; +} + +#endif + /* Return DROPBEAR_SUCCESS or DROPBEAR_FAILURE. * If FAILURE is returned, the position of * buf is undefined. If SUCCESS is returned, buf will be positioned after the diff --git a/signkey.h b/signkey.h index 2640171..b58af47 100644 --- a/signkey.h +++ b/signkey.h @@ -44,6 +44,7 @@ enum signkey_type { DROPBEAR_SIGNKEY_ECDSA_NISTP256, DROPBEAR_SIGNKEY_ECDSA_NISTP384, DROPBEAR_SIGNKEY_ECDSA_NISTP521, + DROPBEAR_SIGNKEY_SK_ECDSA_NISTP256, #endif /* DROPBEAR_ECDSA */ #if DROPBEAR_ED25519 DROPBEAR_SIGNKEY_ED25519, @@ -63,6 +64,7 @@ enum signature_type { DROPBEAR_SIGNATURE_ECDSA_NISTP256 = DROPBEAR_SIGNKEY_ECDSA_NISTP256, DROPBEAR_SIGNATURE_ECDSA_NISTP384 = DROPBEAR_SIGNKEY_ECDSA_NISTP384, DROPBEAR_SIGNATURE_ECDSA_NISTP521 = DROPBEAR_SIGNKEY_ECDSA_NISTP521, + DROPBEAR_SIGNATURE_SK_ECDSA_NISTP256 = DROPBEAR_SIGNKEY_SK_ECDSA_NISTP256, #endif /* DROPBEAR_ECDSA */ #if DROPBEAR_ED25519 DROPBEAR_SIGNATURE_ED25519 = DROPBEAR_SIGNKEY_ED25519, @@ -130,6 +132,7 @@ void sign_key_free(sign_key *key); 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 signature_type expect_sigtype, const buffer *data_buf); +int sk_buf_verify(buffer * buf, sign_key *key, enum signature_type expect_sigtype, const buffer *data_buf, char* app, unsigned int applen); 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/sk-ecdsa.c b/sk-ecdsa.c new file mode 100644 index 0000000..405be4b --- /dev/null +++ b/sk-ecdsa.c @@ -0,0 +1,181 @@ +#include "includes.h" +#include "dbutil.h" +#include "ecc.h" +#include "ecdsa.h" +#include "sk-ecdsa.h" + +#if DROPBEAR_SK_ECDSA + +int signkey_is_sk_ecdsa(enum signkey_type type) +{ + return type == DROPBEAR_SIGNKEY_SK_ECDSA_NISTP256; +} + +int buf_sk_ecdsa_verify(buffer *buf, const ecc_key *key, const buffer *data_buf, const char* app, unsigned int applen) { + /* Based on libtomcrypt's ecc_verify_hash but without the asn1 */ + int ret = DROPBEAR_FAILURE; + hash_state hs; + struct dropbear_ecc_curve *curve = NULL; + unsigned char hash[64]; + unsigned char subhash[SHA256_HASH_SIZE]; + buffer *sk_buffer = NULL; + unsigned char flags; + unsigned int counter; + ecc_point *mG = NULL, *mQ = NULL; + void *r = NULL, *s = NULL, *v = NULL, *w = NULL, *u1 = NULL, *u2 = NULL, + *e = NULL, *p = NULL, *m = NULL; + void *mp = NULL; + + /* verify + * + * w = s^-1 mod n + * u1 = xw + * u2 = rw + * X = u1*G + u2*Q + * v = X_x1 mod n + * accept if v == r + */ + + TRACE(("buf_sk_ecdsa_verify")) + curve = curve_for_dp(key->dp); + + mG = ltc_ecc_new_point(); + mQ = ltc_ecc_new_point(); + if (ltc_init_multi(&r, &s, &v, &w, &u1, &u2, &p, &e, &m, NULL) != CRYPT_OK + || !mG + || !mQ) { + dropbear_exit("ECC error"); + } + + if (buf_get_ecdsa_verify_params(buf, r, s) != DROPBEAR_SUCCESS) { + goto out; + } + + flags = buf_getbyte (buf); + counter = buf_getint (buf); + sk_buffer = buf_new (2*SHA256_HASH_SIZE+5); + sha256_init (&hs); + sha256_process (&hs, app, applen); + sha256_done (&hs, subhash); + buf_putbytes (sk_buffer, subhash, sizeof (subhash)); + buf_putbyte (sk_buffer, flags); + buf_putint (sk_buffer, counter); + sha256_init (&hs); + sha256_process (&hs, data_buf->data, data_buf->len); + sha256_done (&hs, subhash); + buf_putbytes (sk_buffer, subhash, sizeof (subhash)); + + curve->hash_desc->init(&hs); + curve->hash_desc->process(&hs, sk_buffer->data, sk_buffer->len); + curve->hash_desc->done(&hs, hash); + + if (ltc_mp.unsigned_read(e, hash, curve->hash_desc->hashsize) != CRYPT_OK) { + goto out; + } + + /* get the order */ + if (ltc_mp.read_radix(p, (char *)key->dp->order, 16) != CRYPT_OK) { + goto out; + } + + /* get the modulus */ + if (ltc_mp.read_radix(m, (char *)key->dp->prime, 16) != CRYPT_OK) { + goto out; + } + + /* check for zero */ + if (ltc_mp.compare_d(r, 0) == LTC_MP_EQ + || ltc_mp.compare_d(s, 0) == LTC_MP_EQ + || ltc_mp.compare(r, p) != LTC_MP_LT + || ltc_mp.compare(s, p) != LTC_MP_LT) { + goto out; + } + + /* w = s^-1 mod n */ + if (ltc_mp.invmod(s, p, w) != CRYPT_OK) { + goto out; + } + + /* u1 = ew */ + if (ltc_mp.mulmod(e, w, p, u1) != CRYPT_OK) { + goto out; + } + + /* u2 = rw */ + if (ltc_mp.mulmod(r, w, p, u2) != CRYPT_OK) { + goto out; + } + + /* find mG and mQ */ + if (ltc_mp.read_radix(mG->x, (char *)key->dp->Gx, 16) != CRYPT_OK) { + goto out; + } + if (ltc_mp.read_radix(mG->y, (char *)key->dp->Gy, 16) != CRYPT_OK) { + goto out; + } + if (ltc_mp.set_int(mG->z, 1) != CRYPT_OK) { + goto out; + } + + if (ltc_mp.copy(key->pubkey.x, mQ->x) != CRYPT_OK + || ltc_mp.copy(key->pubkey.y, mQ->y) != CRYPT_OK + || ltc_mp.copy(key->pubkey.z, mQ->z) != CRYPT_OK) { + goto out; + } + + /* compute u1*mG + u2*mQ = mG */ + if (ltc_mp.ecc_mul2add == NULL) { + if (ltc_mp.ecc_ptmul(u1, mG, mG, m, 0) != CRYPT_OK) { + goto out; + } + if (ltc_mp.ecc_ptmul(u2, mQ, mQ, m, 0) != CRYPT_OK) { + goto out; + } + + /* find the montgomery mp */ + if (ltc_mp.montgomery_setup(m, &mp) != CRYPT_OK) { + goto out; + } + + /* add them */ + if (ltc_mp.ecc_ptadd(mQ, mG, mG, m, mp) != CRYPT_OK) { + goto out; + } + + /* reduce */ + if (ltc_mp.ecc_map(mG, m, mp) != CRYPT_OK) { + goto out; + } + } else { + /* use Shamir's trick to compute u1*mG + u2*mQ using half of the doubles */ + if (ltc_mp.ecc_mul2add(mG, u1, mQ, u2, mG, m) != CRYPT_OK) { + goto out; + } + } + + /* v = X_x1 mod n */ + if (ltc_mp.mpdiv(mG->x, p, NULL, v) != CRYPT_OK) { + goto out; + } + + /* does v == r */ + if (ltc_mp.compare(v, r) == LTC_MP_EQ) { + ret = DROPBEAR_SUCCESS; + } + +out: + ltc_ecc_del_point(mG); + ltc_ecc_del_point(mQ); + ltc_deinit_multi(r, s, v, w, u1, u2, p, e, m, NULL); + if (mp != NULL) { + ltc_mp.montgomery_deinit(mp); + } + if (sk_buffer) { + buf_free(sk_buffer); + } + return ret; +} + + + +#endif /* DROPBEAR_SK_ECDSA */ diff --git a/sk-ecdsa.h b/sk-ecdsa.h new file mode 100644 index 0000000..30f3bcd --- /dev/null +++ b/sk-ecdsa.h @@ -0,0 +1,15 @@ +#ifndef DROPBEAR_SK_ECDSA_H_ +#define DROPBEAR_SK_ECDSA_H_ + +#include "includes.h" +#include "buffer.h" +#include "signkey.h" + +#if DROPBEAR_SK_ECDSA + +int buf_sk_ecdsa_verify(buffer *buf, const ecc_key *key, const buffer *data_buf, const char* app, unsigned int applen); +int signkey_is_sk_ecdsa(enum signkey_type type); + +#endif + +#endif /* DROPBEAR_SK_ECDSA_H_ */ diff --git a/svr-authpubkey.c b/svr-authpubkey.c index a33cc39..4182c86 100644 --- a/svr-authpubkey.c +++ b/svr-authpubkey.c @@ -64,6 +64,7 @@ #include "ssh.h" #include "packet.h" #include "algo.h" +#include "sk-ecdsa.h" #if DROPBEAR_SVR_PUBKEY_AUTH @@ -95,6 +96,11 @@ void svr_auth_pubkey(int valid_user) { enum signature_type sigtype; enum signkey_type keytype; int auth_failure = 1; + int verify_ret = DROPBEAR_FAILURE; +#if DROPBEAR_SK_ECDSA + char* app = NULL; + unsigned int applen; +#endif TRACE(("enter pubkeyauth")) @@ -182,11 +188,17 @@ void svr_auth_pubkey(int valid_user) { goto out; } +#if DROPBEAR_SK_ECDSA + if (signkey_is_sk_ecdsa(keytype)) { + app = buf_getstring (ses.payload, &applen); + } +#endif + /* create the data which has been signed - this a string containing * session_id, concatenated with the payload packet up to the signature */ assert(ses.payload_beginning <= ses.payload->pos); sign_payload_length = ses.payload->pos - ses.payload_beginning; - signbuf = buf_new(ses.payload->pos + 4 + ses.session_id->len); + signbuf = buf_new(ses.payload->pos + 12 + ses.session_id->len); buf_putbufstring(signbuf, ses.session_id); /* The entire contents of the payload prior. */ @@ -200,7 +212,16 @@ void svr_auth_pubkey(int valid_user) { /* ... and finally verify the signature */ fp = sign_key_fingerprint(keyblob, keybloblen); - if (buf_verify(ses.payload, key, sigtype, signbuf) == DROPBEAR_SUCCESS) { +#if DROPBEAR_SK_ECDSA + if (signkey_is_sk_ecdsa(keytype)) { + verify_ret = sk_buf_verify(ses.payload, key, sigtype, signbuf, app, applen); + } else { + verify_ret = buf_verify(ses.payload, key, sigtype, signbuf); + } +#else + verify_ret = buf_verify(ses.payload, key, sigtype, signbuf); +#endif + if (verify_ret == DROPBEAR_SUCCESS) { dropbear_log(LOG_NOTICE, "Pubkey auth succeeded for '%s' with key %s from %s", ses.authstate.pw_name, fp, svr_ses.addrstring); @@ -232,6 +253,11 @@ out: sign_key_free(key); key = NULL; } +#if DROPBEAR_SK_ECDSA + if (app) { + m_free(app); + } +#endif /* Retain pubkey options only if auth succeeded */ if (!ses.authstate.authdone) { svr_pubkey_options_cleanup(); diff --git a/sysoptions.h b/sysoptions.h index 51c4bc9..10c485b 100644 --- a/sysoptions.h +++ b/sysoptions.h @@ -95,6 +95,7 @@ #define DROPBEAR_MAX_PASSWORD_LEN 100 #define SHA1_HASH_SIZE 20 +#define SHA256_HASH_SIZE 32 #define MD5_HASH_SIZE 16 #define MAX_HASH_SIZE 64 /* sha512 */