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 <moura.fmo@gmail.com>
This commit is contained in:
Felipe Moura 2026-07-24 20:18:38 -03:00 committed by Alan C. Assis
parent 101b713be6
commit 355d090d83
4 changed files with 404 additions and 0 deletions

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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 <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <crypto/cryptodev.h>
#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
};