mirror of
https://github.com/clearml/dropbear
synced 2025-06-26 18:17:32 +00:00
Implement server-side support for sk-ecdsa U2F-backed keys
This commit is contained in:
parent
483b427335
commit
c8fcc08fe0
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
8
ecdsa.c
8
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;
|
||||
|
2
ecdsa.h
2
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 */
|
||||
|
44
signkey.c
44
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
|
||||
|
@ -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
181
sk-ecdsa.c
Normal 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
15
sk-ecdsa.h
Normal 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_ */
|
@ -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();
|
||||
|
@ -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 */
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user