Implement server-side support for sk-ecdsa U2F-backed keys

This commit is contained in:
Egor Duda 2021-12-23 08:44:31 +03:00
parent 483b427335
commit c8fcc08fe0
No known key found for this signature in database
GPG Key ID: 8610EBBBC18A37F1
11 changed files with 283 additions and 7 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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 */

View File

@ -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

View File

@ -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,

181
sk-ecdsa.c Normal file
View File

@ -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 */

15
sk-ecdsa.h Normal file
View File

@ -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_ */

View File

@ -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();

View File

@ -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 */