diff --git a/README.md b/README.md index 95ff39222..35a7c7002 100644 --- a/README.md +++ b/README.md @@ -401,6 +401,25 @@ behavior, give the echoserver the command line option `-f`. $ ./examples/echoserver/echoserver -f +CURVE25519 +========== + +wolfSSH now supports Curve25519 for key exchange. To enable this support simply +compile wolfSSL with support for wolfssh and Curve25519. + + $ cd wolfssl + $ ./configure --enable-wolfssh --enable-curve25519 + +After building and installing wolfSSL, you can simply configure with no options. + + $ cd wolfssh + $ ./configure + +The wolfSSH client and server will automatically negotiate using Curve25519. + + $ ./examples/echoserver/echoserver -f + + $ ./examples/client/client -u jill -P upthehill POST-QUANTUM ============ diff --git a/src/internal.c b/src/internal.c index 544b4abdb..f69dfdcc0 100644 --- a/src/internal.c +++ b/src/internal.c @@ -148,6 +148,9 @@ algorithms off. WOLFSSH_KEY_QUANTITY_REQ Number of keys required to be in an OpenSSH-style key wrapper. + WOLFSSH_NO_CURVE25519_SHA256 + Set when Curve25519 or SHA2-256 are disabled in wolfSSL. Set to disable use + of Curve25519 key exchange. */ static const char sshProtoIdStr[] = "SSH-2.0-wolfSSHv" @@ -571,6 +574,9 @@ static const char cannedKexAlgoNames[] = #if !defined(WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256) "ecdh-nistp256-kyber-512r3-sha256-d00@openquantumsafe.org," #endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + "curve25519-sha256," +#endif #if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP521) "ecdh-sha2-nistp521," #endif @@ -2148,6 +2154,10 @@ static const NameIdPair NameIdMap[] = { /* We use kyber-512 here to achieve interop with OQS's fork. */ { ID_ECDH_NISTP256_KYBER_LEVEL1_SHA256, TYPE_KEX, "ecdh-nistp256-kyber-512r3-sha256-d00@openquantumsafe.org" }, +#endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + /* See RFC 8731 */ + { ID_CURVE25519_SHA256, TYPE_KEX, "curve25519-sha256" }, #endif { ID_EXTINFO_S, TYPE_OTHER, "ext-info-s" }, { ID_EXTINFO_C, TYPE_OTHER, "ext-info-c" }, @@ -3374,6 +3384,10 @@ static INLINE enum wc_HashType HashForId(byte id) case ID_ECDH_NISTP256_KYBER_LEVEL1_SHA256: return WC_HASH_TYPE_SHA256; #endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + case ID_CURVE25519_SHA256: + return WC_HASH_TYPE_SHA256; +#endif #ifndef WOLFSSH_NO_RSA_SHA2_256 case ID_RSA_SHA2_256: return WC_HASH_TYPE_SHA256; @@ -3439,6 +3453,10 @@ static INLINE int wcPrimeForId(byte id) case ID_ECDSA_SHA2_NISTP384: return ECC_SECP384R1; #endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + case ID_CURVE25519_SHA256: + return ECC_X25519; +#endif #ifndef WOLFSSH_NO_ECDH_SHA2_NISTP521 case ID_ECDH_SHA2_NISTP521: return ECC_SECP521R1; @@ -4668,6 +4686,9 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) if (!ssh->handshake->useEcc #ifndef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 && !ssh->handshake->useEccKyber +#endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + && !ssh->handshake->useCurve25519 #endif ) { #ifndef WOLFSSH_NO_DH @@ -4712,6 +4733,37 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) ret = WS_INVALID_ALGO_ID; #endif } +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + else if (ssh->handshake->useCurve25519) { + curve25519_key pub; + ret = wc_curve25519_init(&pub); + + if (ret == 0) + ret = wc_curve25519_check_public(f, fSz, + EC25519_LITTLE_ENDIAN); + + if (ret == 0) { + ret = wc_curve25519_import_public_ex(f, fSz, &pub, + EC25519_LITTLE_ENDIAN); + } + + if (ret == 0) { + PRIVATE_KEY_UNLOCK(); + ret = wc_curve25519_shared_secret_ex( + &ssh->handshake->privKey.curve25519, &pub, + ssh->k, &ssh->kSz, EC25519_LITTLE_ENDIAN); + PRIVATE_KEY_LOCK(); + } + + wc_curve25519_free(&pub); + wc_curve25519_free(&ssh->handshake->privKey.curve25519); + + if (ret != 0) { + WLOG(WS_LOG_ERROR, + "Gen curve25519 shared secret failed, %d", ret); + } + } +#endif /* !WOLFSSH_NO_CURVE25519_SHA256 */ #ifndef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 else if (ssh->handshake->useEccKyber) { /* This is a a hybrid of ECDHE and a post-quantum KEM. In this @@ -4954,8 +5006,8 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) if (ret == WS_SUCCESS) { int useKeyPadding = 1; -#ifndef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 - useKeyPadding = !ssh->handshake->useEccKyber; +#if !defined(WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256) + doKeyPadding = !ssh->handshake->useEccKyber; #endif ret = GenerateKeys(ssh, hashId, useKeyPadding); } @@ -9775,6 +9827,9 @@ int SendKexDhReply(WOLFSSH* ssh) byte useEccKyber = 0; byte sharedSecretHashSz = 0; byte *sharedSecretHash = NULL; +#endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + byte useCurve25519 = 0; #endif byte fPad = 0; byte kPad = 0; @@ -9858,6 +9913,12 @@ int SendKexDhReply(WOLFSSH* ssh) msgId = MSGID_KEXDH_REPLY; break; #endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + case ID_CURVE25519_SHA256: + useCurve25519 = 1; + msgId = MSGID_KEXDH_REPLY; + break; +#endif #ifndef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 case ID_ECDH_NISTP256_KYBER_LEVEL1_SHA256: useEccKyber = 1; /* Only support level 1 for now. */ @@ -9901,6 +9962,9 @@ int SendKexDhReply(WOLFSSH* ssh) if (!useEcc #ifndef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 && !useEccKyber +#endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + && !useCurve25519 #endif ) { #ifndef WOLFSSH_NO_DH @@ -10016,6 +10080,62 @@ int SendKexDhReply(WOLFSSH* ssh) #endif #endif /* !defined(WOLFSSH_NO_ECDH) */ } +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + if (useCurve25519) { +#ifdef WOLFSSH_SMALL_STACK + curve25519_key *pubKey = NULL, *privKey = NULL; + pubKey = (curve25519_key*)WMALLOC(sizeof(curve25519_key), + heap, DYNTYPE_PUBKEY); + privKey = (curve25519_key*)WMALLOC(sizeof(curve25519_key), + heap, DYNTYPE_PRIVKEY); + if (pubKey == NULL || privKey == NULL) { + ret = WS_MEMORY_E; + } +#else + curve25519_key pubKey[1], privKey[1]; +#endif + + if (ret == 0) + ret = wc_curve25519_init_ex(pubKey, heap, + INVALID_DEVID); + if (ret == 0) + ret = wc_curve25519_init_ex(privKey, heap, + INVALID_DEVID); + if (ret == 0) + ret = wc_curve25519_check_public(ssh->handshake->e, + ssh->handshake->eSz, EC25519_LITTLE_ENDIAN); + if (ret == 0) + ret = wc_curve25519_import_public_ex( + ssh->handshake->e, ssh->handshake->eSz, + pubKey, EC25519_LITTLE_ENDIAN); + + if (ret == 0) + ret = wc_curve25519_make_key(ssh->rng, + CURVE25519_KEYSIZE, privKey); + + if (ret == 0) { + PRIVATE_KEY_UNLOCK(); + ret = wc_curve25519_export_public_ex(privKey, + f_ptr, &fSz, EC25519_LITTLE_ENDIAN); + PRIVATE_KEY_LOCK(); + } + + if (ret == 0) { + PRIVATE_KEY_UNLOCK(); + ret = wc_curve25519_shared_secret_ex(privKey, pubKey, + ssh->k, &ssh->kSz, EC25519_LITTLE_ENDIAN); + PRIVATE_KEY_LOCK(); + } + wc_curve25519_free(privKey); + wc_curve25519_free(pubKey); +#ifdef WOLFSSH_SMALL_STACK + WFREE(pubKey, heap, DYNTYPE_PUBKEY); + WFREE(privKey, heap, DYNTYPE_PRIVKEY); + pubKey = NULL; + privKey = NULL; +#endif + } +#endif /* ! WOLFSSH_NO_CURVE25519_SHA256 */ #ifndef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 else if (useEccKyber) { /* This is a hybrid KEM. In this case, I need to generate my ECC @@ -10163,6 +10283,9 @@ int SendKexDhReply(WOLFSSH* ssh) if (ret == 0 #ifndef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 && !useEccKyber +#endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + && !useCurve25519 #endif ) { ret = CreateMpint(f_ptr, &fSz, &fPad); @@ -10380,8 +10503,8 @@ int SendKexDhReply(WOLFSSH* ssh) if (ret == WS_SUCCESS) { int doKeyPadding = 1; -#ifndef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 - doKeyPadding = !useEccKyber; +#if !defined(WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256) + doKeyPadding = !ssh->handshake->useEccKyber; #endif ret = GenerateKeys(ssh, hashId, doKeyPadding); } @@ -10795,6 +10918,12 @@ int SendKexDhInit(WOLFSSH* ssh) msgId = MSGID_KEXECDH_INIT; break; #endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + case ID_CURVE25519_SHA256: + ssh->handshake->useCurve25519 = 1; + msgId = MSGID_KEXECDH_INIT; + break; +#endif #ifndef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 case ID_ECDH_NISTP256_KYBER_LEVEL1_SHA256: /* Only support level 1 for now. */ @@ -10813,6 +10942,9 @@ int SendKexDhInit(WOLFSSH* ssh) #ifndef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 && !ssh->handshake->useEccKyber #endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + && !ssh->handshake->useCurve25519 +#endif ) { #ifndef WOLFSSH_NO_DH DhKey* privKey = &ssh->handshake->privKey.dh; @@ -10828,6 +10960,23 @@ int SendKexDhInit(WOLFSSH* ssh) e, &eSz); #endif } +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + else if (ssh->handshake->useCurve25519) { + curve25519_key* privKey = &ssh->handshake->privKey.curve25519; + if (ret == 0) + ret = wc_curve25519_init_ex(privKey, ssh->ctx->heap, + INVALID_DEVID); + if (ret == 0) + ret = wc_curve25519_make_key(ssh->rng, CURVE25519_KEYSIZE, + privKey); + if (ret == 0) { + PRIVATE_KEY_UNLOCK(); + ret = wc_curve25519_export_public_ex(privKey, e, &eSz, + EC25519_LITTLE_ENDIAN); + PRIVATE_KEY_LOCK(); + } + } +#endif /* ! WOLFSSH_NO_CURVE25519_SHA256 */ else if (ssh->handshake->useEcc #ifndef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 || ssh->handshake->useEccKyber @@ -10899,6 +11048,10 @@ int SendKexDhInit(WOLFSSH* ssh) #ifndef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 && !ssh->handshake->useEccKyber #endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + && !ssh->handshake->useCurve25519 +#endif + ) { ret = CreateMpint(e, &eSz, &ePad); } diff --git a/wolfssh/internal.h b/wolfssh/internal.h index 80ffefec0..5de463303 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -37,6 +37,7 @@ #include #include #include +#include #ifdef WOLFSSH_SCP #include #endif @@ -160,6 +161,10 @@ extern "C" { #undef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 #define WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 #endif +#if !defined(HAVE_CURVE25519) || defined(NO_SHA256) + #undef WOLFSSH_NO_CURVE25519_SHA256 + #define WOLFSSH_NO_CURVE25519_SHA256 +#endif #if defined(WOLFSSH_NO_DH_GROUP1_SHA1) && \ defined(WOLFSSH_NO_DH_GROUP14_SHA1) && \ @@ -168,7 +173,8 @@ extern "C" { defined(WOLFSSH_NO_ECDH_SHA2_NISTP384) && \ defined(WOLFSSH_NO_ECDH_SHA2_NISTP521) && \ defined(WOLFSSH_NO_ECDH_SHA2_ED25519) && \ - defined(WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256) + defined(WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256) && \ + defined(WOLFSSH_NO_CURVE25519_SHA256) #error "You need at least one key agreement algorithm." #endif @@ -307,6 +313,9 @@ enum { ID_DH_GROUP14_SHA256, #ifndef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 ID_ECDH_NISTP256_KYBER_LEVEL1_SHA256, +#endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + ID_CURVE25519_SHA256, #endif ID_EXTINFO_S, /* Pseudo-KEX to indicate server extensions. */ ID_EXTINFO_C, /* Pseudo-KEX to indicate client extensions. */ @@ -578,6 +587,9 @@ typedef struct HandshakeInfo { #ifndef WOLFSSH_NO_ECDH_NISTP256_KYBER_LEVEL1_SHA256 byte useEccKyber; #endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + byte useCurve25519; +#endif union { #ifndef WOLFSSH_NO_DH @@ -585,6 +597,9 @@ typedef struct HandshakeInfo { #endif #if !defined(WOLFSSH_NO_ECDSA) && !defined(WOLFSSH_NO_ECDH) ecc_key ecc; +#endif +#ifndef WOLFSSH_NO_CURVE25519_SHA256 + curve25519_key curve25519; #endif } privKey; } HandshakeInfo;