netutils/dropbear: add Dropbear SSH server port for NuttX

Integrated SSH daemon authenticating against FSUTILS_PASSWD, with an
ECDSA P-256 host key and an NSH session over a PTY per connection. Built
from the upstream Dropbear tarball (pinned commit) and patched for
NuttX, using Dropbear's bundled libtomcrypt for all crypto. setsid()
(apache/nuttx#19184) and link() now come from NuttX, not local stubs.

Signed-off-by: Felipe Moura <moura.fmo@gmail.com>
This commit is contained in:
Felipe Moura 2026-06-23 19:40:49 -03:00 committed by Alan C. Assis
parent e44c4f370f
commit fee2ddbf54
24 changed files with 2449 additions and 1 deletions

View file

@ -0,0 +1,26 @@
--- a/src/dbutil.c
+++ b/src/dbutil.c
@@ -66,6 +66,10 @@
#include "session.h"
#include "atomicio.h"
+#ifdef DROPBEAR_NUTTX
+int execv(const char *path, char * const argv[]);
+#endif
+
#define MAX_FMT 100
static void generic_dropbear_exit(int exitcode, const char* format,
--- a/src/signkey.h
+++ b/src/signkey.h
@@ -150,9 +150,9 @@
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
+char * sign_key_fingerprint(const unsigned char* keyblob, unsigned int keybloblen);
int cmp_base64_key(const unsigned char* keyblob, unsigned int keybloblen,
const unsigned char* algoname, unsigned int algolen,
const buffer * line, char ** fingerprint);

View file

@ -0,0 +1,87 @@
--- a/src/svr-authpasswd.c
+++ b/src/svr-authpasswd.c
@@ -33,6 +33,77 @@
#if DROPBEAR_SVR_PASSWORD_AUTH
+#if DROPBEAR_NUTTX_PASSWD
+
+/* Process a password auth request, sending success or failure messages as
+ * appropriate */
+void svr_auth_password(int valid_user) {
+
+ char * password = NULL;
+ unsigned int passwordlen;
+ unsigned int changepw;
+ int auth_ok = 0;
+
+ /* check if client wants to change password */
+ changepw = buf_getbool(ses.payload);
+ if (changepw) {
+ /* not implemented by this server */
+ send_msg_userauth_failure(0, 1);
+ return;
+ }
+
+ password = buf_getstring(ses.payload, &passwordlen);
+ if (valid_user && passwordlen <= DROPBEAR_MAX_PASSWORD_LEN &&
+ strlen(password) == passwordlen) {
+ auth_ok = dropbear_verify_password(ses.authstate.pw_name, password);
+ }
+ m_burn(password, passwordlen);
+ m_free(password);
+
+ /* After we have got the payload contents we can exit if the username
+ is invalid. Invalid users have already been logged. */
+ if (!valid_user) {
+ send_msg_userauth_failure(0, 1);
+ return;
+ }
+
+ if (passwordlen > DROPBEAR_MAX_PASSWORD_LEN) {
+ dropbear_log(LOG_WARNING,
+ "Too-long password attempt for '%s' from %s",
+ ses.authstate.pw_name,
+ svr_ses.addrstring);
+ send_msg_userauth_failure(0, 1);
+ return;
+ }
+
+ if (auth_ok == DROPBEAR_SUCCESS) {
+ if (svr_opts.multiauthmethod && (ses.authstate.authtypes & ~AUTH_TYPE_PASSWORD)) {
+ /* successful password authentication, but extra auth required */
+ dropbear_log(LOG_NOTICE,
+ "Password auth succeeded for '%s' from %s, extra auth required",
+ ses.authstate.pw_name,
+ svr_ses.addrstring);
+ ses.authstate.authtypes &= ~AUTH_TYPE_PASSWORD; /* password auth ok, delete the method flag */
+ send_msg_userauth_failure(1, 0); /* Send partial success */
+ } else {
+ /* successful authentication */
+ dropbear_log(LOG_NOTICE,
+ "Password auth succeeded for '%s' from %s",
+ ses.authstate.pw_name,
+ svr_ses.addrstring);
+ send_msg_userauth_success();
+ }
+ } else {
+ dropbear_log(LOG_WARNING,
+ "Bad password attempt for '%s' from %s",
+ ses.authstate.pw_name,
+ svr_ses.addrstring);
+ send_msg_userauth_failure(0, 1);
+ }
+}
+
+#else
+
/* not constant time when strings are differing lengths.
string content isn't leaked, and crypt hashes are predictable length. */
static int constant_time_strcmp(const char* a, const char* b) {
@@ -131,4 +202,6 @@
}
}
+#endif /* DROPBEAR_NUTTX_PASSWD */
+
#endif

View file

@ -0,0 +1,24 @@
--- a/src/sysoptions.h
+++ b/src/sysoptions.h
@@ -183,7 +183,9 @@ defined(__has_feature)
#define LTC_ECC521
#endif
-#define DROPBEAR_LTC_PRNG (DROPBEAR_ECC)
+#ifndef DROPBEAR_LTC_PRNG
+#define DROPBEAR_LTC_PRNG (DROPBEAR_ECC)
+#endif
/* RSA can be vulnerable to timing attacks which use the time required for
* signing to guess the private key. Blinding avoids this attack, though makes
@@ -436,7 +438,9 @@ defined(__has_feature)
#define DROPBEAR_CLIENT_TCP_FAST_OPEN 0
#endif
-#define DROPBEAR_TRACKING_MALLOC (DROPBEAR_FUZZ)
+#ifndef DROPBEAR_TRACKING_MALLOC
+#define DROPBEAR_TRACKING_MALLOC (DROPBEAR_FUZZ)
+#endif
/* Used to work around Memory Sanitizer false positives */
#if defined(__has_feature)

View file

@ -0,0 +1,202 @@
--- a/src/compat.c
+++ b/src/compat.c
@@ -89,6 +89,6 @@
#ifndef HAVE_GETUSERSHELL
static char **curshell, **shells, *strings;
-static char **initshells();
+static char **initshells(void);
#endif
#ifndef HAVE_STRLCPY
@@ -234,7 +234,7 @@
curshell = initshells();
}
-static char **initshells() {
+static char **initshells(void) {
static const char *okshells[] = { COMPAT_USER_SHELLS, NULL };
register char **sp, *cp;
register FILE *fp;
--- a/src/common-algo.c
+++ b/src/common-algo.c
@@ -39,16 +39,25 @@
/* 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*/
-static int void_cipher(const unsigned char* in, unsigned char* out,
- unsigned long len, void* UNUSED(cipher_state)) {
+static int void_cipher(const unsigned char* in, unsigned char* out,
+ unsigned long len, void* cipher_state) {
+ UNUSED(cipher_state);
+
if (in != out) {
memmove(out, in, len);
}
return CRYPT_OK;
}
-static int void_start(int UNUSED(cipher), const unsigned char* UNUSED(IV),
- const unsigned char* UNUSED(key),
- int UNUSED(keylen), int UNUSED(num_rounds), void* UNUSED(cipher_state)) {
+static int void_start(int cipher, const unsigned char* IV,
+ const unsigned char* key,
+ int keylen, int num_rounds, void* cipher_state) {
+ UNUSED(cipher);
+ UNUSED(IV);
+ UNUSED(key);
+ UNUSED(keylen);
+ UNUSED(num_rounds);
+ UNUSED(cipher_state);
+
return CRYPT_OK;
}
--- a/src/common-channel.c
+++ b/src/common-channel.c
@@ -410,7 +410,9 @@
#ifndef HAVE_WRITEV
static int writechannel_fallback(struct Channel* channel, int fd, circbuffer *cbuf,
- const unsigned char *UNUSED(moredata), unsigned int *morelen) {
+ const unsigned char *moredata, unsigned int *morelen) {
+ UNUSED(moredata);
+
unsigned char *circ_p1, *circ_p2;
unsigned int circ_len1, circ_len2;
--- a/src/dbutil.c
+++ b/src/dbutil.c
@@ -138,7 +138,9 @@
}
-static void generic_dropbear_log(int UNUSED(priority), const char* format,
+static void generic_dropbear_log(int priority, const char* format,
va_list param) {
+ UNUSED(priority);
+
char printbuf[1024];
--- a/src/netio.c
+++ b/src/netio.c
@@ -47,7 +47,10 @@
}
}
-static void cancel_callback(int result, int sock, void* UNUSED(data), const char* UNUSED(errstring)) {
+static void cancel_callback(int result, int sock, void* data, const char* errstring) {
+ UNUSED(data);
+ UNUSED(errstring);
+
if (result == DROPBEAR_SUCCESS)
{
m_close(sock);
--- a/src/ltc_prng.c
+++ b/src/ltc_prng.c
@@ -32,8 +32,10 @@
@param prng [out] The PRNG state to initialize
@return CRYPT_OK if successful
*/
-int dropbear_prng_start(prng_state* UNUSED(prng))
+int dropbear_prng_start(prng_state* prng)
{
+ UNUSED(prng);
+
return CRYPT_OK;
}
@@ -44,8 +46,12 @@
@param prng PRNG state to update
@return CRYPT_OK if successful
*/
-int dropbear_prng_add_entropy(const unsigned char* UNUSED(in), unsigned long UNUSED(inlen), prng_state* UNUSED(prng))
+int dropbear_prng_add_entropy(const unsigned char* in, unsigned long inlen, prng_state* prng)
{
+ UNUSED(in);
+ UNUSED(inlen);
+ UNUSED(prng);
+
return CRYPT_OK;
}
@@ -54,8 +60,10 @@
@param prng The PRNG to make active
@return CRYPT_OK if successful
*/
-int dropbear_prng_ready(prng_state* UNUSED(prng))
+int dropbear_prng_ready(prng_state* prng)
{
+ UNUSED(prng);
+
return CRYPT_OK;
}
@@ -66,8 +74,10 @@
@param prng The active PRNG to read from
@return Number of octets read
*/
-unsigned long dropbear_prng_read(unsigned char* out, unsigned long outlen, prng_state* UNUSED(prng))
+unsigned long dropbear_prng_read(unsigned char* out, unsigned long outlen, prng_state* prng)
{
+ UNUSED(prng);
+
LTC_ARGCHK(out != NULL);
genrandom(out, outlen);
return outlen;
@@ -78,8 +88,10 @@
@param prng The PRNG to terminate
@return CRYPT_OK if successful
*/
-int dropbear_prng_done(prng_state* UNUSED(prng))
+int dropbear_prng_done(prng_state* prng)
{
+ UNUSED(prng);
+
return CRYPT_OK;
}
@@ -90,8 +102,11 @@
@param prng The PRNG to export
@return CRYPT_OK if successful
*/
-int dropbear_prng_export(unsigned char* UNUSED(out), unsigned long* outlen, prng_state* UNUSED(prng))
+int dropbear_prng_export(unsigned char* out, unsigned long* outlen, prng_state* prng)
{
+ UNUSED(out);
+ UNUSED(prng);
+
LTC_ARGCHK(outlen != NULL);
*outlen = 0;
@@ -105,8 +120,12 @@
@param prng The PRNG to import
@return CRYPT_OK if successful
*/
-int dropbear_prng_import(const unsigned char* UNUSED(in), unsigned long UNUSED(inlen), prng_state* UNUSED(prng))
+int dropbear_prng_import(const unsigned char* in, unsigned long inlen, prng_state* prng)
{
+ UNUSED(in);
+ UNUSED(inlen);
+ UNUSED(prng);
+
return CRYPT_OK;
}
--- a/src/chachapoly.c
+++ b/src/chachapoly.c
@@ -43,9 +43,13 @@
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),
+static int dropbear_chachapoly_start(int cipher, const unsigned char* IV,
const unsigned char *key, int keylen,
- int UNUSED(num_rounds), dropbear_chachapoly_state *state) {
+ int num_rounds, dropbear_chachapoly_state *state) {
+ UNUSED(cipher);
+ UNUSED(IV);
+ UNUSED(num_rounds);
+
int err;
TRACE2(("enter dropbear_chachapoly_start"))