From aabd5dbb172971d75158c4d8f2a7ab1f57b078c3 Mon Sep 17 00:00:00 2001 From: Felipe Moura Date: Sat, 11 Jul 2026 16:37:40 -0300 Subject: [PATCH] netutils/dropbear: back chacha20-poly1305 with NuttX /dev/crypto Replace the bundled libtomcrypt chacha20-poly1305 implementation with an adapter that drives the NuttX crypto device: the SSH construction maps onto CRYPTO_CHACHA20_DJB (the original 64-bit counter/nonce ChaCha20 parameterization used by chacha20-poly1305@openssh.com) for the packet length and payload streams, and onto CRYPTO_POLY1305 for the authentication tag (plain MACs are driven in two steps through /dev/crypto: COP_FLAG_UPDATE feeds the data, a final call retrieves the tag). The NuttX crypto backend is a hard requirement of the port: NETUTILS_DROPBEAR selects CRYPTO_CRYPTODEV and its software backend, and the bundled chachapoly.c is unconditionally dropped from the build. Signed-off-by: Felipe Moura --- netutils/dropbear/CMakeLists.txt | 14 + netutils/dropbear/Kconfig | 10 +- netutils/dropbear/Makefile | 10 + netutils/dropbear/port/dropbear_chachapoly.c | 369 +++++++++++++++++++ 4 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 netutils/dropbear/port/dropbear_chachapoly.c diff --git a/netutils/dropbear/CMakeLists.txt b/netutils/dropbear/CMakeLists.txt index 8ca691be094..c1b5adacc58 100644 --- a/netutils/dropbear/CMakeLists.txt +++ b/netutils/dropbear/CMakeLists.txt @@ -135,6 +135,11 @@ if(CONFIG_NETUTILS_DROPBEAR) file(GLOB_RECURSE LIBTOMCRYPT_SRCS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/dropbear/libtomcrypt/src/*.c") list(FILTER LIBTOMCRYPT_SRCS EXCLUDE REGEX ".*/prngs/sober128tab\\.c$") + + # Replace the bundled chacha20-poly1305 with the /dev/crypto adapter. + list(REMOVE_ITEM DROPBEAR_SRCS dropbear/src/chachapoly.c) + list(APPEND DROPBEAR_SRCS port/dropbear_chachapoly.c) + list(APPEND DROPBEAR_SRCS ${LIBTOMCRYPT_SRCS}) if(CONFIG_NETUTILS_DROPBEAR_SCP) @@ -225,6 +230,15 @@ if(CONFIG_NETUTILS_DROPBEAR) set_source_files_properties(${LIBTOMCRYPT_SRCS} PROPERTIES COMPILE_DEFINITIONS LTC_SOURCE=1) + # The NuttX crypto backend pulled in by /dev/crypto also exports ecc_make_key. + # Dropbear only uses libtomcrypt's ecc_make_key_ex, so rename the unused plain + # wrapper to avoid a multiple definition. Append so the LTC_SOURCE define set + # above is preserved. + set_property( + SOURCE dropbear/libtomcrypt/src/pk/ecc/ecc_make_key.c + APPEND + PROPERTY COMPILE_DEFINITIONS ecc_make_key=dropbear_ltc_ecc_make_key) + target_compile_options(${PROGNAME} PRIVATE -Wno-pointer-sign -Wno-format) if(CONFIG_NETUTILS_DROPBEAR_SCP) diff --git a/netutils/dropbear/Kconfig b/netutils/dropbear/Kconfig index 8e7c0868e7c..7c401d99ce2 100644 --- a/netutils/dropbear/Kconfig +++ b/netutils/dropbear/Kconfig @@ -17,13 +17,21 @@ menuconfig NETUTILS_DROPBEAR depends on DEV_URANDOM depends on LIBC_NETDB depends on LIBC_GAISTRERROR - select CRYPTO + depends on ALLOW_BSD_COMPONENTS + depends on CRYPTO_CRYPTODEV + depends on CRYPTO_CRYPTODEV_SOFTWARE_CRYPTO select CRYPTO_RANDOM_POOL ---help--- Enable a minimal Dropbear SSH server port for NuttX. This initial port is based on the ESP-IDF MCU test port and provides a single foreground SSH server process with SSH sessions backed by NSH. + The port implements the chacha20-poly1305@openssh.com cipher + through the NuttX crypto device (/dev/crypto), using the + CRYPTO_CHACHA20_DJB and CRYPTO_POLY1305 algorithms instead of + the bundled libtomcrypt implementation, which is dropped from + the build. + if NETUTILS_DROPBEAR config NETUTILS_DROPBEAR_STACKSIZE diff --git a/netutils/dropbear/Makefile b/netutils/dropbear/Makefile index 53b1f2f720b..fc1ad0cdfa3 100644 --- a/netutils/dropbear/Makefile +++ b/netutils/dropbear/Makefile @@ -119,6 +119,11 @@ CSRCS += \ TOMMATH_SRCS = $(shell if [ -d "$(DROPBEAR_UNPACKNAME)/libtommath" ]; then find "$(DROPBEAR_UNPACKNAME)/libtommath" -name "*.c"; fi) TOMCRYPT_SRCS = $(shell if [ -d "$(DROPBEAR_UNPACKNAME)/libtomcrypt/src" ]; then find "$(DROPBEAR_UNPACKNAME)/libtomcrypt/src" -name "*.c" ! -name "sober128tab.c"; fi) +# Replace the bundled chacha20-poly1305 with the /dev/crypto adapter. + +CSRCS := $(filter-out dropbear/src/chachapoly.c,$(CSRCS)) +CSRCS += port/dropbear_chachapoly.c + CSRCS += $(TOMMATH_SRCS) CSRCS += $(TOMCRYPT_SRCS) @@ -130,6 +135,11 @@ endif # Match the ESP-IDF port behavior: LTC_SOURCE is only for libtomcrypt sources. $(foreach src,$(TOMCRYPT_SRCS),$(eval $(src)_CFLAGS += ${DEFINE_PREFIX}LTC_SOURCE=1)) +# The NuttX crypto backend pulled in by /dev/crypto also exports +# ecc_make_key. Dropbear only uses libtomcrypt's ecc_make_key_ex, so rename +# the unused plain wrapper to avoid a link-time multiple definition. +dropbear/libtomcrypt/src/pk/ecc/ecc_make_key.c_CFLAGS += ${DEFINE_PREFIX}ecc_make_key=dropbear_ltc_ecc_make_key + MAINSRC = dropbear_main.c ifneq ($(CONFIG_NETUTILS_DROPBEAR_SCP),) diff --git a/netutils/dropbear/port/dropbear_chachapoly.c b/netutils/dropbear/port/dropbear_chachapoly.c new file mode 100644 index 00000000000..36de1d1fe4d --- /dev/null +++ b/netutils/dropbear/port/dropbear_chachapoly.c @@ -0,0 +1,369 @@ +/**************************************************************************** + * apps/netutils/dropbear/port/dropbear_chachapoly.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/* chacha20-poly1305@openssh.com backed by the NuttX crypto device. + * + * The SSH construction (see OpenSSH PROTOCOL.chacha20poly1305) uses the + * original DJB ChaCha20 parameterization: a 64-bit block counter in state + * words 12..13 and a 64-bit nonce (the packet sequence number, big endian) + * in words 14..15. This maps to the kernel CRYPTO_CHACHA20_DJB transform, + * whose 16-byte IV is loaded verbatim into words 12..15 as a 64-bit + * little-endian counter followed by the 64-bit nonce. The Poly1305 tag is + * computed with the kernel CRYPTO_POLY1305 transform, keyed with the first + * keystream block (counter 0) of the main key, as the protocol requires. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "includes.h" + +#include +#include +#include +#include + +#include + +#include "algo.h" +#include "dbutil.h" +#include "chachapoly.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define CHACHA20_KEY_LEN 32 +#define CHACHA20_BLOCKSIZE 8 +#define CHACHA20_IV_LEN 16 +#define POLY1305_KEY_LEN 32 +#define POLY1305_TAG_LEN 16 + +/* The keystream comes from the NuttX crypto device, so each upstream + * chacha_state is unused and its input[] buffer just stores the 32-byte key, + * keeping the upstream header unpatched. + */ + +#define KEY_MAIN(s) ((FAR unsigned char *)(s)->chacha.input) +#define KEY_HEADER(s) ((FAR unsigned char *)(s)->header.input) + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct ltc_cipher_descriptor g_dropbear_chachapoly_dummy = +{ + .name = NULL +}; + +static const struct dropbear_hash g_dropbear_chachapoly_mac = +{ + NULL, + POLY1305_KEY_LEN, + POLY1305_TAG_LEN +}; + +static int g_dropbear_cryptofd = -1; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +const struct dropbear_cipher dropbear_chachapoly = +{ + &g_dropbear_chachapoly_dummy, + CHACHA20_KEY_LEN * 2, + CHACHA20_BLOCKSIZE +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int dropbear_cryptodev_fd(void) +{ + int fd; + int cfd; + + if (g_dropbear_cryptofd >= 0) + { + return g_dropbear_cryptofd; + } + + fd = open("/dev/crypto", O_RDWR); + if (fd < 0) + { + return -1; + } + + if (ioctl(fd, CRIOGET, &cfd) < 0) + { + close(fd); + return -1; + } + + close(fd); + g_dropbear_cryptofd = cfd; + return g_dropbear_cryptofd; +} + +/* One ChaCha20 (DJB layout) pass: encrypt/decrypt len bytes with the given + * 64-bit block counter and the packet sequence number as nonce. + */ + +static int dropbear_chacha(FAR const unsigned char *key, unsigned int seq, + uint64_t counter, FAR const unsigned char *in, + FAR unsigned char *out, size_t len) +{ + struct session_op session; + struct crypt_op cryp; + unsigned char iv[CHACHA20_IV_LEN]; + int cfd; + int ret = CRYPT_ERROR; + int i; + + cfd = dropbear_cryptodev_fd(); + if (cfd < 0) + { + return CRYPT_ERROR; + } + + /* IV = 64-bit little-endian block counter || 64-bit nonce. The nonce is + * the packet sequence number stored big endian, as OpenSSH does. + */ + + for (i = 0; i < 8; i++) + { + iv[i] = (unsigned char)(counter >> (8 * i)); + } + + STORE64H((uint64_t)seq, iv + 8); + + memset(&session, 0, sizeof(session)); + session.cipher = CRYPTO_CHACHA20_DJB; + session.key = (caddr_t)key; + session.keylen = CHACHA20_KEY_LEN; + if (ioctl(cfd, CIOCGSESSION, &session) < 0) + { + return CRYPT_ERROR; + } + + memset(&cryp, 0, sizeof(cryp)); + cryp.ses = session.ses; + cryp.op = COP_ENCRYPT; + cryp.len = len; + cryp.src = (caddr_t)in; + cryp.dst = (caddr_t)out; + cryp.iv = (caddr_t)iv; + cryp.ivlen = sizeof(iv); + if (ioctl(cfd, CIOCCRYPT, &cryp) == 0) + { + ret = CRYPT_OK; + } + + ioctl(cfd, CIOCFSESSION, &session.ses); + return ret; +} + +static int dropbear_poly1305(FAR const unsigned char *key, + FAR const unsigned char *in, size_t len, + FAR unsigned char *tag) +{ + struct session_op session; + struct crypt_op cryp; + int cfd; + int ret = CRYPT_ERROR; + + cfd = dropbear_cryptodev_fd(); + if (cfd < 0) + { + return CRYPT_ERROR; + } + + memset(&session, 0, sizeof(session)); + session.mac = CRYPTO_POLY1305; + session.mackey = (caddr_t)key; + session.mackeylen = POLY1305_KEY_LEN; + if (ioctl(cfd, CIOCGSESSION, &session) < 0) + { + return CRYPT_ERROR; + } + + /* Plain (non-HMAC) MACs are driven in two steps through /dev/crypto: + * COP_FLAG_UPDATE feeds the data, then a final call without the flag + * writes out the tag. + */ + + memset(&cryp, 0, sizeof(cryp)); + cryp.ses = session.ses; + cryp.op = COP_ENCRYPT; + cryp.flags = COP_FLAG_UPDATE; + cryp.len = len; + cryp.src = (caddr_t)in; + if (ioctl(cfd, CIOCCRYPT, &cryp) == 0) + { + cryp.flags = 0; + cryp.len = 0; + cryp.src = NULL; + cryp.mac = (caddr_t)tag; + if (ioctl(cfd, CIOCCRYPT, &cryp) == 0) + { + ret = CRYPT_OK; + } + } + + ioctl(cfd, CIOCFSESSION, &session.ses); + return ret; +} + +static int dropbear_chachapoly_start(int cipher, + FAR const unsigned char *iv, + FAR const unsigned char *key, + int keylen, int num_rounds, + FAR void *cipher_state) +{ + FAR dropbear_chachapoly_state *state = cipher_state; + + UNUSED(cipher); + UNUSED(iv); + + if (keylen != CHACHA20_KEY_LEN * 2 || num_rounds != 0) + { + return CRYPT_ERROR; + } + + if (dropbear_cryptodev_fd() < 0) + { + return CRYPT_ERROR; + } + + memcpy(KEY_MAIN(state), key, CHACHA20_KEY_LEN); + memcpy(KEY_HEADER(state), key + CHACHA20_KEY_LEN, CHACHA20_KEY_LEN); + return CRYPT_OK; +} + +static int dropbear_chachapoly_crypt(unsigned int seq, + FAR const unsigned char *in, + FAR unsigned char *out, + unsigned long len, unsigned long taglen, + FAR void *cipher_state, + int direction) +{ + FAR dropbear_chachapoly_state *state = cipher_state; + unsigned char key[POLY1305_KEY_LEN]; + unsigned char tag[POLY1305_TAG_LEN]; + unsigned char zero[POLY1305_KEY_LEN]; + int ret = CRYPT_ERROR; + + if (len < 4 || taglen != POLY1305_TAG_LEN) + { + return CRYPT_ERROR; + } + + /* Poly1305 key = first keystream block of the main key at counter 0 */ + + memset(zero, 0, sizeof(zero)); + if (dropbear_chacha(KEY_MAIN(state), seq, 0, zero, key, + sizeof(key)) != CRYPT_OK) + { + goto out; + } + + if (direction == LTC_DECRYPT) + { + if (dropbear_poly1305(key, in, len, tag) != CRYPT_OK) + { + goto out; + } + + if (constant_time_memcmp(in + len, tag, sizeof(tag)) != 0) + { + goto out; + } + } + + /* Packet length: header key, counter 0. Payload: main key, counter 1. */ + + if (dropbear_chacha(KEY_HEADER(state), seq, 0, in, out, 4) != CRYPT_OK) + { + goto out; + } + + if (dropbear_chacha(KEY_MAIN(state), seq, 1, in + 4, out + 4, + len - 4) != CRYPT_OK) + { + goto out; + } + + if (direction == LTC_ENCRYPT) + { + if (dropbear_poly1305(key, out, len, out + len) != CRYPT_OK) + { + goto out; + } + } + + ret = CRYPT_OK; + +out: + zeromem(key, sizeof(key)); + zeromem(tag, sizeof(tag)); + return ret; +} + +static int +dropbear_chachapoly_getlength(unsigned int seq, FAR const unsigned char *in, + FAR unsigned int *outlen, unsigned long len, + FAR void *cipher_state) +{ + FAR dropbear_chachapoly_state *state = cipher_state; + unsigned char buf[4]; + + if (len < sizeof(buf)) + { + return CRYPT_ERROR; + } + + if (dropbear_chacha(KEY_HEADER(state), seq, 0, in, buf, + sizeof(buf)) != CRYPT_OK) + { + return CRYPT_ERROR; + } + + LOAD32H(*outlen, buf); + return CRYPT_OK; +} + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +const struct dropbear_cipher_mode dropbear_mode_chachapoly = +{ + dropbear_chachapoly_start, + NULL, + NULL, + dropbear_chachapoly_crypt, + dropbear_chachapoly_getlength, + &g_dropbear_chachapoly_mac +};