From 0bda809b70abf2cc2767be54ab29f5c1b968ba73 Mon Sep 17 00:00:00 2001 From: Felipe Moura Date: Fri, 24 Jul 2026 20:18:38 -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). Signed-off-by: Felipe Moura --- netutils/dropbear/CMakeLists.txt | 4 + netutils/dropbear/Kconfig | 6 + netutils/dropbear/Makefile | 5 + netutils/dropbear/port/dropbear_chachapoly.c | 389 +++++++++++++++++++ 4 files changed, 404 insertions(+) create mode 100644 netutils/dropbear/port/dropbear_chachapoly.c diff --git a/netutils/dropbear/CMakeLists.txt b/netutils/dropbear/CMakeLists.txt index 96cbcc233..c429a2a0d 100644 --- a/netutils/dropbear/CMakeLists.txt +++ b/netutils/dropbear/CMakeLists.txt @@ -142,6 +142,10 @@ if(CONFIG_NETUTILS_DROPBEAR) list(FILTER LIBTOMCRYPT_SRCS EXCLUDE REGEX ".*/mac/hmac/hmac_done\\.c$") list(APPEND DROPBEAR_SRCS port/dropbear_ltc_hmac_sha256.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) diff --git a/netutils/dropbear/Kconfig b/netutils/dropbear/Kconfig index ae37135cb..99066ee2e 100644 --- a/netutils/dropbear/Kconfig +++ b/netutils/dropbear/Kconfig @@ -37,6 +37,12 @@ menuconfig NETUTILS_DROPBEAR hash states (hashkeys() in common-kex.c), which cannot be represented by a kernel crypto session. + The port also 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 145e5c5ae..f36398088 100644 --- a/netutils/dropbear/Makefile +++ b/netutils/dropbear/Makefile @@ -129,6 +129,11 @@ TOMCRYPT_SRCS := $(filter-out \ CSRCS += port/dropbear_ltc_hmac_sha256.c +# 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) diff --git a/netutils/dropbear/port/dropbear_chachapoly.c b/netutils/dropbear/port/dropbear_chachapoly.c new file mode 100644 index 000000000..f670d14ca --- /dev/null +++ b/netutils/dropbear/port/dropbear_chachapoly.c @@ -0,0 +1,389 @@ +/**************************************************************************** + * 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 +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +const struct dropbear_cipher dropbear_chachapoly = +{ + &g_dropbear_chachapoly_dummy, + CHACHA20_KEY_LEN * 2, + CHACHA20_BLOCKSIZE +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* Open a fresh /dev/crypto session descriptor. NuttX file descriptors are + * owned by the task group, so the fd is never cached across calls: each + * operation opens it in the task that uses it and closes it when done. + */ + +static int dropbear_cryptodev_open(void) +{ + int fd; + int cfd; + + fd = open("/dev/crypto", O_RDWR); + if (fd < 0) + { + return -1; + } + + if (ioctl(fd, CRIOGET, &cfd) < 0) + { + close(fd); + return -1; + } + + close(fd); + + /* Keep the descriptor out of the NSH shells Dropbear forks and execs for + * each SSH session. + */ + + if (fcntl(cfd, F_SETFD, FD_CLOEXEC) < 0) + { + close(cfd); + return -1; + } + + return cfd; +} + +/* 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(int cfd, 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 ret = CRYPT_ERROR; + int i; + + /* 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(int cfd, 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 ret = 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; + int cfd; + + UNUSED(cipher); + UNUSED(iv); + + if (keylen != CHACHA20_KEY_LEN * 2 || num_rounds != 0) + { + return CRYPT_ERROR; + } + + /* Validate that the crypto device is reachable at cipher setup. */ + + cfd = dropbear_cryptodev_open(); + if (cfd < 0) + { + return CRYPT_ERROR; + } + + close(cfd); + + 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 cfd; + int ret = CRYPT_ERROR; + + if (len < 4 || taglen != POLY1305_TAG_LEN) + { + return CRYPT_ERROR; + } + + cfd = dropbear_cryptodev_open(); + if (cfd < 0) + { + return CRYPT_ERROR; + } + + /* Poly1305 key = first keystream block of the main key at counter 0 */ + + memset(zero, 0, sizeof(zero)); + if (dropbear_chacha(cfd, KEY_MAIN(state), seq, 0, zero, key, + sizeof(key)) != CRYPT_OK) + { + goto out; + } + + if (direction == LTC_DECRYPT) + { + if (dropbear_poly1305(cfd, 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(cfd, KEY_HEADER(state), seq, 0, in, out, 4) != + CRYPT_OK) + { + goto out; + } + + if (dropbear_chacha(cfd, KEY_MAIN(state), seq, 1, in + 4, out + 4, + len - 4) != CRYPT_OK) + { + goto out; + } + + if (direction == LTC_ENCRYPT) + { + if (dropbear_poly1305(cfd, key, out, len, out + len) != CRYPT_OK) + { + goto out; + } + } + + ret = CRYPT_OK; + +out: + close(cfd); + 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]; + int cfd; + int ret; + + if (len < sizeof(buf)) + { + return CRYPT_ERROR; + } + + cfd = dropbear_cryptodev_open(); + if (cfd < 0) + { + return CRYPT_ERROR; + } + + ret = dropbear_chacha(cfd, KEY_HEADER(state), seq, 0, in, buf, + sizeof(buf)); + close(cfd); + if (ret != 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 +};