diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c159fe7ff7e..c88651b64a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -135,6 +135,10 @@ jobs: runs-on: ubuntu-latest env: DOCKER_BUILDKIT: 1 + # Documented sim/login CI test credential (not a production secret). + # Used when CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y and defconfig omits + # the password. See nuttx tools/update_romfs_password.sh. + NUTTX_ROMFS_PASSWD_PASSWORD: NuttXSimLogin1! strategy: max-parallel: 12 @@ -179,8 +183,10 @@ jobs: uses: ./sources/nuttx/.github/actions/ci-container env: BLOBDIR: /tools/blobs + NUTTX_ROMFS_PASSWD_PASSWORD: NuttXSimLogin1! with: run: | + export NUTTX_ROMFS_PASSWD_PASSWORD=NuttXSimLogin1! export ARTIFACTDIR=`pwd`/buildartifacts for i in 1 2 3; do @@ -295,6 +301,8 @@ jobs: runs-on: macos-15-intel needs: macOS-Arch if: ${{ needs.macOS-Arch.outputs.skip_all_builds != '1' }} + env: + NUTTX_ROMFS_PASSWD_PASSWORD: NuttXSimLogin1! strategy: max-parallel: 2 matrix: @@ -329,6 +337,7 @@ jobs: - name: Run Builds run: | echo "::add-matcher::sources/nuttx/.github/gcc.json" + export NUTTX_ROMFS_PASSWD_PASSWORD=NuttXSimLogin1! export ARTIFACTDIR=`pwd`/buildartifacts cd sources/nuttx/tools/ci ./cibuild.sh -i -c -A -R testlist/${{matrix.boards}}.dat diff --git a/fsutils/passwd/CMakeLists.txt b/fsutils/passwd/CMakeLists.txt index e2e0ebce488..16caf1258a6 100644 --- a/fsutils/passwd/CMakeLists.txt +++ b/fsutils/passwd/CMakeLists.txt @@ -23,7 +23,8 @@ if(CONFIG_FSUTILS_PASSWD) set(CSRCS) - list(APPEND CSRCS passwd_verify.c passwd_find.c passwd_encrypt.c) + list(APPEND CSRCS passwd_verify.c passwd_find.c passwd_encrypt.c + passwd_pbkdf2.c) if(NOT CONFIG_FSUTILS_PASSWD_READONLY) list( diff --git a/fsutils/passwd/Kconfig b/fsutils/passwd/Kconfig index 42a8301e284..198bb2df098 100644 --- a/fsutils/passwd/Kconfig +++ b/fsutils/passwd/Kconfig @@ -6,8 +6,19 @@ config FSUTILS_PASSWD bool "Password file support" default n + depends on CRYPTO_CRYPTODEV + depends on NETUTILS_CODECS + depends on CODECS_BASE64 ---help--- - Enables support for /etc/passwd file access routines + Enables support for /etc/passwd file access routines. + + Requires CONFIG_CRYPTO=y, CRYPTO_CRYPTODEV (and + ALLOW_BSD_COMPONENTS), plus NETUTILS_CODECS/CODECS_BASE64 for + base64url hash encoding. + + NOTE: Password hashes use PBKDF2-HMAC-SHA256 (modular crypt format). + Existing TEA-encrypted /etc/passwd entries are NOT compatible and + must be regenerated. if FSUTILS_PASSWD @@ -23,23 +34,14 @@ config FSUTILS_PASSWD_IOBUFFER_SIZE int "Allocated I/O buffer size" default 512 -config FSUTILS_PASSWD_KEY1 - hex "Encryption key value 1" - default 0 +config FSUTILS_PASSWD_PBKDF2_ITERATIONS + int "Default PBKDF2 iteration count for new passwords" + default 10000 + range 1000 200000 ---help--- - Leave at 0 if random key generation is enabled under Board - Selection. Otherwise set all four keys to unique non-zero values. - -config FSUTILS_PASSWD_KEY2 - hex "Encryption key value 2" - default 0 - -config FSUTILS_PASSWD_KEY3 - hex "Encryption key value 3" - default 0 - -config FSUTILS_PASSWD_KEY4 - hex "Encryption key value 4" - default 0 + Number of PBKDF2-HMAC-SHA256 iterations applied when setting a new + password. Higher values slow brute-force attacks but also increase + login latency on low-MHz MCUs. The iteration count is stored in each + hash string, so changing this option only affects newly-set passwords. endif # FSUTILS_PASSWD diff --git a/fsutils/passwd/Makefile b/fsutils/passwd/Makefile index b11148ffd6d..d15b1c6dbaf 100644 --- a/fsutils/passwd/Makefile +++ b/fsutils/passwd/Makefile @@ -26,6 +26,7 @@ include $(APPDIR)/Make.defs ifeq ($(CONFIG_FSUTILS_PASSWD),y) CSRCS += passwd_verify.c passwd_find.c passwd_encrypt.c +CSRCS += passwd_pbkdf2.c ifneq ($(CONFIG_FSUTILS_PASSWD_READONLY),y) CSRCS += passwd_adduser.c passwd_deluser.c passwd_update.c passwd_append.c CSRCS += passwd_delete.c passwd_lock.c diff --git a/fsutils/passwd/passwd.h b/fsutils/passwd/passwd.h index 6dd1785e385..e617cef37c8 100644 --- a/fsutils/passwd/passwd.h +++ b/fsutils/passwd/passwd.h @@ -36,17 +36,18 @@ * Pre-processor Definitions ****************************************************************************/ -#define MAX_ENCRYPTED 48 /* Maximum size of a password (encrypted, ASCII) */ -#define MAX_USERNAME 48 /* Maximum size of a username */ -#define MAX_RECORD (MAX_USERNAME + MAX_ENCRYPTED + 1) +/* MCF format: $pbkdf2-sha256$$$ */ -/* The TEA incryption algorithm generates 8 bytes of encrypted data per - * 8 bytes of unencrypted data. The encrypted presentation is base64 which - * is 8-bits of ASCII for each 6 bits of data. That is a 3-to-4 expansion - * ratio. MAX_ENCRYPTED must be a multiple of 8 bytes. - */ +#define PASSWD_MCF_PREFIX "$pbkdf2-sha256$" +#define PASSWD_SALT_BYTES 16 +#define PASSWD_HASH_BYTES 32 -#define MAX_PASSWORD (3 * MAX_ENCRYPTED / 4) +/* 15 + 6 + 1 + 22 + 1 + 43 = 88 bytes for default parameters */ + +#define MAX_ENCRYPTED 96 +#define MAX_USERNAME 48 +#define MAX_RECORD (MAX_USERNAME + MAX_ENCRYPTED + 1) +#define MAX_PASSWORD 256 /**************************************************************************** * Public Types @@ -55,7 +56,7 @@ struct passwd_s { off_t offset; /* File offset (start of record) */ - char encrypted[MAX_ENCRYPTED + 1]; /* Encrtyped password in file */ + char encrypted[MAX_ENCRYPTED + 1]; /* Password hash in file */ }; /**************************************************************************** @@ -94,10 +95,11 @@ void passwd_unlock(FAR sem_t *sem); * Name: passwd_encrypt * * Description: - * Encrypt a password. Currently uses the Tiny Encryption Algorithm. + * Hash a password with PBKDF2-HMAC-SHA256 and encode the result in modular + * crypt format for storage in /etc/passwd. * * Input Parameters: - * password -- The password string to be encrypted + * password -- The password string to be hashed * * Returned Value: * Zero (OK) is returned on success; a negated errno value is returned on diff --git a/fsutils/passwd/passwd_append.c b/fsutils/passwd/passwd_append.c index be77c9aed65..125a4cfe671 100644 --- a/fsutils/passwd/passwd_append.c +++ b/fsutils/passwd/passwd_append.c @@ -69,7 +69,7 @@ int passwd_append(FAR const char *username, FAR const char *password) { int errcode = errno; DEBUGASSERT(errcode > 0); - return errcode; + return -errcode; } /* The format of the password file is: diff --git a/fsutils/passwd/passwd_encrypt.c b/fsutils/passwd/passwd_encrypt.c index 50e516f77c2..c1e8b941e51 100644 --- a/fsutils/passwd/passwd_encrypt.c +++ b/fsutils/passwd/passwd_encrypt.c @@ -25,85 +25,145 @@ ****************************************************************************/ #include +#include -#include +#include +#include +#include #include #include -#include +#include +#include -#include +#include #include "passwd.h" +#include "passwd_pbkdf2.h" /**************************************************************************** - * Private Data + * Pre-processor Definitions ****************************************************************************/ -/* This should be better protected */ +#ifndef CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS +# define CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS 10000 +#endif -static uint32_t g_tea_key[4] = -{ - CONFIG_FSUTILS_PASSWD_KEY1, - CONFIG_FSUTILS_PASSWD_KEY2, - CONFIG_FSUTILS_PASSWD_KEY3, - CONFIG_FSUTILS_PASSWD_KEY4 -}; +#define PASSWD_MIN_LENGTH 8 -/**************************************************************************** - * Private Functions - ****************************************************************************/ +static const char g_password_specials[] = + "!@#$%^&*()_+-=[]{}|;:,.<>?"; /**************************************************************************** - * Name: passwd_base64 - * - * Description: - * Encode a 5 bit value as a base64 character. - * - * Input Parameters: - * binary - 5 bit value - * - * Returned Value: - * The ASCII base64 character. Must not return the field delimiter ':' - * + * Private Functions ****************************************************************************/ -static char passwd_base64(uint8_t binary) +static int validate_password_complexity(FAR const char *password) { - /* 0-26 -> 'A'-'Z' */ + FAR const char *p; + size_t passlen; + int has_upper = 0; + int has_lower = 0; + int has_digit = 0; + int has_special = 0; + + passlen = strlen(password); + if (passlen < PASSWD_MIN_LENGTH) + { + _err("ERROR: password must be at least %d characters\n", + PASSWD_MIN_LENGTH); + return -EINVAL; + } - binary &= 63; - if (binary < 26) + if (passlen > MAX_PASSWORD) { - return 'A' + binary; + _err("ERROR: password must be at most %d characters\n", MAX_PASSWORD); + return -EINVAL; } - /* 26-51 -> 'a'-'z' */ + for (p = password; *p != '\0'; p++) + { + if (isupper((unsigned char)*p)) + { + has_upper = 1; + } + else if (islower((unsigned char)*p)) + { + has_lower = 1; + } + else if (isdigit((unsigned char)*p)) + { + has_digit = 1; + } + else if (strchr(g_password_specials, *p) != NULL) + { + has_special = 1; + } + } - binary -= 26; - if (binary < 26) + if (!has_upper) { - return 'a' + binary; + _err("ERROR: password must contain at least one uppercase " + "letter (A-Z)\n"); + return -EINVAL; } - /* 52->61 -> '0'-'9' */ + if (!has_lower) + { + _err("ERROR: password must contain at least one lowercase " + "letter (a-z)\n"); + return -EINVAL; + } + + if (!has_digit) + { + _err("ERROR: password must contain at least one digit (0-9)\n"); + return -EINVAL; + } - binary -= 26; - if (binary < 10) + if (!has_special) { - return '0' + binary; + _err("ERROR: password must contain at least one special " + "character (!@#$%%^&*()_+-=[]{}|;:,.<>?)\n"); + return -EINVAL; } - /* 62 -> '+' */ + return OK; +} + +/**************************************************************************** + * Name: passwd_fill_random + * + * Description: + * Fill a buffer with random bytes using getrandom() or /dev/urandom. + * + ****************************************************************************/ + +static int passwd_fill_random(FAR uint8_t *buf, size_t len) +{ + ssize_t nread; + int fd; + + nread = getrandom(buf, len, 0); + if (nread == (ssize_t)len) + { + return OK; + } - binary -= 10; - if (binary == 0) + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { - return '+'; + return -errno; } - /* 63 -> '/' */ + nread = read(fd, buf, len); + close(fd); - return '/'; + if (nread != (ssize_t)len) + { + return nread < 0 ? -errno : -EIO; + } + + return OK; } /**************************************************************************** @@ -114,108 +174,65 @@ static char passwd_base64(uint8_t binary) * Name: passwd_encrypt * * Description: - * Encrypt a password. Currently uses the Tiny Encryption Algorithm. - * - * Input Parameters: - * password -- The password string to be encrypted - * - * Returned Value: - * Zero (OK) is returned on success; a negated errno value is returned on - * failure. + * Hash a password with PBKDF2-HMAC-SHA256 and encode as modular crypt + * format: $pbkdf2-sha256$$$ * ****************************************************************************/ int passwd_encrypt(FAR const char *password, char encrypted[MAX_ENCRYPTED + 1]) { - union - { - char b[8]; - uint16_t h[4]; - uint32_t l[2]; - } value; - - FAR const char *src; - FAR char *bptr; - FAR char *dest; - uint32_t tmp; - uint8_t remainder; - int remaining; - int gulpsize; - int nbits; - int i; - - /* How long is the password? */ - - remaining = strlen(password); - if (remaining > MAX_PASSWORD) + uint8_t salt[PASSWD_SALT_BYTES]; + uint8_t hash[PASSWD_HASH_BYTES]; + char salt_b64[32]; + char hash_b64[48]; + size_t passlen; + int ret; + + ret = validate_password_complexity(password); + if (ret < 0) { - return -E2BIG; + return ret; } - /* Convert the password in 8-byte TEA cycles */ - - src = password; - dest = encrypted; - *dest = '\0'; + passlen = strlen(password); - remainder = 0; - nbits = 0; - - for (; remaining > 0; remaining -= gulpsize) + ret = passwd_fill_random(salt, sizeof(salt)); + if (ret < 0) { - /* Copy bytes */ - - gulpsize = sizeof(value.b); - if (gulpsize > remaining) - { - gulpsize = remaining; - } - - bptr = value.b; - for (i = 0; i < gulpsize; i++) - { - *bptr++ = *src++; - } - - /* Pad with spaces if necessary */ - - for (; i < sizeof(value.b); i++) - { - *bptr++ = ' '; - } - - /* Perform the conversion for this cycle */ - - tea_encrypt(value.l, g_tea_key); - - /* Generate the base64 output string from this cycle */ - - tmp = remainder; + return ret; + } - for (i = 0; i < 4; i++) - { - tmp = (uint32_t)value.h[i] << nbits | tmp; - nbits += 16; - - while (nbits >= 6) - { - *dest++ = passwd_base64((uint8_t)(tmp & 0x3f)); - tmp >>= 6; - nbits -= 6; - } - } + ret = passwd_pbkdf2_hmac_sha256((FAR const uint8_t *)password, passlen, + salt, sizeof(salt), + CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS, + hash, sizeof(hash)); + if (ret < 0) + { + return ret; + } - remainder = (uint8_t)tmp; - *dest = '\0'; + ret = base64url_encode(salt, sizeof(salt), salt_b64, + sizeof(salt_b64)); + if (ret < 0) + { + return ret; } - /* Handle any remainder */ + ret = base64url_encode(hash, sizeof(hash), hash_b64, + sizeof(hash_b64)); + if (ret < 0) + { + return ret; + } - if (nbits > 0) + ret = snprintf(encrypted, MAX_ENCRYPTED + 1, + PASSWD_MCF_PREFIX "%u$%s$%s", + CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS, + salt_b64, hash_b64); + if (ret < 0 || (size_t)ret > MAX_ENCRYPTED) { - *dest++ = passwd_base64(remainder); - *dest = '\0'; + return -E2BIG; } return OK; diff --git a/fsutils/passwd/passwd_pbkdf2.c b/fsutils/passwd/passwd_pbkdf2.c new file mode 100644 index 00000000000..e070387ab50 --- /dev/null +++ b/fsutils/passwd/passwd_pbkdf2.c @@ -0,0 +1,114 @@ +/**************************************************************************** + * apps/fsutils/passwd/passwd_pbkdf2.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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "passwd_pbkdf2.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int passwd_pbkdf2_hmac_sha256(FAR const uint8_t *pass, size_t passlen, + FAR const uint8_t *salt, size_t saltlen, + uint32_t iterations, + FAR uint8_t *out, size_t outlen) +{ + struct session_op session; + struct crypt_op cryp; + int cryptodev_fd = -1; + int fd = -1; + int ret = 0; + + if (pass == NULL || salt == NULL || out == NULL || passlen == 0 || + saltlen == 0 || iterations == 0 || outlen == 0) + { + return -EINVAL; + } + + fd = open("/dev/crypto", O_RDWR, 0); + if (fd < 0) + { + return -errno; + } + + if (ioctl(fd, CRIOGET, &cryptodev_fd) < 0) + { + ret = -errno; + goto errout; + } + + memset(&session, 0, sizeof(session)); + session.cipher = 0; + session.mac = CRYPTO_PBKDF2_HMAC_SHA256; + session.mackey = (caddr_t)pass; + session.mackeylen = passlen; + + if (ioctl(cryptodev_fd, CIOCGSESSION, &session) < 0) + { + ret = -errno; + goto errout; + } + + memset(&cryp, 0, sizeof(cryp)); + cryp.ses = session.ses; + cryp.op = COP_ENCRYPT; + cryp.src = (caddr_t)salt; + cryp.len = saltlen; + cryp.mac = (caddr_t)out; + cryp.iterations = iterations; + cryp.olen = outlen; + + if (ioctl(cryptodev_fd, CIOCCRYPT, &cryp) < 0) + { + ret = -errno; + goto errout_with_session; + } + +errout_with_session: + ioctl(cryptodev_fd, CIOCFSESSION, &session.ses); + +errout: + if (cryptodev_fd >= 0) + { + close(cryptodev_fd); + } + + if (fd >= 0) + { + close(fd); + } + + return ret; +} diff --git a/fsutils/passwd/passwd_pbkdf2.h b/fsutils/passwd/passwd_pbkdf2.h new file mode 100644 index 00000000000..1a66109fa8c --- /dev/null +++ b/fsutils/passwd/passwd_pbkdf2.h @@ -0,0 +1,44 @@ +/**************************************************************************** + * apps/fsutils/passwd/passwd_pbkdf2.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __APPS_FSUTILS_PASSWD_PASSWD_PBKDF2_H +#define __APPS_FSUTILS_PASSWD_PASSWD_PBKDF2_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +int passwd_pbkdf2_hmac_sha256(FAR const uint8_t *pass, size_t passlen, + FAR const uint8_t *salt, size_t saltlen, + uint32_t iterations, + FAR uint8_t *out, size_t outlen); + +#endif /* __APPS_FSUTILS_PASSWD_PASSWD_PBKDF2_H */ diff --git a/fsutils/passwd/passwd_verify.c b/fsutils/passwd/passwd_verify.c index 2c63f37591d..fef8da6dfd3 100644 --- a/fsutils/passwd/passwd_verify.c +++ b/fsutils/passwd/passwd_verify.c @@ -24,11 +24,105 @@ * Included Files ****************************************************************************/ +#include + +#include +#include +#include #include -#include -#include "fsutils/passwd.h" +#include +#include + #include "passwd.h" +#include "passwd_pbkdf2.h" + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: passwd_verify_hash + * + * Description: + * Verify a password against a stored PBKDF2-SHA256 modular crypt hash. + * Rejects any hash not in $pbkdf2-sha256$ format. + * + * Returned Value: + * 0 on match, -1 on mismatch or parse error. + * + ****************************************************************************/ + +static int passwd_verify_hash(FAR const char *stored, + FAR const char *password) +{ + FAR const char *p; + FAR const char *salt_b64; + FAR const char *hash_b64; + char *endptr; + uint8_t salt[PASSWD_SALT_BYTES]; + uint8_t expected[PASSWD_HASH_BYTES]; + uint8_t actual[PASSWD_HASH_BYTES]; + size_t saltlen; + size_t hashlen; + size_t passlen; + unsigned long iterations; + int ret; + + if (strncmp(stored, PASSWD_MCF_PREFIX, strlen(PASSWD_MCF_PREFIX)) != 0) + { + return -1; + } + + p = stored + strlen(PASSWD_MCF_PREFIX); + iterations = strtoul(p, &endptr, 10); + if (endptr == p || *endptr != '$' || iterations < 1 || + iterations > 200000) + { + return -1; + } + + salt_b64 = endptr + 1; + hash_b64 = strchr(salt_b64, '$'); + if (hash_b64 == NULL) + { + return -1; + } + + ret = base64url_decode(salt_b64, salt, sizeof(salt), &saltlen); + if (ret < 0 || saltlen == 0) + { + return -1; + } + + ret = base64url_decode(hash_b64 + 1, expected, sizeof(expected), + &hashlen); + if (ret < 0 || hashlen != PASSWD_HASH_BYTES) + { + return -1; + } + + passlen = strlen(password); + if (passlen == 0 || passlen > MAX_PASSWORD) + { + return -1; + } + + ret = passwd_pbkdf2_hmac_sha256((FAR const uint8_t *)password, passlen, + salt, saltlen, (uint32_t)iterations, + actual, sizeof(actual)); + if (ret < 0) + { + return -1; + } + + if (timingsafe_bcmp(actual, expected, sizeof(expected)) != 0) + { + return -1; + } + + return 0; +} /**************************************************************************** * Public Functions @@ -39,53 +133,33 @@ * * Description: * Return true if the username exists in the /etc/passwd file and if the - * password matches the user password in that failed. - * - * Input Parameters: + * password matches the user password in that file. * * Returned Value: - * One (1) is returned on success match, Zero (OK) is returned on an - * unsuccessful match; a negated errno value is returned on any other - * failure. + * Zero (0) is returned on a successful match, -1 on mismatch or invalid + * hash format; a negated errno value is returned on other failures. * ****************************************************************************/ int passwd_verify(FAR const char *username, FAR const char *password) { struct passwd_s passwd; - char encrypted[MAX_ENCRYPTED + 1]; PASSWD_SEM_DECL(sem); int ret; - /* Get exclusive access to the /etc/passwd file */ - ret = passwd_lock(&sem); if (ret < 0) { return ret; } - /* Verify that the username exists in the /etc/passwd file */ - ret = passwd_find(username, &passwd); if (ret < 0) { - /* The username does not exist in the /etc/passwd file */ - goto errout_with_lock; } - /* Encrypt the provided password */ - - ret = passwd_encrypt(password, encrypted); - if (ret < 0) - { - goto errout_with_lock; - } - - /* Compare the encrypted passwords */ - - ret = (strcmp(passwd.encrypted, encrypted) == 0) ? 1 : 0; + ret = passwd_verify_hash(passwd.encrypted, password); errout_with_lock: passwd_unlock(sem); diff --git a/include/fsutils/passwd.h b/include/fsutils/passwd.h index 996bcf5097f..93fed255db8 100644 --- a/include/fsutils/passwd.h +++ b/include/fsutils/passwd.h @@ -36,9 +36,9 @@ /* passwd_verify() return value tests */ -#define PASSWORD_VERIFY_MATCH(ret) (ret == 1) -#define PASSWORD_VERIFY_NOMATCH(ret) (ret == 0) -#define PASSWORD_VERIFY_ERROR(ret) (ret < 0) +#define PASSWORD_VERIFY_MATCH(ret) ((ret) == 0) +#define PASSWORD_VERIFY_NOMATCH(ret) ((ret) == -1) +#define PASSWORD_VERIFY_ERROR(ret) ((ret) < -1) /**************************************************************************** * Public Function Prototypes @@ -115,9 +115,8 @@ int passwd_update(FAR const char *username, FAR const char *password); * password - The password to be verified * * Returned Value: - * One (1) is returned on success match, Zero (OK) is returned on an - * unsuccessful match; a negated errno value is returned on any other - * failure. + * Zero (0) is returned on a successful match, -1 on mismatch or invalid + * hash format; a negated errno value is returned on other failures. * ****************************************************************************/ diff --git a/include/netutils/base64.h b/include/netutils/base64.h index e5869d8ac09..5f146d7e858 100644 --- a/include/netutils/base64.h +++ b/include/netutils/base64.h @@ -30,17 +30,17 @@ * may be used to endorse or promote products derived from this software * without specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ @@ -73,6 +73,10 @@ FAR void *base64w_encode(FAR const void *src, size_t len, FAR void *dst, FAR size_t *out_len); FAR void *base64w_decode(FAR const void *src, size_t len, FAR void *dst, FAR size_t *out_len); +int base64url_encode(FAR const void *src, size_t len, FAR char *dst, + size_t dstlen); +int base64url_decode(FAR const char *src, FAR void *dst, size_t dstmax, + FAR size_t *out_len); #endif /* CONFIG_CODECS_BASE64 */ #ifdef __cplusplus diff --git a/netutils/codecs/Kconfig b/netutils/codecs/Kconfig index 4b22e1d3f0f..08f5ed81699 100644 --- a/netutils/codecs/Kconfig +++ b/netutils/codecs/Kconfig @@ -16,7 +16,8 @@ config CODECS_BASE64 default n ---help--- Enables support for the following interfaces: base64_encode(), - base64_decode(), base64w_encode(), and base64w_decode(), + base64_decode(), base64w_encode(), base64w_decode(), + base64url_encode(), and base64url_decode(), Contributed NuttX by Darcy Gong. diff --git a/netutils/codecs/base64.c b/netutils/codecs/base64.c index 36485b85a55..731283857be 100644 --- a/netutils/codecs/base64.c +++ b/netutils/codecs/base64.c @@ -55,6 +55,7 @@ #include #include +#include #include #include "netutils/base64.h" @@ -317,4 +318,150 @@ FAR void *base64w_decode(FAR const void *src, size_t len, FAR void *dst, return _base64_decode(src, len, dst, out_len, true); } +/**************************************************************************** + * Name: base64url_val + ****************************************************************************/ + +static int base64url_val(char c) +{ + if (c >= 'A' && c <= 'Z') + { + return c - 'A'; + } + + if (c >= 'a' && c <= 'z') + { + return c - 'a' + 26; + } + + if (c >= '0' && c <= '9') + { + return c - '0' + 52; + } + + if (c == '-') + { + return 62; + } + + if (c == '_') + { + return 63; + } + + return -1; +} + +/**************************************************************************** + * Name: base64url_encode + * + * Description: + * Encode binary data as unpadded base64url (RFC 4648 section 5). + * + ****************************************************************************/ + +int base64url_encode(FAR const void *src, size_t len, FAR char *dst, + size_t dstlen) +{ + FAR const uint8_t *in = src; + uint32_t acc = 0; + size_t i; + size_t o = 0; + int bits = 0; + + static const char g_base64url[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + + for (i = 0; i < len; i++) + { + acc = (acc << 8) | in[i]; + bits += 8; + + while (bits >= 6) + { + if (o + 1 >= dstlen) + { + return -E2BIG; + } + + bits -= 6; + dst[o++] = g_base64url[(acc >> bits) & 0x3f]; + } + } + + if (bits > 0) + { + if (o + 1 >= dstlen) + { + return -E2BIG; + } + + dst[o++] = g_base64url[(acc << (6 - bits)) & 0x3f]; + } + + if (o >= dstlen) + { + return -E2BIG; + } + + dst[o] = '\0'; + return 0; +} + +/**************************************************************************** + * Name: base64url_decode + * + * Description: + * Decode unpadded base64url (RFC 4648 section 5). + * + ****************************************************************************/ + +int base64url_decode(FAR const char *src, FAR void *dst, size_t dstmax, + FAR size_t *out_len) +{ + FAR uint8_t *out = dst; + uint32_t acc = 0; + size_t o = 0; + int bits = 0; + int v; + + *out_len = 0; + + while (*src != '\0') + { + if (*src == '$' || *src == ':') + { + break; + } + + v = base64url_val(*src++); + if (v < 0) + { + return -EINVAL; + } + + acc = (acc << 6) | (uint32_t)v; + bits += 6; + + if (bits >= 8) + { + bits -= 8; + if (o >= dstmax) + { + return -E2BIG; + } + + out[o++] = (uint8_t)((acc >> bits) & 0xff); + } + } + + if (bits >= 6) + { + return -EINVAL; + } + + *out_len = o; + return 0; +} + #endif /* CONFIG_CODECS_BASE64 */ diff --git a/netutils/dropbear/.gitignore b/netutils/dropbear/.gitignore new file mode 100644 index 00000000000..1dc4834a11d --- /dev/null +++ b/netutils/dropbear/.gitignore @@ -0,0 +1,6 @@ +/dropbear +/*.zip +*.o +.built +.depend +Make.dep diff --git a/netutils/dropbear/CMakeLists.txt b/netutils/dropbear/CMakeLists.txt new file mode 100644 index 00000000000..c429a2a0d50 --- /dev/null +++ b/netutils/dropbear/CMakeLists.txt @@ -0,0 +1,256 @@ +# ############################################################################## +# apps/netutils/dropbear/CMakeLists.txt +# +# 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. +# +# ############################################################################## + +if(CONFIG_NETUTILS_DROPBEAR) + + set(DROPBEAR_COMMIT "${CONFIG_NETUTILS_DROPBEAR_COMMIT}") + string(REPLACE "\"" "" DROPBEAR_COMMIT "${DROPBEAR_COMMIT}") + + set(DROPBEAR_ZIP "${DROPBEAR_COMMIT}.zip") + set(DROPBEAR_URL "https://github.com/mkj/dropbear/archive") + set(DROPBEAR_UNPACKNAME "dropbear") + + if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${DROPBEAR_UNPACKNAME}") + if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${DROPBEAR_ZIP}") + message(STATUS "Downloading Dropbear: ${DROPBEAR_URL}/${DROPBEAR_ZIP}") + file(DOWNLOAD "${DROPBEAR_URL}/${DROPBEAR_ZIP}" + "${CMAKE_CURRENT_SOURCE_DIR}/${DROPBEAR_ZIP}") + endif() + message(STATUS "Unpacking Dropbear: ${DROPBEAR_ZIP}") + execute_process( + COMMAND unzip -q -o "${DROPBEAR_ZIP}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE result) + if(result EQUAL 0) + file(RENAME "${CMAKE_CURRENT_SOURCE_DIR}/dropbear-${DROPBEAR_COMMIT}" + "${CMAKE_CURRENT_SOURCE_DIR}/${DROPBEAR_UNPACKNAME}") + execute_process( + COMMAND + patch -s -N -l -p1 -d "${DROPBEAR_UNPACKNAME}" -i + "${CMAKE_CURRENT_SOURCE_DIR}/patch/0001-guard-platform-declarations.patch" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + execute_process( + COMMAND + patch -s -N -l -p1 -d "${DROPBEAR_UNPACKNAME}" -i + "${CMAKE_CURRENT_SOURCE_DIR}/patch/0002-use-nuttx-passwd-auth.patch" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + execute_process( + COMMAND + patch -s -N -l -p1 -d "${DROPBEAR_UNPACKNAME}" -i + "${CMAKE_CURRENT_SOURCE_DIR}/patch/0003-allow-localoptions-to-override-tracking-malloc.patch" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + execute_process( + COMMAND + patch -s -N -l -p1 -d "${DROPBEAR_UNPACKNAME}" -i + "${CMAKE_CURRENT_SOURCE_DIR}/patch/0004-use-nuttx-unused-macro.patch" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + message(STATUS "Generating default_options_guard.h") + execute_process( + COMMAND + sh + "${CMAKE_CURRENT_SOURCE_DIR}/${DROPBEAR_UNPACKNAME}/src/ifndef_wrapper.sh" + INPUT_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/${DROPBEAR_UNPACKNAME}/src/default_options.h" + OUTPUT_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/${DROPBEAR_UNPACKNAME}/src/default_options_guard.h" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + endif() + endif() + + set(PROGNAME "${CONFIG_NETUTILS_DROPBEAR_PROGNAME}") + string(REPLACE "\"" "" PROGNAME "${PROGNAME}") + + set(DROPBEAR_SRCS + dropbear_nshsession.c + port/nuttx_auth.c + port/dropbear_utils.c + dropbear/src/dbutil.c + dropbear/src/buffer.c + dropbear/src/dbhelpers.c + dropbear/src/bignum.c + dropbear/src/signkey.c + dropbear/src/dbrandom.c + dropbear/src/queue.c + dropbear/src/atomicio.c + dropbear/src/compat.c + dropbear/src/fake-rfc2553.c + dropbear/src/curve25519.c + dropbear/src/chachapoly.c + dropbear/src/ltc_prng.c + dropbear/src/ecc.c + dropbear/src/ecdsa.c + dropbear/src/crypto_desc.c + dropbear/src/dbmalloc.c + dropbear/src/gensignkey.c + dropbear/src/common-session.c + dropbear/src/packet.c + dropbear/src/common-algo.c + dropbear/src/common-kex.c + dropbear/src/common-channel.c + dropbear/src/common-chansession.c + dropbear/src/termcodes.c + dropbear/src/tcp-accept.c + dropbear/src/listener.c + dropbear/src/process-packet.c + dropbear/src/common-runopts.c + dropbear/src/circbuffer.c + dropbear/src/list.c + dropbear/src/netio.c + dropbear/src/gcm.c + dropbear/src/kex-x25519.c + dropbear/src/svr-kex.c + dropbear/src/svr-auth.c + dropbear/src/svr-authpasswd.c + dropbear/src/svr-session.c + dropbear/src/svr-service.c + dropbear/src/svr-runopts.c + dropbear/src/svr-tcpfwd.c + dropbear/src/svr-forward.c + dropbear/src/svr-streamfwd.c + dropbear/src/svr-authpam.c) + + file(GLOB LIBTOMMATH_SRCS CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/dropbear/libtommath/*.c") + list(APPEND DROPBEAR_SRCS ${LIBTOMMATH_SRCS}) + + file(GLOB_RECURSE LIBTOMCRYPT_SRCS CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/dropbear/libtomcrypt/src/*.c") + list(FILTER LIBTOMCRYPT_SRCS EXCLUDE REGEX ".*/prngs/sober128tab\\.c$") + + # Drop the bundled libtomcrypt HMAC modules replaced by the NuttX adapter. + list(FILTER LIBTOMCRYPT_SRCS EXCLUDE REGEX ".*/mac/hmac/hmac_init\\.c$") + list(FILTER LIBTOMCRYPT_SRCS EXCLUDE REGEX ".*/mac/hmac/hmac_process\\.c$") + 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) + list(APPEND DROPBEAR_SRCS dropbear/src/scpmisc.c port/nuttx_scp.c) + endif() + + nuttx_add_application( + NAME + ${PROGNAME} + SRCS + ${DROPBEAR_SRCS} + dropbear_main.c + STACKSIZE + ${CONFIG_NETUTILS_DROPBEAR_STACKSIZE} + PRIORITY + ${CONFIG_NETUTILS_DROPBEAR_PRIORITY} + DEPENDS + ${DROPBEAR_UNPACKNAME}) + + if(CONFIG_NETUTILS_DROPBEAR_SCP) + nuttx_add_application( + NAME + scp + SRCS + dropbear/src/scp.c + STACKSIZE + ${CONFIG_NETUTILS_DROPBEAR_SCP_STACKSIZE} + PRIORITY + ${CONFIG_NETUTILS_DROPBEAR_SCP_PRIORITY} + DEPENDS + ${DROPBEAR_UNPACKNAME}) + endif() + + target_include_directories( + ${PROGNAME} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/port + ${CMAKE_CURRENT_SOURCE_DIR}/dropbear + ${CMAKE_CURRENT_SOURCE_DIR}/dropbear/src + ${CMAKE_CURRENT_SOURCE_DIR}/dropbear/libtomcrypt/src/headers + ${CMAKE_CURRENT_SOURCE_DIR}/dropbear/libtommath + ${CMAKE_CURRENT_SOURCE_DIR}/../../nshlib) + + if(CONFIG_NETUTILS_DROPBEAR_SCP) + target_include_directories( + scp + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/port + ${CMAKE_CURRENT_SOURCE_DIR}/dropbear + ${CMAKE_CURRENT_SOURCE_DIR}/dropbear/src) + endif() + + if(CONFIG_NETUTILS_DROPBEAR_COMPRESSION) + target_include_directories( + ${PROGNAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../system/zlib/zlib) + endif() + + target_compile_definitions( + ${PROGNAME} + PRIVATE LOCALOPTIONS_H_EXISTS=1 + DROPBEAR_NUTTX=1 + DROPBEAR_NUTTX_PASSWD=1 + base64_encode=dropbear_ltc_base64_encode + base64_decode=dropbear_ltc_base64_decode + ecc_make_key=dropbear_ltc_ecc_make_key) + + if(CONFIG_NETUTILS_DROPBEAR_SCP) + target_compile_definitions( + scp PRIVATE LOCALOPTIONS_H_EXISTS=1 DROPBEAR_NUTTX=1 + DROPBEAR_NUTTX_PASSWD=1) + endif() + + set_source_files_properties( + dropbear_nshsession.c + PROPERTIES COMPILE_DEFINITIONS + "Channel=dropbear_channel;ChanType=dropbear_chantype") + + if(CONFIG_NETUTILS_DROPBEAR_SCP) + set_source_files_properties( + dropbear/src/scp.c + PROPERTIES + COMPILE_DEFINITIONS + "xmalloc=dropbear_scp_xmalloc;xrealloc=dropbear_scp_xrealloc;xfree=dropbear_scp_xfree;execvp=dropbear_scp_execvp" + ) + set_source_files_properties( + dropbear/src/scpmisc.c + PROPERTIES + COMPILE_DEFINITIONS + "xmalloc=dropbear_scp_xmalloc;xrealloc=dropbear_scp_xrealloc;xfree=dropbear_scp_xfree" + ) + endif() + + # LTC_SOURCE must be set only for libtomcrypt sources. + set_source_files_properties(${LIBTOMCRYPT_SRCS} PROPERTIES COMPILE_DEFINITIONS + LTC_SOURCE=1) + + target_compile_options(${PROGNAME} PRIVATE -Wno-pointer-sign -Wno-format) + + if(CONFIG_NETUTILS_DROPBEAR_SCP) + target_compile_options( + scp + PRIVATE -Wno-pointer-sign -Wno-format -include + ${CMAKE_CURRENT_SOURCE_DIR}/port/nuttx_scp.h + -Wno-strict-prototypes) + endif() + + target_sources(apps PRIVATE ${DROPBEAR_SRCS}) + +endif() diff --git a/netutils/dropbear/Kconfig b/netutils/dropbear/Kconfig new file mode 100644 index 00000000000..99066ee2e98 --- /dev/null +++ b/netutils/dropbear/Kconfig @@ -0,0 +1,157 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +menuconfig NETUTILS_DROPBEAR + tristate "Dropbear SSH server" + default n + depends on NET && NET_TCP + depends on !DISABLE_PSEUDOFS_OPERATIONS + depends on !DISABLE_PTHREAD + depends on SCHED_WAITPID + depends on SCHED_HAVE_PARENT + depends on SCHED_CHILD_STATUS + depends on NSH_LIBRARY + depends on FSUTILS_PASSWD + depends on PSEUDOTERM + depends on SERIAL + depends on DEV_URANDOM + depends on LIBC_NETDB + depends on LIBC_GAISTRERROR + depends on CRYPTO + depends on CRYPTO_RANDOM_POOL + depends on ALLOW_BSD_COMPONENTS + depends on CRYPTO_CRYPTODEV + depends on CRYPTO_CRYPTODEV_SOFTWARE_CRYPTO + ---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 computes the hmac-sha2-256 packet MAC through the NuttX + crypto device (/dev/crypto, CRYPTO_SHA2_256_HMAC) instead of the + bundled libtomcrypt implementation, which is dropped from the + build. The SHA-256 hash descriptor itself stays in the + application: Dropbear's key derivation clones partially-updated + 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 + int "Dropbear main stack size" + default 65536 + ---help--- + Stack size for the Dropbear server built-in. + This is architecture-specific, so adjust it according to your setup. + +config NETUTILS_DROPBEAR_PRIORITY + int "Dropbear main priority" + default 100 + +config NETUTILS_DROPBEAR_SHELL_PRIORITY + int "Dropbear NSH session priority" + default 100 + +config NETUTILS_DROPBEAR_PROGNAME + string "Dropbear program name" + default "dropbear" + ---help--- + This is the name of the program that will be used when the NSH ELF + program is installed. + +config NETUTILS_DROPBEAR_LISTEN_RETRIES + int "Dropbear listen retries" + default 0 + ---help--- + Number of times to retry listen setup when no listen socket could + be opened. Zero means to retry forever. + +config NETUTILS_DROPBEAR_LISTEN_RETRY_MAX + int "Dropbear maximum listen retry interval" + default 120 + range 1 3600 + ---help--- + Maximum number of seconds to wait between listen setup retries. + The retry delay starts at one second and doubles until it reaches + this value. + +config NETUTILS_DROPBEAR_SHELL_STACKSIZE + int "Dropbear NSH session task stack size" + default 8192 + +config NETUTILS_DROPBEAR_SCP + bool "Enable scp remote copy helper" + default y + depends on PIPES + ---help--- + Build Dropbear's scp program and allow SSH exec requests so a host + scp client can copy files to and from NuttX using the legacy scp + protocol. This does not build a full SSH client for initiating scp + transfers from the target. + +config NETUTILS_DROPBEAR_SCP_STACKSIZE + int "Dropbear scp stack size" + default 32768 + depends on NETUTILS_DROPBEAR_SCP + +config NETUTILS_DROPBEAR_SCP_PRIORITY + int "Dropbear scp priority" + default 100 + depends on NETUTILS_DROPBEAR_SCP + +config NETUTILS_DROPBEAR_PORT + int "Dropbear listen port" + default 2222 + +config NETUTILS_DROPBEAR_HOSTKEY_PATH + string "Dropbear ECDSA P-256 host key path" + default "/etc/dropbear/dropbear_ecdsa_host_key" + ---help--- + Path to the persistent ECDSA P-256 host key used by the Dropbear + server. The file is stored in Dropbear's native host key format. + +config NETUTILS_DROPBEAR_GENERATE_HOSTKEY + bool "Generate host key if missing" + default y + ---help--- + Pass -R so Dropbear generates an ECDSA P-256 host key on demand and + persists it at NETUTILS_DROPBEAR_HOSTKEY_PATH when it does not exist. + Product builds can disable this and provision the host key + externally (loaded with -r). + +config NETUTILS_DROPBEAR_COMPRESSION + bool "Enable SSH compression (zlib)" + default n + depends on LIB_ZLIB + ---help--- + Enable zlib compression for SSH sessions. Requires the zlib + library (LIB_ZLIB). When disabled, Dropbear is built with + DISABLE_ZLIB and negotiates no compression. + + WARNING: each session allocates a zlib deflate state of about + 256 KiB (DROPBEAR_ZLIB_WINDOW_BITS=15, DROPBEAR_ZLIB_MEM_LEVEL=8), + and the state is allocated even for the delayed zlib@openssh.com + method, right after key exchange. + +config NETUTILS_DROPBEAR_SYSLOG + bool "Log via syslog" + default n + ---help--- + Route Dropbear log messages through syslog(). When disabled, + Dropbear is built with DISABLE_SYSLOG. + +config NETUTILS_DROPBEAR_COMMIT + string "Dropbear upstream commit" + default "54ef47adf8c99b422be6a8f694f2866e62f88b9e" + ---help--- + Upstream Dropbear (mkj/dropbear) commit to download and build. + +endif diff --git a/netutils/dropbear/Make.defs b/netutils/dropbear/Make.defs new file mode 100644 index 00000000000..9fcb635f7a4 --- /dev/null +++ b/netutils/dropbear/Make.defs @@ -0,0 +1,25 @@ +############################################################################ +# apps/netutils/dropbear/Make.defs +# +# 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. +# +############################################################################ + +ifneq ($(CONFIG_NETUTILS_DROPBEAR),) +CONFIGURED_APPS += $(APPDIR)/netutils/dropbear +endif diff --git a/netutils/dropbear/Makefile b/netutils/dropbear/Makefile new file mode 100644 index 00000000000..f3639808854 --- /dev/null +++ b/netutils/dropbear/Makefile @@ -0,0 +1,205 @@ +############################################################################ +# apps/netutils/dropbear/Makefile +# +# 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. +# +############################################################################ + +include $(APPDIR)/Make.defs + +ifneq ($(CONFIG_NETUTILS_DROPBEAR),) + +DROPBEAR_COMMIT = $(patsubst "%",%,$(strip $(CONFIG_NETUTILS_DROPBEAR_COMMIT))) +DROPBEAR_ZIP = $(DROPBEAR_COMMIT).zip +DROPBEAR_UNPACKNAME = dropbear +DROPBEAR_URL = https://github.com/mkj/dropbear/archive +UNPACK ?= unzip -q -o +ifeq ($(CONFIG_HOST_MACOS),y) +PATCH ?= gpatch +else +PATCH ?= patch +endif + +MODULE = $(CONFIG_NETUTILS_DROPBEAR) +PROGNAME = $(CONFIG_NETUTILS_DROPBEAR_PROGNAME) +PRIORITY = $(CONFIG_NETUTILS_DROPBEAR_PRIORITY) +STACKSIZE = $(CONFIG_NETUTILS_DROPBEAR_STACKSIZE) + +ifneq ($(CONFIG_NETUTILS_DROPBEAR_SCP),) +PROGNAME += scp +PRIORITY += $(CONFIG_NETUTILS_DROPBEAR_SCP_PRIORITY) +STACKSIZE += $(CONFIG_NETUTILS_DROPBEAR_SCP_STACKSIZE) +endif + +CFLAGS += ${INCDIR_PREFIX}"$(APPDIR)$(DELIM)netutils$(DELIM)dropbear" +CFLAGS += ${INCDIR_PREFIX}"$(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)port" +CFLAGS += ${INCDIR_PREFIX}"$(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)dropbear" +CFLAGS += ${INCDIR_PREFIX}"$(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)dropbear$(DELIM)src" +CFLAGS += ${INCDIR_PREFIX}"$(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)dropbear$(DELIM)libtomcrypt$(DELIM)src$(DELIM)headers" +CFLAGS += ${INCDIR_PREFIX}"$(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)dropbear$(DELIM)libtommath" +CFLAGS += ${INCDIR_PREFIX}"$(APPDIR)$(DELIM)nshlib" + +ifneq ($(CONFIG_NETUTILS_DROPBEAR_COMPRESSION),) +CFLAGS += ${INCDIR_PREFIX}"$(APPDIR)$(DELIM)system$(DELIM)zlib$(DELIM)zlib" +endif + +CFLAGS += ${DEFINE_PREFIX}LOCALOPTIONS_H_EXISTS=1 +CFLAGS += ${DEFINE_PREFIX}DROPBEAR_NUTTX=1 +CFLAGS += ${DEFINE_PREFIX}DROPBEAR_NUTTX_PASSWD=1 +CFLAGS += -Wno-pointer-sign -Wno-format + +dropbear_nshsession.c_CFLAGS += ${DEFINE_PREFIX}Channel=dropbear_channel +dropbear_nshsession.c_CFLAGS += ${DEFINE_PREFIX}ChanType=dropbear_chantype + +CSRCS = dropbear_nshsession.c +CSRCS += port/nuttx_auth.c +CSRCS += port/dropbear_utils.c + +CSRCS += \ + dropbear/src/dbutil.c \ + dropbear/src/buffer.c \ + dropbear/src/dbhelpers.c \ + dropbear/src/bignum.c \ + dropbear/src/signkey.c \ + dropbear/src/dbrandom.c \ + dropbear/src/queue.c \ + dropbear/src/atomicio.c \ + dropbear/src/compat.c \ + dropbear/src/fake-rfc2553.c \ + dropbear/src/curve25519.c \ + dropbear/src/chachapoly.c \ + dropbear/src/ltc_prng.c \ + dropbear/src/ecc.c \ + dropbear/src/ecdsa.c \ + dropbear/src/crypto_desc.c \ + dropbear/src/dbmalloc.c \ + dropbear/src/gensignkey.c \ + dropbear/src/common-session.c \ + dropbear/src/packet.c \ + dropbear/src/common-algo.c \ + dropbear/src/common-kex.c \ + dropbear/src/common-channel.c \ + dropbear/src/common-chansession.c \ + dropbear/src/termcodes.c \ + dropbear/src/tcp-accept.c \ + dropbear/src/listener.c \ + dropbear/src/process-packet.c \ + dropbear/src/common-runopts.c \ + dropbear/src/circbuffer.c \ + dropbear/src/list.c \ + dropbear/src/netio.c \ + dropbear/src/gcm.c \ + dropbear/src/kex-x25519.c \ + dropbear/src/svr-kex.c \ + dropbear/src/svr-auth.c \ + dropbear/src/svr-authpasswd.c \ + dropbear/src/svr-session.c \ + dropbear/src/svr-service.c \ + dropbear/src/svr-runopts.c \ + dropbear/src/svr-tcpfwd.c \ + dropbear/src/svr-forward.c \ + dropbear/src/svr-streamfwd.c \ + dropbear/src/svr-authpam.c + +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) + +# Drop the bundled libtomcrypt HMAC modules replaced by the NuttX adapter. + +TOMCRYPT_SRCS := $(filter-out \ + %/mac/hmac/hmac_init.c \ + %/mac/hmac/hmac_process.c \ + %/mac/hmac/hmac_done.c, \ + $(TOMCRYPT_SRCS)) + +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) + +ifneq ($(CONFIG_NETUTILS_DROPBEAR_SCP),) +CSRCS += dropbear/src/scpmisc.c +CSRCS += port/nuttx_scp.c +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)) + +# Avoid duplicate symbols when NuttX/apps also provide these APIs. +# Apply to all dropbear sources so libtomcrypt definitions and dropbear +# callers (e.g. signkey.c) use the same renamed symbols. +CFLAGS += ${DEFINE_PREFIX}base64_encode=dropbear_ltc_base64_encode +CFLAGS += ${DEFINE_PREFIX}base64_decode=dropbear_ltc_base64_decode +CFLAGS += ${DEFINE_PREFIX}ecc_make_key=dropbear_ltc_ecc_make_key + +MAINSRC = dropbear_main.c + +ifneq ($(CONFIG_NETUTILS_DROPBEAR_SCP),) +MAINSRC += dropbear/src/scp.c + +dropbear/src/scp.c_CFLAGS += ${DEFINE_PREFIX}xmalloc=dropbear_scp_xmalloc +dropbear/src/scp.c_CFLAGS += ${DEFINE_PREFIX}xrealloc=dropbear_scp_xrealloc +dropbear/src/scp.c_CFLAGS += ${DEFINE_PREFIX}xfree=dropbear_scp_xfree +dropbear/src/scp.c_CFLAGS += ${DEFINE_PREFIX}execvp=dropbear_scp_execvp +dropbear/src/scp.c_CFLAGS += -include port/nuttx_scp.h +dropbear/src/scp.c_CFLAGS += -Wno-strict-prototypes + +dropbear/src/scpmisc.c_CFLAGS += ${DEFINE_PREFIX}xmalloc=dropbear_scp_xmalloc +dropbear/src/scpmisc.c_CFLAGS += ${DEFINE_PREFIX}xrealloc=dropbear_scp_xrealloc +dropbear/src/scpmisc.c_CFLAGS += ${DEFINE_PREFIX}xfree=dropbear_scp_xfree +endif + +$(DROPBEAR_ZIP): + @echo "Downloading: $(DROPBEAR_ZIP)" + $(Q) curl -L -o $(DROPBEAR_ZIP) $(DROPBEAR_URL)/$(DROPBEAR_ZIP) + +$(DROPBEAR_UNPACKNAME): $(DROPBEAR_ZIP) + @echo "Unpacking: $(DROPBEAR_ZIP) -> $(DROPBEAR_UNPACKNAME)" + $(Q) $(UNPACK) $(DROPBEAR_ZIP) + $(Q) rm -rf $(DROPBEAR_UNPACKNAME) + $(Q) mv dropbear-$(DROPBEAR_COMMIT) $(DROPBEAR_UNPACKNAME) + @echo "Patching $(DROPBEAR_UNPACKNAME)" + $(Q) $(PATCH) -s -N -l -p1 -d $(DROPBEAR_UNPACKNAME) -i $(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)patch$(DELIM)0001-guard-platform-declarations.patch; true + $(Q) $(PATCH) -s -N -l -p1 -d $(DROPBEAR_UNPACKNAME) -i $(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)patch$(DELIM)0002-use-nuttx-passwd-auth.patch; true + $(Q) $(PATCH) -s -N -l -p1 -d $(DROPBEAR_UNPACKNAME) -i $(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)patch$(DELIM)0003-allow-localoptions-to-override-tracking-malloc.patch; true + $(Q) $(PATCH) -s -N -l -p1 -d $(DROPBEAR_UNPACKNAME) -i $(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)patch$(DELIM)0004-use-nuttx-unused-macro.patch; true + @echo "Generating default_options_guard.h" + $(Q) sh $(DROPBEAR_UNPACKNAME)$(DELIM)src$(DELIM)ifndef_wrapper.sh \ + < $(DROPBEAR_UNPACKNAME)$(DELIM)src$(DELIM)default_options.h \ + > $(DROPBEAR_UNPACKNAME)$(DELIM)src$(DELIM)default_options_guard.h + $(Q) touch $(DROPBEAR_UNPACKNAME) + +ifeq ($(wildcard $(DROPBEAR_UNPACKNAME)/.git),) +context:: $(DROPBEAR_UNPACKNAME) + +clean:: + $(call DELFILE, port$(DELIM)*.o) + +distclean:: + $(call DELDIR, $(DROPBEAR_UNPACKNAME)) + $(call DELFILE, $(DROPBEAR_ZIP)) +endif + +endif + +include $(APPDIR)/Application.mk diff --git a/netutils/dropbear/dropbear_main.c b/netutils/dropbear/dropbear_main.c new file mode 100644 index 00000000000..798ba2b554f --- /dev/null +++ b/netutils/dropbear/dropbear_main.c @@ -0,0 +1,329 @@ +/**************************************************************************** + * apps/netutils/dropbear/dropbear_main.c + * + * SPDX-License-Identifier: Apache-2.0 + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include "includes.h" +#include "algo.h" +#include "crypto_desc.h" +#define dropbear_main dropbear_multi_entry +#include "dbutil.h" +#undef dropbear_main +#include "dbrandom.h" +#include "netio.h" +#include "runopts.h" +#include "session.h" +#include "signkey.h" +#include "gensignkey.h" +#include "ssh.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define DROPBEAR_PORT_STRING_HELPER(n) #n +#define DROPBEAR_PORT_STRING(n) DROPBEAR_PORT_STRING_HELPER(n) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +typedef void (*dropbear_exit_handler_t)(int exitcode, FAR const char *format, + va_list param) ATTRIB_NORETURN; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static jmp_buf g_session_exit_jmp; +static int g_session_exitcode; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void dropbear_session_exit(int exitcode, FAR const char *format, + va_list param) noreturn_function; +static void dropbear_session_exit(int exitcode, FAR const char *format, + va_list param) +{ + char exitmsg[150]; + char fullmsg[300]; + char fromaddr[60]; + int signal_pipe[2]; + + vsnprintf(exitmsg, sizeof(exitmsg), format, param); + + fromaddr[0] = '\0'; + + if (svr_ses.addrstring != NULL) + { + snprintf(fromaddr, sizeof(fromaddr), " from <%s>", svr_ses.addrstring); + } + + if (!ses.init_done) + { + snprintf(fullmsg, sizeof(fullmsg), "Early exit%s: %s", fromaddr, + exitmsg); + } + else if (ses.authstate.authdone) + { + snprintf(fullmsg, sizeof(fullmsg), "Exit (%s)%s: %s", + ses.authstate.pw_name, fromaddr, exitmsg); + } + else if (ses.authstate.pw_name != NULL) + { + snprintf(fullmsg, sizeof(fullmsg), + "Exit before auth%s: (user '%s', %u fails): %s", + fromaddr, ses.authstate.pw_name, ses.authstate.failcount, + exitmsg); + } + else + { + snprintf(fullmsg, sizeof(fullmsg), "Exit before auth%s: %s", fromaddr, + exitmsg); + } + + dropbear_log(LOG_INFO, "%s", fullmsg); + + signal_pipe[0] = ses.signal_pipe[0]; + signal_pipe[1] = ses.signal_pipe[1]; + session_cleanup(); + + if (signal_pipe[0] > STDERR_FILENO) + { + m_close(signal_pipe[0]); + } + + if (signal_pipe[1] > STDERR_FILENO && signal_pipe[1] != signal_pipe[0]) + { + m_close(signal_pipe[1]); + } + + memset(&ses, 0, sizeof(ses)); + memset(&svr_ses, 0, sizeof(svr_ses)); + + g_session_exitcode = exitcode; + longjmp(g_session_exit_jmp, 1); + + while (1) + { + } +} + +static void dropbear_setup(FAR const char *port) +{ + /* Load the host key with -r so it lives in the persistent (epoch 0) + * allocation rather than being created lazily inside a per-session malloc + * epoch (-R), which would free it when the first session ends and leave a + * dangling svr_opts.hostkey for the next connection. + */ + + FAR char *argv[] = + { + "dropbear", + "-F", + "-p", + (FAR char *)port, + "-r", + CONFIG_NETUTILS_DROPBEAR_HOSTKEY_PATH, + NULL + }; + + _dropbear_exit = svr_dropbear_exit; + _dropbear_log = svr_dropbear_log; + + disallow_core(); + svr_getopts(6, argv); + seedrandom(); + crypto_init(); + +#ifdef CONFIG_NETUTILS_DROPBEAR_GENERATE_HOSTKEY + /* Generate the ECDSA host key now if it is missing. signkey_generate() + * with skip_exist=1 silently succeeds when the file already exists. + */ + + if (signkey_generate(DROPBEAR_SIGNKEY_ECDSA_NISTP256, 0, + CONFIG_NETUTILS_DROPBEAR_HOSTKEY_PATH, 1) + == DROPBEAR_FAILURE) + { + dropbear_exit("failed to generate host key"); + } +#endif + + load_all_hostkeys(); + + if (dropbear_auth_initialize() < 0) + { + dropbear_exit("failed to initialize password auth"); + } +} + +static void dropbear_run_session(int childsock) +{ + dropbear_exit_handler_t saved_exit; + + saved_exit = _dropbear_exit; + _dropbear_exit = dropbear_session_exit; + + m_malloc_set_epoch(1); + + if (setjmp(g_session_exit_jmp) == 0) + { + svr_session(childsock, -1); + } + + m_malloc_free_epoch(1, 1); + m_malloc_set_epoch(0); + + _dropbear_exit = saved_exit; + + if (g_session_exitcode != EXIT_SUCCESS) + { + dropbear_log(LOG_WARNING, "session exited with status %d", + g_session_exitcode); + } +} + +static size_t dropbear_listen_sockets(FAR int *socks, size_t sockcount, + FAR int *maxfd) +{ + FAR char *errstring = NULL; + size_t sockpos = 0; + unsigned int i; + + for (i = 0; i < svr_opts.portcount; i++) + { + int nsock; + unsigned int n; + + nsock = dropbear_listen(svr_opts.addresses[i], svr_opts.ports[i], + &socks[sockpos], sockcount - sockpos, + &errstring, maxfd, svr_opts.interface); + + if (nsock < 0) + { + dropbear_log(LOG_WARNING, "failed listening on '%s': %s", + svr_opts.ports[i], + errstring != NULL ? errstring : "unknown error"); + m_free(errstring); + errstring = NULL; + continue; + } + + for (n = 0; n < (unsigned int)nsock; n++) + { + set_sock_priority(socks[sockpos + n], DROPBEAR_PRIO_LOWDELAY); + } + + sockpos += (size_t)nsock; + } + + return sockpos; +} + +static size_t dropbear_wait_listen_sockets(FAR int *socks, size_t sockcount, + FAR int *maxfd) +{ + int retries = 0; + int retry_delay = 1; + + while (1) + { + size_t count; + + *maxfd = -1; + count = dropbear_listen_sockets(socks, sockcount, maxfd); + + if (count > 0) + { + return count; + } + +#if CONFIG_NETUTILS_DROPBEAR_LISTEN_RETRIES > 0 + if (retries++ >= CONFIG_NETUTILS_DROPBEAR_LISTEN_RETRIES) + { + dropbear_exit("no listening ports available"); + } +#else + retries++; +#endif + + dropbear_log(LOG_WARNING, "Retry %d in %d seconds...", retries, + retry_delay); + sleep(retry_delay); + + retry_delay *= 2; + + if (retry_delay > CONFIG_NETUTILS_DROPBEAR_LISTEN_RETRY_MAX) + { + retry_delay = CONFIG_NETUTILS_DROPBEAR_LISTEN_RETRY_MAX; + } + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + FAR const char *port = DROPBEAR_PORT_STRING(CONFIG_NETUTILS_DROPBEAR_PORT); + int listensocks[MAX_LISTEN_ADDR]; + int maxfd = -1; + + if (argc > 1) + { + port = argv[1]; + } + + dropbear_setup(port); + + dropbear_wait_listen_sockets(listensocks, MAX_LISTEN_ADDR, &maxfd); + + printf("dropbear: listening on port %s\n", port); + + while (1) + { + struct sockaddr_storage remoteaddr; + socklen_t remoteaddrlen = sizeof(remoteaddr); + FAR char *remote_host = NULL; + FAR char *remote_port = NULL; + int childsock; + + childsock = accept(listensocks[0], (FAR struct sockaddr *)&remoteaddr, + &remoteaddrlen); + + if (childsock < 0) + { + continue; + } + + getaddrstring(&remoteaddr, &remote_host, &remote_port, 0); + dropbear_log(LOG_INFO, "connection from %s:%s", + remote_host != NULL ? remote_host : "?", + remote_port != NULL ? remote_port : "?"); + m_free(remote_host); + m_free(remote_port); + + seedrandom(); + dropbear_run_session(childsock); + close(childsock); + } + + return EXIT_SUCCESS; +} diff --git a/netutils/dropbear/dropbear_nshsession.c b/netutils/dropbear/dropbear_nshsession.c new file mode 100644 index 00000000000..0af7e68ead6 --- /dev/null +++ b/netutils/dropbear/dropbear_nshsession.c @@ -0,0 +1,829 @@ +/**************************************************************************** + * apps/netutils/dropbear/dropbear_nshsession.c + * + * SPDX-License-Identifier: Apache-2.0 + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "includes.h" +#include "channel.h" +#include "chansession.h" +#include "dbutil.h" +#include "session.h" + +#include "nsh.h" +#include "nsh_console.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct dropbear_nshsession_s +{ + int pty_readfd; + int pty_writefd; + pid_t nsh_pid; + volatile bool done; + pthread_t waiter; + bool waiter_started; + bool have_winsize; + bool has_pty; + struct winsize win; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int dropbear_nsh_main(int argc, FAR char *argv[]) +{ + FAR struct console_stdio_s *pstate; + int ret; + + pstate = nsh_newconsole(true); + if (pstate == NULL) + { + dropbear_log(LOG_WARNING, "failed to create NSH console"); + return -ENOMEM; + } + + ret = nsh_session(pstate, NSH_LOGIN_NONE, argc, argv); + dropbear_log(LOG_INFO, "NSH session exited: %d", ret); + + nsh_exit(&pstate->cn_vtbl, ret); + return ret; +} + +static FAR void *dropbear_nsh_waiter(FAR void *arg) +{ + FAR struct dropbear_nshsession_s *sess = arg; + unsigned char ch = 0; + int status; + int ret; + + ret = waitpid(sess->nsh_pid, &status, 0); + if (ret < 0) + { + dropbear_log(LOG_WARNING, "NSH session wait failed: %s", + strerror(errno)); + } + + sess->nsh_pid = -1; + sess->done = true; + + /* Match Dropbear's SIGCHLD wakeup path. The session loop checks channel + * close conditions when this pipe becomes readable. + */ + + if (ses.signal_pipe[1] >= 0) + { + write(ses.signal_pipe[1], &ch, sizeof(ch)); + } + + return NULL; +} + +static int dropbear_newchansess(FAR struct dropbear_channel *channel) +{ + FAR struct dropbear_nshsession_s *sess; + + sess = m_malloc(sizeof(*sess)); + memset(sess, 0, sizeof(*sess)); + sess->pty_readfd = -1; + sess->pty_writefd = -1; + sess->nsh_pid = -1; + + channel->typedata = sess; + channel->prio = DROPBEAR_PRIO_LOWDELAY; + return 0; +} + +static int dropbear_sesscheckclose(FAR struct dropbear_channel *channel) +{ + FAR struct dropbear_nshsession_s *sess = channel->typedata; + + return sess != NULL && sess->done; +} + +static int dropbear_setup_spawn_attrs(FAR posix_spawnattr_t *attr, + FAR const char **errmsg) +{ + struct sched_param param; + int rc; + + param.sched_priority = CONFIG_NETUTILS_DROPBEAR_SHELL_PRIORITY; + *errmsg = "spawn priority setup"; + rc = posix_spawnattr_setschedparam(attr, ¶m); + if (rc != 0) + { + return rc; + } + + *errmsg = "spawn stack setup"; + rc = posix_spawnattr_setstacksize( + attr, CONFIG_NETUTILS_DROPBEAR_SHELL_STACKSIZE); + if (rc != 0) + { + return rc; + } + + *errmsg = "spawn flags setup"; + return posix_spawnattr_setflags(attr, POSIX_SPAWN_SETSCHEDPARAM); +} + +static int +dropbear_setup_spawn_stdio(FAR posix_spawn_file_actions_t *actions, + int slavefd) +{ + static const int stdio_fds[] = + { + STDIN_FILENO, + STDOUT_FILENO, + STDERR_FILENO + }; + + int i; + int rc; + + for (i = 0; i < nitems(stdio_fds); i++) + { + rc = posix_spawn_file_actions_adddup2(actions, slavefd, stdio_fds[i]); + if (rc != 0) + { + return rc; + } + } + + return 0; +} + +#ifdef CONFIG_NETUTILS_DROPBEAR_SCP +static int +dropbear_setup_spawn_stdio3(FAR posix_spawn_file_actions_t *actions, + int stdinfd, int stdoutfd, int stderrfd) +{ + int rc; + + rc = posix_spawn_file_actions_adddup2(actions, stdinfd, STDIN_FILENO); + if (rc != 0) + { + return rc; + } + + rc = posix_spawn_file_actions_adddup2(actions, stdoutfd, STDOUT_FILENO); + if (rc != 0) + { + return rc; + } + + return posix_spawn_file_actions_adddup2(actions, stderrfd, STDERR_FILENO); +} +#endif /* CONFIG_NETUTILS_DROPBEAR_SCP */ + +static int +dropbear_setup_spawn_close(FAR posix_spawn_file_actions_t *actions, + int fd) +{ + if (fd <= STDERR_FILENO) + { + return 0; + } + + return posix_spawn_file_actions_addclose(actions, fd); +} + +static void dropbear_close_fd(FAR int *fd) +{ + if (*fd >= 0) + { + close(*fd); + *fd = -1; + } +} + +static int dropbear_start_nsh(FAR struct dropbear_channel *channel, + FAR struct dropbear_nshsession_s *sess) +{ + posix_spawn_file_actions_t actions; + posix_spawnattr_t attr; + FAR const char *errmsg; + int masterfd; + int slavefd; + int writefd; + int rc; + + if (sess->nsh_pid >= 0) + { + dropbear_log(LOG_WARNING, "NSH session already running"); + return DROPBEAR_FAILURE; + } + + rc = openpty(&masterfd, &slavefd, NULL, NULL, + sess->have_winsize ? &sess->win : NULL); + if (rc < 0) + { + dropbear_log(LOG_WARNING, "openpty failed: %s", strerror(errno)); + return DROPBEAR_FAILURE; + } + + writefd = dup(masterfd); + if (writefd < 0) + { + dropbear_log(LOG_WARNING, "pty dup failed: %s", strerror(errno)); + close(masterfd); + close(slavefd); + return DROPBEAR_FAILURE; + } + + rc = posix_spawn_file_actions_init(&actions); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn actions init failed: %s", + strerror(rc)); + close(masterfd); + close(writefd); + close(slavefd); + return DROPBEAR_FAILURE; + } + + rc = posix_spawnattr_init(&attr); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn attr init failed: %s", strerror(rc)); + goto err_with_actions; + } + + rc = dropbear_setup_spawn_attrs(&attr, &errmsg); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "%s failed: %s", errmsg, strerror(rc)); + goto err_with_attr; + } + + rc = dropbear_setup_spawn_stdio(&actions, slavefd); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn stdio setup failed: %s", + strerror(rc)); + goto err_with_attr; + } + + rc = dropbear_setup_spawn_close(&actions, masterfd); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn master close setup failed: %s", + strerror(rc)); + goto err_with_attr; + } + + rc = dropbear_setup_spawn_close(&actions, writefd); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn write close setup failed: %s", + strerror(rc)); + goto err_with_attr; + } + + rc = dropbear_setup_spawn_close(&actions, slavefd); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn slave close setup failed: %s", + strerror(rc)); + goto err_with_attr; + } + + sess->nsh_pid = task_spawn("dropbear nsh", dropbear_nsh_main, &actions, + &attr, NULL, NULL); + if (sess->nsh_pid < 0) + { + dropbear_log(LOG_WARNING, "failed to create NSH task: %s", + strerror(-sess->nsh_pid)); + goto err_with_attr; + } + + close(slavefd); + slavefd = -1; + + rc = pthread_create(&sess->waiter, NULL, dropbear_nsh_waiter, sess); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "failed to create NSH waiter: %s", + strerror(rc)); + close(masterfd); + close(writefd); + masterfd = -1; + writefd = -1; + kill(sess->nsh_pid, SIGTERM); + waitpid(sess->nsh_pid, NULL, 0); + sess->nsh_pid = -1; + goto err_with_attr; + } + + sess->waiter_started = true; + sess->has_pty = true; + sess->pty_readfd = masterfd; + sess->pty_writefd = writefd; + + channel->readfd = masterfd; + channel->writefd = writefd; + channel->bidir_fd = 0; + + setnonblocking(channel->readfd); + setnonblocking(channel->writefd); + ses.maxfd = MAX(ses.maxfd, channel->readfd); + ses.maxfd = MAX(ses.maxfd, channel->writefd); + + posix_spawnattr_destroy(&attr); + posix_spawn_file_actions_destroy(&actions); + + dropbear_log(LOG_INFO, "NSH PTY session started"); + return DROPBEAR_SUCCESS; + +err_with_attr: + posix_spawnattr_destroy(&attr); + +err_with_actions: + posix_spawn_file_actions_destroy(&actions); + + if (masterfd >= 0) + { + close(masterfd); + } + + if (writefd >= 0) + { + close(writefd); + } + + if (slavefd >= 0) + { + close(slavefd); + } + + sess->nsh_pid = -1; + return DROPBEAR_FAILURE; +} + +#ifdef CONFIG_NETUTILS_DROPBEAR_SCP +static int dropbear_start_exec(FAR struct dropbear_channel *channel, + FAR struct dropbear_nshsession_s *sess, + FAR char *cmd) +{ + posix_spawn_file_actions_t actions; + posix_spawnattr_t attr; + FAR const char *errmsg; + FAR char * const argv[] = + { + "-c", + cmd, + NULL + }; + + int inpipe[2] = + { + -1, + -1 + }; + + int outpipe[2] = + { + -1, + -1 + }; + + int errpipe[2] = + { + -1, + -1 + }; + + int rc; + + if (sess->nsh_pid >= 0) + { + dropbear_log(LOG_WARNING, "NSH exec already running"); + return DROPBEAR_FAILURE; + } + + if (pipe(inpipe) < 0 || pipe(outpipe) < 0 || pipe(errpipe) < 0) + { + dropbear_log(LOG_WARNING, "exec pipe setup failed: %s", + strerror(errno)); + goto err_with_pipes; + } + + rc = posix_spawn_file_actions_init(&actions); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn actions init failed: %s", + strerror(rc)); + goto err_with_pipes; + } + + rc = posix_spawnattr_init(&attr); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn attr init failed: %s", strerror(rc)); + goto err_with_actions; + } + + rc = dropbear_setup_spawn_attrs(&attr, &errmsg); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "%s failed: %s", errmsg, strerror(rc)); + goto err_with_attr; + } + + rc = dropbear_setup_spawn_stdio3(&actions, inpipe[0], outpipe[1], + errpipe[1]); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn stdio setup failed: %s", + strerror(rc)); + goto err_with_attr; + } + + rc = dropbear_setup_spawn_close(&actions, inpipe[0]); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn stdin close setup failed: %s", + strerror(rc)); + goto err_with_attr; + } + + rc = dropbear_setup_spawn_close(&actions, inpipe[1]); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn stdin writer close setup failed: %s", + strerror(rc)); + goto err_with_attr; + } + + rc = dropbear_setup_spawn_close(&actions, outpipe[0]); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn stdout reader close setup failed: %s", + strerror(rc)); + goto err_with_attr; + } + + rc = dropbear_setup_spawn_close(&actions, outpipe[1]); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn stdout close setup failed: %s", + strerror(rc)); + goto err_with_attr; + } + + rc = dropbear_setup_spawn_close(&actions, errpipe[0]); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn stderr reader close setup failed: %s", + strerror(rc)); + goto err_with_attr; + } + + rc = dropbear_setup_spawn_close(&actions, errpipe[1]); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "spawn stderr close setup failed: %s", + strerror(rc)); + goto err_with_attr; + } + + sess->nsh_pid = task_spawn("dropbear exec", dropbear_nsh_main, &actions, + &attr, argv, NULL); + if (sess->nsh_pid < 0) + { + dropbear_log(LOG_WARNING, "failed to create NSH exec task: %s", + strerror(-sess->nsh_pid)); + goto err_with_attr; + } + + dropbear_close_fd(&inpipe[0]); + dropbear_close_fd(&outpipe[1]); + dropbear_close_fd(&errpipe[1]); + + rc = pthread_create(&sess->waiter, NULL, dropbear_nsh_waiter, sess); + if (rc != 0) + { + dropbear_log(LOG_WARNING, "failed to create NSH exec waiter: %s", + strerror(rc)); + dropbear_close_fd(&inpipe[1]); + dropbear_close_fd(&outpipe[0]); + dropbear_close_fd(&errpipe[0]); + kill(sess->nsh_pid, SIGTERM); + waitpid(sess->nsh_pid, NULL, 0); + sess->nsh_pid = -1; + goto err_with_attr; + } + + sess->waiter_started = true; + sess->has_pty = false; + sess->pty_readfd = -1; + sess->pty_writefd = -1; + + channel->readfd = outpipe[0]; + channel->writefd = inpipe[1]; + channel->errfd = errpipe[0]; + channel->bidir_fd = 0; + + setnonblocking(channel->readfd); + setnonblocking(channel->writefd); + setnonblocking(channel->errfd); + ses.maxfd = MAX(ses.maxfd, channel->readfd); + ses.maxfd = MAX(ses.maxfd, channel->writefd); + ses.maxfd = MAX(ses.maxfd, channel->errfd); + + posix_spawnattr_destroy(&attr); + posix_spawn_file_actions_destroy(&actions); + + dropbear_log(LOG_INFO, "NSH exec started: %s", cmd); + return DROPBEAR_SUCCESS; + +err_with_attr: + posix_spawnattr_destroy(&attr); + +err_with_actions: + posix_spawn_file_actions_destroy(&actions); + +err_with_pipes: + dropbear_close_fd(&inpipe[0]); + dropbear_close_fd(&inpipe[1]); + dropbear_close_fd(&outpipe[0]); + dropbear_close_fd(&outpipe[1]); + dropbear_close_fd(&errpipe[0]); + dropbear_close_fd(&errpipe[1]); + sess->nsh_pid = -1; + return DROPBEAR_FAILURE; +} +#endif /* CONFIG_NETUTILS_DROPBEAR_SCP */ + +static void dropbear_parse_winsize(FAR struct dropbear_nshsession_s *sess) +{ + unsigned int cols = buf_getint(ses.payload); + unsigned int rows = buf_getint(ses.payload); + unsigned int width = buf_getint(ses.payload); + unsigned int height = buf_getint(ses.payload); + + sess->win.ws_col = cols; + sess->win.ws_row = rows; + sess->win.ws_xpixel = width; + sess->win.ws_ypixel = height; + sess->have_winsize = true; + + if (sess->pty_readfd >= 0) + { + ioctl(sess->pty_readfd, TIOCSWINSZ, (unsigned long)&sess->win); + } +} + +static int dropbear_handle_pty_req(FAR struct dropbear_nshsession_s *sess) +{ + unsigned int len; + FAR char *term; + FAR char *modes; + + term = buf_getstring(ses.payload, &len); + dropbear_parse_winsize(sess); + modes = buf_getstring(ses.payload, &len); + + m_free(term); + m_free(modes); + return DROPBEAR_SUCCESS; +} + +static int dropbear_signal_from_name(FAR const char *name) +{ + int i; + + for (i = 0; signames[i].name != NULL; i++) + { + if (strcmp(name, signames[i].name) == 0) + { + return signames[i].signal; + } + } + + return -EINVAL; +} + +static int dropbear_write_terminal_signal( + FAR struct dropbear_nshsession_s *sess, + int signo) +{ +#ifdef CONFIG_TTY_SIGINT + unsigned char ch; + ssize_t nwritten; + + if (signo == SIGINT && sess->has_pty && sess->pty_writefd >= 0) + { + ch = CONFIG_TTY_SIGINT_CHAR; + nwritten = write(sess->pty_writefd, &ch, sizeof(ch)); + if (nwritten == sizeof(ch)) + { + return DROPBEAR_SUCCESS; + } + + dropbear_log(LOG_WARNING, "SSH terminal INT failed: %s", + strerror(errno)); + return DROPBEAR_FAILURE; + } +#endif + + return DROPBEAR_FAILURE; +} + +static int dropbear_handle_signal(FAR struct dropbear_nshsession_s *sess) +{ + unsigned int len; + FAR char *name; + int signo; + int ret; + + name = buf_getstring(ses.payload, &len); + signo = dropbear_signal_from_name(name); + if (signo < 0) + { + dropbear_log(LOG_WARNING, "unsupported SSH signal '%s'", name); + m_free(name); + return DROPBEAR_FAILURE; + } + + ret = dropbear_write_terminal_signal(sess, signo); + if (ret == DROPBEAR_SUCCESS) + { + dropbear_log(LOG_INFO, "SSH signal '%s' sent to PTY", name); + m_free(name); + return DROPBEAR_SUCCESS; + } + + if (sess->nsh_pid <= 0) + { + dropbear_log(LOG_WARNING, "SSH signal '%s' has no NSH session", name); + m_free(name); + return DROPBEAR_FAILURE; + } + + ret = kill(sess->nsh_pid, signo); + if (ret < 0) + { + dropbear_log(LOG_WARNING, "SSH signal '%s' failed: %s", + name, strerror(errno)); + m_free(name); + return DROPBEAR_FAILURE; + } + + dropbear_log(LOG_INFO, "SSH signal '%s' sent to NSH session", name); + m_free(name); + return DROPBEAR_SUCCESS; +} + +static void dropbear_chansessionrequest(FAR struct dropbear_channel *channel) +{ + unsigned int typelen; + FAR char *type = buf_getstring(ses.payload, &typelen); + unsigned char wantreply = buf_getbool(ses.payload); + FAR struct dropbear_nshsession_s *sess = channel->typedata; + int ret = DROPBEAR_FAILURE; + + TRACE(("dropbear_chansessionrequest: type='%s'", type)) + + if (strcmp(type, "pty-req") == 0) + { + ret = dropbear_handle_pty_req(sess); + } + else if (strcmp(type, "shell") == 0) + { + ret = dropbear_start_nsh(channel, sess); + } + else if (strcmp(type, "exec") == 0) + { +#ifdef CONFIG_NETUTILS_DROPBEAR_SCP + unsigned int cmdlen; + FAR char *cmd; + + cmd = buf_getstring(ses.payload, &cmdlen); + ret = dropbear_start_exec(channel, sess, cmd); + m_free(cmd); +#else + dropbear_log(LOG_WARNING, "SSH exec requests are not supported"); +#endif + } + else if (strcmp(type, "window-change") == 0) + { + dropbear_parse_winsize(sess); + ret = DROPBEAR_SUCCESS; + } + else if (strcmp(type, "signal") == 0) + { + ret = dropbear_handle_signal(sess); + } + else if (strcmp(type, "break") == 0) + { + ret = DROPBEAR_SUCCESS; + } + else + { + TRACE(("dropbear_chansessionrequest: unhandled type '%s'", type)) + } + + if (wantreply) + { + if (ret == DROPBEAR_SUCCESS) + { + send_msg_channel_success(channel); + } + else + { + send_msg_channel_failure(channel); + } + } + + m_free(type); +} + +static void +dropbear_closechansess(FAR const struct dropbear_channel *channel) +{ + FAR struct dropbear_nshsession_s *sess = channel->typedata; + + if (sess != NULL) + { + sess->done = true; + } +} + +static void +dropbear_cleanupchansess(FAR const struct dropbear_channel *channel) +{ + FAR struct dropbear_nshsession_s *sess = channel->typedata; + + if (sess == NULL) + { + return; + } + + sess->done = true; + + if (sess->nsh_pid > 0) + { + kill(sess->nsh_pid, SIGTERM); + } + + if (sess->waiter_started) + { + pthread_join(sess->waiter, NULL); + sess->waiter_started = false; + } + + m_free(sess); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/* Chansession lifecycle hooks invoked from svr-session.c. Upstream + * svr-chansession.c uses these to set up and reap the global childpids[] / + * SIGCHLD machinery; the NSH bridge tracks each session's child through its + * own waiter thread (dropbear_nsh_waiter), so both are no-ops here. + */ + +void svr_chansessinitialise(void) +{ +} + +void svr_chansess_checksignal(void) +{ +} + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +const struct dropbear_chantype svrchansess = +{ + "session", + dropbear_newchansess, + dropbear_sesscheckclose, + dropbear_chansessionrequest, + dropbear_closechansess, + dropbear_cleanupchansess, +}; diff --git a/netutils/dropbear/patch/0001-guard-platform-declarations.patch b/netutils/dropbear/patch/0001-guard-platform-declarations.patch new file mode 100644 index 00000000000..7eb1dfcd23a --- /dev/null +++ b/netutils/dropbear/patch/0001-guard-platform-declarations.patch @@ -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); diff --git a/netutils/dropbear/patch/0002-use-nuttx-passwd-auth.patch b/netutils/dropbear/patch/0002-use-nuttx-passwd-auth.patch new file mode 100644 index 00000000000..5e71d73daba --- /dev/null +++ b/netutils/dropbear/patch/0002-use-nuttx-passwd-auth.patch @@ -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 diff --git a/netutils/dropbear/patch/0003-allow-localoptions-to-override-tracking-malloc.patch b/netutils/dropbear/patch/0003-allow-localoptions-to-override-tracking-malloc.patch new file mode 100644 index 00000000000..b1dc3e52c29 --- /dev/null +++ b/netutils/dropbear/patch/0003-allow-localoptions-to-override-tracking-malloc.patch @@ -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) diff --git a/netutils/dropbear/patch/0004-use-nuttx-unused-macro.patch b/netutils/dropbear/patch/0004-use-nuttx-unused-macro.patch new file mode 100644 index 00000000000..2ea58805739 --- /dev/null +++ b/netutils/dropbear/patch/0004-use-nuttx-unused-macro.patch @@ -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")) diff --git a/netutils/dropbear/port/config.h b/netutils/dropbear/port/config.h new file mode 100644 index 00000000000..0f5355fd7d9 --- /dev/null +++ b/netutils/dropbear/port/config.h @@ -0,0 +1,20 @@ +/**************************************************************************** + * apps/netutils/dropbear/port/config.h + * + * SPDX-License-Identifier: Apache-2.0 + ****************************************************************************/ + +#ifndef __APPS_NETUTILS_DROPBEAR_PORT_CONFIG_H +#define __APPS_NETUTILS_DROPBEAR_PORT_CONFIG_H + +/* Dropbear looks for a file named config.h. Keep this wrapper name stable; + * NuttX-specific autoconf replacements live in nuttx_config.h. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "nuttx_config.h" + +#endif /* __APPS_NETUTILS_DROPBEAR_PORT_CONFIG_H */ diff --git a/netutils/dropbear/port/dropbear_chachapoly.c b/netutils/dropbear/port/dropbear_chachapoly.c new file mode 100644 index 00000000000..f670d14ca72 --- /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 +}; diff --git a/netutils/dropbear/port/dropbear_ltc_hmac_sha256.c b/netutils/dropbear/port/dropbear_ltc_hmac_sha256.c new file mode 100644 index 00000000000..dda511d5f86 --- /dev/null +++ b/netutils/dropbear/port/dropbear_ltc_hmac_sha256.c @@ -0,0 +1,255 @@ +/**************************************************************************** + * apps/netutils/dropbear/port/dropbear_ltc_hmac_sha256.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. + * + ****************************************************************************/ + +/* LibTomCrypt-compatible incremental HMAC API backed by a + * CRYPTO_SHA2_256_HMAC /dev/crypto session (hmac-sha2-256, the only MAC the + * NuttX Dropbear configuration enables): init opens the session, process + * feeds data with COP_FLAG_UPDATE, done reads the tag and frees the session. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "includes.h" + +#include +#include +#include +#include +#include +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define DROPBEAR_HMAC_SHA256_DIGESTLEN 32 +#define DROPBEAR_HMAC_SHA256_BLOCKLEN 64 + +/* Stash the process-local /dev/crypto descriptor and session id in the + * unused md hash-state buffer so the upstream hmac_state needs no patch. + */ + +static_assert(sizeof(int) + sizeof(uint32_t) <= sizeof(hash_state), + "cryptodev state does not fit in hmac_state.md"); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int dropbear_hmac_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); + + if (fcntl(cfd, F_SETFD, FD_CLOEXEC) < 0) + { + close(cfd); + return -1; + } + + return cfd; +} + +static int dropbear_hmac_hash_is_sha256(int hash) +{ + int ret; + + ret = hash_is_valid(hash); + if (ret != CRYPT_OK) + { + return ret; + } + + if (hash_descriptor[hash].hashsize != DROPBEAR_HMAC_SHA256_DIGESTLEN || + hash_descriptor[hash].blocksize != DROPBEAR_HMAC_SHA256_BLOCKLEN) + { + return CRYPT_INVALID_HASH; + } + + return CRYPT_OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int hmac_init(hmac_state *hmac, int hash, const unsigned char *key, + unsigned long keylen) +{ + struct session_op session; + int cfd; + int ret; + + LTC_ARGCHK(hmac != NULL); + LTC_ARGCHK(key != NULL); + + if (keylen == 0 || keylen > INT_MAX) + { + return CRYPT_INVALID_KEYSIZE; + } + + ret = dropbear_hmac_hash_is_sha256(hash); + if (ret != CRYPT_OK) + { + return ret; + } + + zeromem(hmac, sizeof(*hmac)); + cfd = dropbear_hmac_cryptodev_open(); + if (cfd < 0) + { + return CRYPT_ERROR; + } + + memset(&session, 0, sizeof(session)); + session.mac = CRYPTO_SHA2_256_HMAC; + session.mackey = (caddr_t)key; + session.mackeylen = (int)keylen; + if (ioctl(cfd, CIOCGSESSION, &session) < 0) + { + close(cfd); + zeromem(hmac, sizeof(*hmac)); + return CRYPT_ERROR; + } + + memcpy(&hmac->md, &cfd, sizeof(cfd)); + memcpy((FAR uint8_t *)&hmac->md + sizeof(cfd), + &session.ses, sizeof(session.ses)); + hmac->hash = hash; + return CRYPT_OK; +} + +int hmac_process(hmac_state *hmac, const unsigned char *in, + unsigned long inlen) +{ + struct crypt_op cryp; + uint32_t ses; + int cfd; + int ret; + + LTC_ARGCHK(hmac != NULL); + LTC_ARGCHK(in != NULL || inlen == 0); + + ret = dropbear_hmac_hash_is_sha256(hmac->hash); + if (ret != CRYPT_OK) + { + return ret; + } + + if (inlen > UINT_MAX) + { + return CRYPT_OVERFLOW; + } + + memcpy(&cfd, &hmac->md, sizeof(cfd)); + memcpy(&ses, (FAR uint8_t *)&hmac->md + sizeof(cfd), sizeof(ses)); + if (cfd < 0) + { + return CRYPT_ERROR; + } + + memset(&cryp, 0, sizeof(cryp)); + cryp.ses = ses; + cryp.op = COP_ENCRYPT; + cryp.flags = COP_FLAG_UPDATE; + cryp.len = (unsigned int)inlen; + cryp.src = (caddr_t)in; + if (ioctl(cfd, CIOCCRYPT, &cryp) < 0) + { + return CRYPT_ERROR; + } + + return CRYPT_OK; +} + +int hmac_done(hmac_state *hmac, unsigned char *out, unsigned long *outlen) +{ + unsigned char digest[DROPBEAR_HMAC_SHA256_DIGESTLEN]; + struct crypt_op cryp; + unsigned long copylen; + uint32_t ses; + int cfd; + int ret = CRYPT_ERROR; + + LTC_ARGCHK(hmac != NULL); + LTC_ARGCHK(out != NULL); + LTC_ARGCHK(outlen != NULL); + + memcpy(&cfd, &hmac->md, sizeof(cfd)); + memcpy(&ses, (FAR uint8_t *)&hmac->md + sizeof(cfd), sizeof(ses)); + if (cfd < 0) + { + goto out; + } + + ret = dropbear_hmac_hash_is_sha256(hmac->hash); + if (ret != CRYPT_OK) + { + goto out; + } + + memset(&cryp, 0, sizeof(cryp)); + cryp.ses = ses; + cryp.op = COP_ENCRYPT; + cryp.len = 0; + cryp.src = (caddr_t)digest; + cryp.mac = (caddr_t)digest; + if (ioctl(cfd, CIOCCRYPT, &cryp) < 0) + { + ret = CRYPT_ERROR; + goto out; + } + + copylen = MIN(*outlen, DROPBEAR_HMAC_SHA256_DIGESTLEN); + memcpy(out, digest, copylen); + *outlen = copylen; + ret = CRYPT_OK; + +out: + if (cfd >= 0) + { + ioctl(cfd, CIOCFSESSION, &ses); + close(cfd); + } + + zeromem(digest, sizeof(digest)); + zeromem(hmac, sizeof(*hmac)); + return ret; +} diff --git a/netutils/dropbear/port/dropbear_utils.c b/netutils/dropbear/port/dropbear_utils.c new file mode 100644 index 00000000000..73619cbe750 --- /dev/null +++ b/netutils/dropbear/port/dropbear_utils.c @@ -0,0 +1,149 @@ +/**************************************************************************** + * apps/netutils/dropbear/port/dropbear_utils.c + * + * SPDX-License-Identifier: Apache-2.0 + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "dropbear_utils.h" + +#include +#include +#include +#include +#include + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int dropbear_hex_value(char ch) +{ + if (ch >= '0' && ch <= '9') + { + return ch - '0'; + } + + if (ch >= 'a' && ch <= 'f') + { + return ch - 'a' + 10; + } + + if (ch >= 'A' && ch <= 'F') + { + return ch - 'A' + 10; + } + + return -1; +} + +static int dropbear_try_mkdir(FAR const char *path) +{ + if (mkdir(path, 0700) < 0 && errno != EEXIST) + { + return -errno; + } + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void dropbear_hex_encode(FAR char *dst, FAR const uint8_t *src, + size_t srclen) +{ + static const char hex[] = "0123456789abcdef"; + size_t i; + + for (i = 0; i < srclen; i++) + { + dst[i * 2] = hex[src[i] >> 4]; + dst[i * 2 + 1] = hex[src[i] & 0x0f]; + } + + dst[srclen * 2] = '\0'; +} + +int dropbear_hex_decode(FAR const char *src, size_t srclen, + FAR uint8_t *dst, size_t dstlen) +{ + size_t i; + + if (srclen != dstlen * 2) + { + return -EINVAL; + } + + for (i = 0; i < dstlen; i++) + { + int hi = dropbear_hex_value(src[i * 2]); + int lo = dropbear_hex_value(src[i * 2 + 1]); + + if (hi < 0 || lo < 0) + { + return -EINVAL; + } + + dst[i] = (uint8_t)((hi << 4) | lo); + } + + return OK; +} + +int dropbear_try_prepare_parent(FAR const char *path) +{ + char dir[PATH_MAX]; + struct stat st; + FAR char *slash; + FAR char *p; + int ret; + + if (strlcpy(dir, path, sizeof(dir)) >= sizeof(dir)) + { + return -ENAMETOOLONG; + } + + slash = strrchr(dir, '/'); + if (slash == NULL || slash == dir) + { + return OK; + } + + *slash = '\0'; + if (stat(dir, &st) == 0) + { + return OK; + } + + for (p = dir + 1; *p != '\0'; p++) + { + if (*p == '/') + { + *p = '\0'; + ret = dropbear_try_mkdir(dir); + if (ret < 0) + { + return ret; + } + + *p = '/'; + } + } + + return dropbear_try_mkdir(dir); +} + +#ifndef CONFIG_SCHED_USER_IDENTITY +int dropbear_getgroups(int size, gid_t list[]) +{ + (void)size; + (void)list; + set_errno(ENOSYS); + return ERROR; +} +#endif diff --git a/netutils/dropbear/port/dropbear_utils.h b/netutils/dropbear/port/dropbear_utils.h new file mode 100644 index 00000000000..3e7d2722e7d --- /dev/null +++ b/netutils/dropbear/port/dropbear_utils.h @@ -0,0 +1,31 @@ +/**************************************************************************** + * apps/netutils/dropbear/port/dropbear_utils.h + * + * SPDX-License-Identifier: Apache-2.0 + ****************************************************************************/ + +#ifndef __APPS_NETUTILS_DROPBEAR_PORT_DROPBEAR_UTILS_H +#define __APPS_NETUTILS_DROPBEAR_PORT_DROPBEAR_UTILS_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "config.h" + +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +void dropbear_hex_encode(FAR char *dst, FAR const uint8_t *src, + size_t srclen); + +int dropbear_hex_decode(FAR const char *src, size_t srclen, + FAR uint8_t *dst, size_t dstlen); + +int dropbear_try_prepare_parent(FAR const char *path); + +#endif /* __APPS_NETUTILS_DROPBEAR_PORT_DROPBEAR_UTILS_H */ diff --git a/netutils/dropbear/port/localoptions.h b/netutils/dropbear/port/localoptions.h new file mode 100644 index 00000000000..28ac5a3b5b4 --- /dev/null +++ b/netutils/dropbear/port/localoptions.h @@ -0,0 +1,81 @@ +/**************************************************************************** + * apps/netutils/dropbear/port/localoptions.h + * + * SPDX-License-Identifier: Apache-2.0 + ****************************************************************************/ + +#ifndef __APPS_NETUTILS_DROPBEAR_PORT_LOCALOPTIONS_H +#define __APPS_NETUTILS_DROPBEAR_PORT_LOCALOPTIONS_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define DROPBEAR_TRACKING_MALLOC 1 + +/* Persist the native Dropbear ECDSA host key at the configured path. */ + +#define ECDSA_PRIV_FILENAME CONFIG_NETUTILS_DROPBEAR_HOSTKEY_PATH + +#define DROPBEAR_SVR_DROP_PRIVS 0 + +#ifdef CONFIG_SCHED_USER_IDENTITY +# define DROPBEAR_SVR_MULTIUSER 1 +#else +# define DROPBEAR_SVR_MULTIUSER 0 +#endif + +#define DROPBEAR_SVR_PASSWORD_AUTH 1 +#define DROPBEAR_SVR_PUBKEY_AUTH 0 +#define DROPBEAR_SVR_PUBKEY_OPTIONS 0 + +#define DROPBEAR_REEXEC 0 +#define DROPBEAR_SMALL_CODE 1 +#define DROPBEAR_USER_ALGO_LIST 0 + +#define DROPBEAR_X11FWD 0 +#define DROPBEAR_SVR_AGENTFWD 0 +#define DROPBEAR_SVR_LOCALTCPFWD 0 +#define DROPBEAR_SVR_REMOTETCPFWD 0 +#define DROPBEAR_SVR_LOCALSTREAMFWD 0 +#define DROPBEAR_SVR_REMOTESTREAMFWD 0 + +#define DROPBEAR_DSS 0 +#define DROPBEAR_RSA 0 +#define DROPBEAR_ECDSA 1 +#define DROPBEAR_ED25519 0 +#define DROPBEAR_SK_KEYS 0 +#define DROPBEAR_ECC_256 1 +#define DROPBEAR_ECC_384 0 +#define DROPBEAR_ECC_521 0 + +#define DROPBEAR_AES128 1 +#define DROPBEAR_AES256 0 +#define DROPBEAR_CHACHA20POLY1305 1 +#define DROPBEAR_ENABLE_CBC_MODE 0 +#define DROPBEAR_ENABLE_GCM_MODE 0 + +#define DROPBEAR_SHA1_HMAC 0 +#define DROPBEAR_SHA2_256_HMAC 1 +#define DROPBEAR_SHA2_512_HMAC 0 +#define DROPBEAR_SHA1_96_HMAC 0 + +#define DROPBEAR_CURVE25519 1 +#define DROPBEAR_DH_GROUP14_SHA1 0 +#define DROPBEAR_DH_GROUP14_SHA256 0 +#define DROPBEAR_DH_GROUP16 0 +#define DROPBEAR_DH_GROUP1 0 +#define DROPBEAR_ECDH 0 +#define DROPBEAR_SNTRUP761 0 +#define DROPBEAR_MLKEM768 0 + +#define DROPBEAR_DEFAULT_CLI_AUTHKEY "/etc/dropbear/authorized_keys" +#define DROPBEAR_SFTPSERVER 0 + +#endif /* __APPS_NETUTILS_DROPBEAR_PORT_LOCALOPTIONS_H */ diff --git a/netutils/dropbear/port/nuttx_auth.c b/netutils/dropbear/port/nuttx_auth.c new file mode 100644 index 00000000000..83becdcae91 --- /dev/null +++ b/netutils/dropbear/port/nuttx_auth.c @@ -0,0 +1,109 @@ +/**************************************************************************** + * apps/netutils/dropbear/port/nuttx_auth.c + * + * SPDX-License-Identifier: Apache-2.0 + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "fsutils/passwd.h" + +#include "dbutil.h" + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct passwd g_dropbear_pw; +static char g_dropbear_name[64]; +static char g_dropbear_dir[] = "/"; +static char g_dropbear_shell[] = "/bin/sh"; +static char g_dropbear_password_marker[] = "x"; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static FAR struct passwd *dropbear_fill_pw(FAR const char *name, uid_t uid) +{ + memset(&g_dropbear_pw, 0, sizeof(g_dropbear_pw)); + + snprintf(g_dropbear_name, sizeof(g_dropbear_name), "%s", name); + + g_dropbear_pw.pw_uid = uid; + g_dropbear_pw.pw_gid = 0; + g_dropbear_pw.pw_name = g_dropbear_name; + g_dropbear_pw.pw_passwd = g_dropbear_password_marker; + g_dropbear_pw.pw_gecos = g_dropbear_name; + g_dropbear_pw.pw_dir = g_dropbear_dir; + g_dropbear_pw.pw_shell = g_dropbear_shell; + + return &g_dropbear_pw; +} + +FAR struct passwd *dropbear_getpwuid(uid_t uid) +{ + return dropbear_fill_pw("root", uid); +} + +FAR struct passwd *dropbear_getpwnam(FAR const char *name) +{ + if (name == NULL || name[0] == '\0') + { + return NULL; + } + + return dropbear_fill_pw(name, 0); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +uid_t dropbear_getuid(void) +{ + return 0; +} + +uid_t dropbear_geteuid(void) +{ + return 0; +} + +int dropbear_auth_initialize(void) +{ + dropbear_log(LOG_INFO, "using NuttX passwd auth at %s", + CONFIG_FSUTILS_PASSWD_PATH); + return OK; +} + +int dropbear_verify_password(FAR const char *username, + FAR const char *password) +{ + int ret; + + ret = passwd_verify(username, password); + if (PASSWORD_VERIFY_MATCH(ret)) + { + return DROPBEAR_SUCCESS; + } + + if (PASSWORD_VERIFY_ERROR(ret) && ret != -ENOENT) + { + dropbear_log(LOG_WARNING, "passwd_verify failed for '%s': %d", + username, ret); + } + + return DROPBEAR_FAILURE; +} diff --git a/netutils/dropbear/port/nuttx_config.h b/netutils/dropbear/port/nuttx_config.h new file mode 100644 index 00000000000..d91709d1e90 --- /dev/null +++ b/netutils/dropbear/port/nuttx_config.h @@ -0,0 +1,142 @@ +/**************************************************************************** + * apps/netutils/dropbear/port/nuttx_config.h + * + * SPDX-License-Identifier: Apache-2.0 + ****************************************************************************/ + +#ifndef __APPS_NETUTILS_DROPBEAR_PORT_NUTTX_CONFIG_H +#define __APPS_NETUTILS_DROPBEAR_PORT_NUTTX_CONFIG_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define DROPBEAR_SERVER 1 +#define BUNDLED_LIBTOM 1 +#define DISABLE_LASTLOG 1 +#define DISABLE_PAM 1 +#define DISABLE_PUTUTLINE 1 +#define DISABLE_PUTUTXLINE 1 +#define DISABLE_UTMP 1 +#define DISABLE_UTMPX 1 +#define DISABLE_WTMP 1 +#define DISABLE_WTMPX 1 + +#ifndef CONFIG_NETUTILS_DROPBEAR_SYSLOG +# define DISABLE_SYSLOG 1 +#endif + +#ifndef CONFIG_NETUTILS_DROPBEAR_COMPRESSION +# define DISABLE_ZLIB 1 +#endif + +#define DROPBEAR_FUZZ 0 +#define DROPBEAR_PLUGIN 0 + +#define HAVE_BASENAME 1 +#define HAVE_CLOCK_GETTIME 1 +#define HAVE_CONST_GAI_STRERROR_PROTO 1 +#define HAVE_CRYPT 1 +#define HAVE_DECL_HTOLE64 1 +#define HAVE_ENDIAN_H 1 +#define HAVE_EXPLICIT_BZERO 1 +#define HAVE_FREEADDRINFO 1 +#define HAVE_GAI_STRERROR 1 +#define HAVE_GETADDRINFO 1 +#define HAVE_GETNAMEINFO 1 +#define HAVE_GETRANDOM 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_LIBGEN_H 1 +#define HAVE_NETDB_H 1 +#define HAVE_NETINET_IN_H 1 +#define HAVE_NETINET_TCP_H 1 +#define HAVE_PATHS_H 1 +#define HAVE_PUTENV 1 +#define HAVE_STATIC_ASSERT 1 +#define HAVE_STDINT_H 1 +#define HAVE_STDIO_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STRLCAT 1 +#define HAVE_STRLCPY 1 +#define HAVE_STRUCT_ADDRINFO 1 +#define HAVE_STRUCT_IN6_ADDR 1 +#define HAVE_STRUCT_SOCKADDR_IN6 1 +#define HAVE_STRUCT_SOCKADDR_STORAGE 1 +#define HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY 1 +#define HAVE_SYS_RANDOM_H 1 +#define HAVE_SYS_SELECT_H 1 +#define HAVE_SYS_SOCKET_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_SYS_UIO_H 1 +#define HAVE_SYS_WAIT_H 1 +#define HAVE_UINT16_T 1 +#define HAVE_UINT32_T 1 +#define HAVE_UINT8_T 1 +#define HAVE_U_INT16_T 1 +#define HAVE_U_INT32_T 1 +#define HAVE_U_INT8_T 1 +#define HAVE_UNDERSCORE_STATIC_ASSERT 1 +#define HAVE_UNISTD_H 1 +/* NuttX exposes writev(), but keep this port on Dropbear's simpler write() + * path until the SSH-to-NSH channel bridge is validated with vectored + * writes. + */ + +#undef HAVE_WRITEV +#define STDC_HEADERS 1 + +#define PACKAGE_BUGREPORT "" +#define PACKAGE_NAME "" +#define PACKAGE_STRING "" +#define PACKAGE_TARNAME "" +#define PACKAGE_URL "" +#define PACKAGE_VERSION "" + +#define SELECT_TYPE_ARG1 int +#define SELECT_TYPE_ARG234 (fd_set *) +#define SELECT_TYPE_ARG5 (struct timeval *) + +#ifndef PF_UNIX +# define PF_UNIX AF_UNIX +#endif + +#ifndef GRND_NONBLOCK +# define GRND_NONBLOCK O_NONBLOCK +#endif + +#define IPPORT_RESERVED 1024 + +#define getuid dropbear_getuid +#define geteuid dropbear_geteuid +#define getpwuid dropbear_getpwuid +#define getpwnam dropbear_getpwnam +uid_t getuid(void); +uid_t geteuid(void); +struct passwd *getpwuid(uid_t uid); +struct passwd *getpwnam(const char *name); +int dropbear_auth_initialize(void); +int dropbear_verify_password(const char *username, const char *password); + +#ifndef CONFIG_SCHED_USER_IDENTITY +/* NuttX has no supplementary-group support, so getgroups() does not exist. */ + +int dropbear_getgroups(int size, gid_t list[]); + +# define getgroups dropbear_getgroups +#endif + +#endif /* __APPS_NETUTILS_DROPBEAR_PORT_NUTTX_CONFIG_H */ diff --git a/netutils/dropbear/port/nuttx_scp.c b/netutils/dropbear/port/nuttx_scp.c new file mode 100644 index 00000000000..6d7606abe6d --- /dev/null +++ b/netutils/dropbear/port/nuttx_scp.c @@ -0,0 +1,49 @@ +/**************************************************************************** + * apps/netutils/dropbear/port/nuttx_scp.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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "nuttx_scp.h" + +#include +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/* scp only runs in server mode (-t/-f) on NuttX; execvp would only be + * reached in client mode to spawn a local ssh, which is not supported. + */ + +int dropbear_scp_execvp(FAR const char *file, FAR char * const argv[]) +{ + (void)file; + (void)argv; + + set_errno(ENOSYS); + return ERROR; +} diff --git a/netutils/dropbear/port/nuttx_scp.h b/netutils/dropbear/port/nuttx_scp.h new file mode 100644 index 00000000000..c5edf799aef --- /dev/null +++ b/netutils/dropbear/port/nuttx_scp.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * apps/netutils/dropbear/port/nuttx_scp.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __APPS_NETUTILS_DROPBEAR_PORT_NUTTX_SCP_H +#define __APPS_NETUTILS_DROPBEAR_PORT_NUTTX_SCP_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +int dropbear_scp_execvp(FAR const char *file, FAR char * const argv[]); + +#endif /* __APPS_NETUTILS_DROPBEAR_PORT_NUTTX_SCP_H */ diff --git a/nshlib/CMakeLists.txt b/nshlib/CMakeLists.txt index 8adab316892..866f8bb6c9f 100644 --- a/nshlib/CMakeLists.txt +++ b/nshlib/CMakeLists.txt @@ -101,6 +101,10 @@ if(CONFIG_NSH_LIBRARY) endif() endif() + if(CONFIG_NSH_DROPBEAR) + list(APPEND CSRCS nsh_dropbear.c) + endif() + if(NOT CONFIG_NSH_DISABLESCRIPT) list(APPEND CSRCS nsh_test.c) endif() diff --git a/nshlib/Kconfig b/nshlib/Kconfig index f00137f0b72..165bcc8b387 100644 --- a/nshlib/Kconfig +++ b/nshlib/Kconfig @@ -1201,6 +1201,28 @@ config NSH_DISABLE_TELNETSTART endmenu # Telnet Configuration +menu "Dropbear Configuration" + +config NSH_DROPBEAR + bool "Start Dropbear SSH server" + default n + depends on NETUTILS_DROPBEAR + depends on NSH_BUILTIN_APPS + depends on !NSH_DISABLEBG + select NSH_LOGIN + ---help--- + If NSH_DROPBEAR is set to 'y', then NSH starts the Dropbear SSH + server automatically. + +config NSH_DISABLE_DROPBEARSTART + bool "Disable to start Dropbear" + default n + depends on NSH_DROPBEAR + ---help--- + Determines if NSH starts Dropbear automatically. + +endmenu # Dropbear Configuration + config NSH_LOGIN bool default n @@ -1208,32 +1230,30 @@ config NSH_LOGIN config NSH_CONSOLE_LOGIN bool "Console Login" default n + depends on FSUTILS_PASSWD select NSH_LOGIN ---help--- If defined, then the console user will be required to provide a - username and password to start the NSH shell. + username and password to start the NSH shell. Requires + CONFIG_FSUTILS_PASSWD so credentials are verified against the + encrypted password file (for example ROMFS /etc/passwd). config NSH_TELNET_LOGIN bool "Telnet Login" default n - depends on NSH_TELNET + depends on NSH_TELNET && FSUTILS_PASSWD select NSH_LOGIN ---help--- If defined, then the Telnet user will be required to provide a - username and password to start the NSH shell. + username and password to start the NSH shell. Requires + CONFIG_FSUTILS_PASSWD so credentials are verified against the + encrypted password file (for example ROMFS /etc/passwd). if NSH_LOGIN choice prompt "Verification method" - default NSH_LOGIN_PASSWD if FSUTILS_PASSWD - default NSH_LOGIN_FIXED if !FSUTILS_PASSWD - -config NSH_LOGIN_FIXED - bool "Fixed username/password" - ---help--- - Verify user credentials by matching to fixed username and password - strings + default NSH_LOGIN_PASSWD config NSH_LOGIN_PLATFORM bool "Platform username/password" @@ -1265,17 +1285,10 @@ endchoice # Verification method config NSH_LOGIN_USERNAME string "Login username" - default "admin" - depends on !NSH_LOGIN_PASSWD - ---help--- - Login user name. Default: "admin" - -config NSH_LOGIN_PASSWORD - string "Login password" - default "Administrator" + default "root" depends on !NSH_LOGIN_PASSWD ---help--- - Login password: Default: "Administrator" + Login user name. Default: "root" config NSH_LOGIN_FAILDELAY int "Login failure delay" diff --git a/nshlib/Makefile b/nshlib/Makefile index d15ca4aa311..4aba9e3a827 100644 --- a/nshlib/Makefile +++ b/nshlib/Makefile @@ -86,6 +86,10 @@ CSRCS += nsh_telnetlogin.c endif endif +ifeq ($(CONFIG_NSH_DROPBEAR),y) +CSRCS += nsh_dropbear.c +endif + ifneq ($(CONFIG_NSH_DISABLESCRIPT),y) CSRCS += nsh_test.c endif diff --git a/nshlib/nsh.h b/nshlib/nsh.h index f20fcf7732e..d2b7abc4473 100644 --- a/nshlib/nsh.h +++ b/nshlib/nsh.h @@ -266,8 +266,7 @@ * If CONFIG_NSH_TELNET_LOGIN is defined, then these additional * options may be specified: * - * CONFIG_NSH_LOGIN_USERNAME - Login user name. Default: "admin" - * CONFIG_NSH_LOGIN_PASSWORD - Login password: Default: "Administrator" + * CONFIG_NSH_LOGIN_USERNAME - Login user name. Default: "root" * CONFIG_NSH_LOGIN_FAILCOUNT - Number of login retry attempts. * Default 3. */ @@ -275,11 +274,7 @@ #ifdef CONFIG_NSH_TELNET_LOGIN # ifndef CONFIG_NSH_LOGIN_USERNAME -# define CONFIG_NSH_LOGIN_USERNAME "admin" -# endif - -# ifndef CONFIG_NSH_LOGIN_PASSWORD -# define CONFIG_NSH_LOGIN_PASSWORD "nuttx" +# define CONFIG_NSH_LOGIN_USERNAME "root" # endif # ifndef CONFIG_NSH_LOGIN_FAILCOUNT @@ -844,6 +839,10 @@ int nsh_login(FAR struct console_stdio_s *pstate); int nsh_telnetlogin(FAR struct console_stdio_s *pstate); #endif +#if defined(CONFIG_NSH_DROPBEAR) && !defined(CONFIG_NSH_DISABLE_DROPBEARSTART) +int nsh_dropbearstart(void); +#endif + /* Application interface */ int nsh_command(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char *argv[]); diff --git a/nshlib/nsh_dropbear.c b/nshlib/nsh_dropbear.c new file mode 100644 index 00000000000..9e7753f11a0 --- /dev/null +++ b/nshlib/nsh_dropbear.c @@ -0,0 +1,76 @@ +/**************************************************************************** + * apps/nshlib/nsh_dropbear.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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include "nsh.h" +#include "nsh_console.h" + +#ifdef CONFIG_NSH_DROPBEAR + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: nsh_dropbearstart + * + * Description: + * nsh_dropbearstart() starts the Dropbear SSH server. This function + * returns immediately after the daemon has been started. + * + * Returned Values: + * Zero is returned if Dropbear was started. A negated errno value will be + * returned on failure. + * + ****************************************************************************/ + +#ifndef CONFIG_NSH_DISABLE_DROPBEARSTART +int nsh_dropbearstart(void) +{ + FAR struct console_stdio_s *pstate = nsh_newconsole(false); + char cmdline[] = CONFIG_NETUTILS_DROPBEAR_PROGNAME " &"; + int ret; + + DEBUGASSERT(pstate != NULL); + + ninfo("Starting the Dropbear SSH server\n"); + + ret = nsh_parse(&pstate->cn_vtbl, cmdline); + if (ret < 0) + { + nerr("ERROR: Failed to start Dropbear: %d\n", ret); + } + + nsh_release(&pstate->cn_vtbl); + return ret; +} +#endif + +#endif /* CONFIG_NSH_DROPBEAR */ diff --git a/nshlib/nsh_identity.c b/nshlib/nsh_identity.c index e69fd92e253..7bcb9a4a06e 100644 --- a/nshlib/nsh_identity.c +++ b/nshlib/nsh_identity.c @@ -165,9 +165,6 @@ static bool nsh_verify_credentials(FAR const char *username, return PASSWORD_VERIFY_MATCH(passwd_verify(username, password)); #elif defined(CONFIG_NSH_LOGIN_PLATFORM) return PASSWORD_VERIFY_MATCH(platform_user_verify(username, password)); -#elif defined(CONFIG_NSH_LOGIN_FIXED) - return strcmp(password, CONFIG_NSH_LOGIN_PASSWORD) == 0 && - strcmp(username, CONFIG_NSH_LOGIN_USERNAME) == 0; #else UNUSED(username); UNUSED(password); diff --git a/nshlib/nsh_init.c b/nshlib/nsh_init.c index aea095e34cd..49c1488e6bf 100644 --- a/nshlib/nsh_init.c +++ b/nshlib/nsh_init.c @@ -175,4 +175,16 @@ void nsh_initialize(void) nsh_telnetstart(AF_UNSPEC); #endif + +#if defined(CONFIG_NSH_DROPBEAR) && \ + !defined(CONFIG_NSH_DISABLE_DROPBEARSTART) && \ + !defined(CONFIG_NETINIT_NETLOCAL) + /* If Dropbear is selected as an SSH front-end, then start the daemon + * UNLESS network initialization is deferred via CONFIG_NETINIT_NETLOCAL. + * In that case, Dropbear must be started manually with the dropbear + * command after the network has been initialized. + */ + + nsh_dropbearstart(); +#endif } diff --git a/nshlib/nsh_login.c b/nshlib/nsh_login.c index 6630a1689a3..228a66397dd 100644 --- a/nshlib/nsh_login.c +++ b/nshlib/nsh_login.c @@ -243,9 +243,6 @@ int nsh_login(FAR struct console_stdio_s *pstate) #endif if (PASSWORD_VERIFY_MATCH(ret)) -#elif defined(CONFIG_NSH_LOGIN_FIXED) - if (strcmp(password, CONFIG_NSH_LOGIN_PASSWORD) == 0 && - strcmp(username, CONFIG_NSH_LOGIN_USERNAME) == 0) #else # error No user verification method selected #endif diff --git a/nshlib/nsh_passwdcmds.c b/nshlib/nsh_passwdcmds.c index 5c7f86ed23f..f37cbaf2ac1 100644 --- a/nshlib/nsh_passwdcmds.c +++ b/nshlib/nsh_passwdcmds.c @@ -53,8 +53,19 @@ int cmd_useradd(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv) ret = passwd_adduser(argv[1], argv[2]); if (ret < 0) { - nsh_error(vtbl, g_fmtcmdfailed, argv[0], "passwd_adduser", - NSH_ERRNO_OF(-ret)); + if (ret == -EINVAL) + { + nsh_error(vtbl, + "%s: password does not meet security policy " + "(min 8 chars, upper, lower, digit, special)\n", + argv[0]); + } + else + { + nsh_error(vtbl, g_fmtcmdfailed, argv[0], "passwd_adduser", + NSH_ERRNO_OF(-ret)); + } + return ERROR; } @@ -99,8 +110,19 @@ int cmd_passwd(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char **argv) ret = passwd_update(argv[1], argv[2]); if (ret < 0) { - nsh_error(vtbl, g_fmtcmdfailed, argv[0], "passwd_update", - NSH_ERRNO_OF(-ret)); + if (ret == -EINVAL) + { + nsh_error(vtbl, + "%s: password does not meet security policy " + "(min 8 chars, upper, lower, digit, special)\n", + argv[0]); + } + else + { + nsh_error(vtbl, g_fmtcmdfailed, argv[0], "passwd_update", + NSH_ERRNO_OF(-ret)); + } + return ERROR; } diff --git a/nshlib/nsh_telnetlogin.c b/nshlib/nsh_telnetlogin.c index c796b2a36cc..8007b26846a 100644 --- a/nshlib/nsh_telnetlogin.c +++ b/nshlib/nsh_telnetlogin.c @@ -248,9 +248,6 @@ int nsh_telnetlogin(FAR struct console_stdio_s *pstate) if (PASSWORD_VERIFY_MATCH(platform_user_verify(username, password))) # endif -#elif defined(CONFIG_NSH_LOGIN_FIXED) - if (strcmp(password, CONFIG_NSH_LOGIN_PASSWORD) == 0 && - strcmp(username, CONFIG_NSH_LOGIN_USERNAME) == 0) #else # error No user verification method selected #endif diff --git a/testing/crypto/CMakeLists.txt b/testing/crypto/CMakeLists.txt new file mode 100644 index 00000000000..0b2030ebe8a --- /dev/null +++ b/testing/crypto/CMakeLists.txt @@ -0,0 +1,24 @@ +# ############################################################################## +# apps/testing/crypto/CMakeLists.txt +# +# 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. +# +# ############################################################################## + +nuttx_add_subdirectory() +nuttx_generate_kconfig(MENUDESC "crypto") diff --git a/testing/crypto/Make.defs b/testing/crypto/Make.defs new file mode 100644 index 00000000000..12e81164e0e --- /dev/null +++ b/testing/crypto/Make.defs @@ -0,0 +1,23 @@ +############################################################################ +# apps/testing/crypto/Make.defs +# +# 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. +# +############################################################################ + +include $(wildcard $(APPDIR)/testing/crypto/*/Make.defs) diff --git a/testing/crypto/passwd/CMakeLists.txt b/testing/crypto/passwd/CMakeLists.txt new file mode 100644 index 00000000000..2a5c205c6d2 --- /dev/null +++ b/testing/crypto/passwd/CMakeLists.txt @@ -0,0 +1,37 @@ +# ############################################################################## +# apps/testing/crypto/passwd/CMakeLists.txt +# +# 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. +# +# ############################################################################## + +if(CONFIG_TESTING_PBKDF2) + nuttx_add_application( + NAME + pbkdf2_test + PRIORITY + ${CONFIG_TESTING_PBKDF2_PRIORITY} + STACKSIZE + ${CONFIG_TESTING_PBKDF2_STACKSIZE} + MODULE + ${CONFIG_TESTING_PBKDF2} + SRCS + pbkdf2_test.c + INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_LIST_DIR}/../../../fsutils/passwd) +endif() diff --git a/testing/crypto/passwd/Kconfig b/testing/crypto/passwd/Kconfig new file mode 100644 index 00000000000..b15276e645f --- /dev/null +++ b/testing/crypto/passwd/Kconfig @@ -0,0 +1,34 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config TESTING_PBKDF2 + tristate "PBKDF2 and passwd hash test" + default n + depends on FSUTILS_PASSWD + ---help--- + Enable the PBKDF2-HMAC-SHA256 unit test + (apps/testing/crypto/passwd). Always runs RFC 6070 SHA-256 + vectors. The passwd_encrypt / passwd_verify round-trip runs only + when FSUTILS_PASSWD_READONLY is disabled and DEV_URANDOM is + enabled; otherwise it is skipped with an explanatory message. + +if TESTING_PBKDF2 + +config TESTING_PBKDF2_PRIORITY + int "pbkdf2_test task priority" + default 100 + +config TESTING_PBKDF2_STACKSIZE + int "pbkdf2_test stack size" + default DEFAULT_TASK_STACKSIZE + +config TESTING_PBKDF2_SLOW_VECTOR + bool "Run RFC 6070 vector with 16777216 iterations" + default n + ---help--- + Include RFC 6070 test vector #4 (16,777,216 iterations). This + takes a long time on embedded targets; enable only for manual runs. + +endif diff --git a/testing/crypto/passwd/Make.defs b/testing/crypto/passwd/Make.defs new file mode 100644 index 00000000000..2e7cac96805 --- /dev/null +++ b/testing/crypto/passwd/Make.defs @@ -0,0 +1,25 @@ +############################################################################ +# apps/testing/crypto/passwd/Make.defs +# +# 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. +# +############################################################################ + +ifneq ($(CONFIG_TESTING_PBKDF2),) +CONFIGURED_APPS += $(APPDIR)/testing/crypto/passwd +endif diff --git a/testing/crypto/passwd/Makefile b/testing/crypto/passwd/Makefile new file mode 100644 index 00000000000..2b726cbd6f7 --- /dev/null +++ b/testing/crypto/passwd/Makefile @@ -0,0 +1,34 @@ +############################################################################ +# apps/testing/crypto/passwd/Makefile +# +# 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. +# +############################################################################ + +include $(APPDIR)/Make.defs + +PRIORITY = $(CONFIG_TESTING_PBKDF2_PRIORITY) +STACKSIZE = $(CONFIG_TESTING_PBKDF2_STACKSIZE) +MODULE = $(CONFIG_TESTING_PBKDF2) + +MAINSRC = pbkdf2_test.c +PROGNAME = pbkdf2_test + +CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/fsutils/passwd + +include $(APPDIR)/Application.mk diff --git a/testing/crypto/passwd/pbkdf2_test.c b/testing/crypto/passwd/pbkdf2_test.c new file mode 100644 index 00000000000..d1f0e718df0 --- /dev/null +++ b/testing/crypto/passwd/pbkdf2_test.c @@ -0,0 +1,287 @@ +/**************************************************************************** + * apps/testing/crypto/passwd/pbkdf2_test.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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include + +#include "passwd.h" +#include "passwd_pbkdf2.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define TEST_USERNAME "testuser" +#define TEST_PASSWORD "MySecret1!" +#define WRONG_PASSWORD "WrongPass" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct pbkdf2_vector_s +{ + FAR const char *password; + size_t passwordlen; + FAR const char *salt; + size_t saltlen; + uint32_t iterations; + size_t dklen; + FAR const uint8_t *expected; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* PBKDF2-HMAC-SHA256 test vectors from RFC 6070 (appendix B). */ + +static const uint8_t g_vector1[] = +{ + 0x12, 0x0f, 0xb6, 0xcf, 0xfc, 0xf8, 0xb3, 0x2c, + 0x43, 0xe7, 0x22, 0x52, 0x56, 0xc4, 0xf8, 0x37, + 0xa8, 0x65, 0x48, 0xc9 +}; + +static const uint8_t g_vector2[] = +{ + 0xae, 0x4d, 0x0c, 0x95, 0xaf, 0x6b, 0x46, 0xd3, + 0x2d, 0x0a, 0xdf, 0xf9, 0x28, 0xf0, 0x6d, 0xd0, + 0x2a, 0x30, 0x3f, 0x8e +}; + +static const uint8_t g_vector3[] = +{ + 0xc5, 0xe4, 0x78, 0xd5, 0x92, 0x88, 0xc8, 0x41, + 0xaa, 0x53, 0x0d, 0xb6, 0x84, 0x5c, 0x4c, 0x8d, + 0x96, 0x28, 0x93, 0xa0 +}; + +static const uint8_t g_vector4[] = +{ + 0xcf, 0x81, 0xc6, 0x6f, 0xe8, 0xcf, 0xc0, 0x4d, + 0x1f, 0x31, 0xec, 0xb6, 0x5d, 0xab, 0x40, 0x89, + 0xf7, 0xf1, 0x79, 0xe8 +}; + +static const uint8_t g_vector5[] = +{ + 0x34, 0x8c, 0x89, 0xdb, 0xcb, 0xd3, 0x2b, 0x2f, + 0x32, 0xd8, 0x14, 0xb8, 0x11, 0x6e, 0x84, 0xcf, + 0x2b, 0x17, 0x34, 0x7e, 0xbc, 0x18, 0x00, 0x18, + 0x1c +}; + +static const uint8_t g_vector6[] = +{ + 0x89, 0xb6, 0x9d, 0x05, 0x16, 0xf8, 0x29, 0x89, + 0x3c, 0x69, 0x62, 0x26, 0x65, 0x0a, 0x86, 0x87 +}; + +static const struct pbkdf2_vector_s g_vectors[] = +{ + { + "password", 8, + "salt", 4, + 1, 20, + g_vector1 + }, + { + "password", 8, + "salt", 4, + 2, 20, + g_vector2 + }, + { + "password", 8, + "salt", 4, + 4096, 20, + g_vector3 + }, + { + "password", 8, + "salt", 4, + 16777216, 20, + g_vector4 + }, + { + "passwordPASSWORDpassword", 24, + "saltSALTsaltSALTsaltSALTsaltSALTsalt", 36, + 4096, 25, + g_vector5 + }, + { + "pass\0word", 9, + "sa\0lt", 5, + 4096, 16, + g_vector6 + }, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int test_pbkdf2_vectors(void) +{ + FAR const struct pbkdf2_vector_s *vec; + uint8_t output[32]; + int failures = 0; + int i; + int ret; + + for (i = 0; i < (int)(sizeof(g_vectors) / sizeof(g_vectors[0])); i++) + { + vec = &g_vectors[i]; + +#ifndef CONFIG_TESTING_PBKDF2_SLOW_VECTOR + if (vec->iterations > 100000) + { + printf("pbkdf2_test: skipping slow vector %d (enable " + "TESTING_PBKDF2_SLOW_VECTOR)\n", i); + continue; + } +#endif + + ret = passwd_pbkdf2_hmac_sha256((FAR const uint8_t *)vec->password, + vec->passwordlen, + (FAR const uint8_t *)vec->salt, + vec->saltlen, + vec->iterations, + output, vec->dklen); + if (ret != 0) + { + printf("pbkdf2_test: vector %d pbkdf2 failed: %d\n", i, ret); + failures++; + continue; + } + + if (memcmp(output, vec->expected, vec->dklen) != 0) + { + printf("pbkdf2_test: vector %d output mismatch\n", i); + failures++; + } + } + + if (failures == 0) + { + printf("pbkdf2_test: RFC 6070 SHA-256 vectors OK\n"); + } + + return failures; +} + +static int test_passwd_roundtrip(void) +{ +#if defined(CONFIG_FSUTILS_PASSWD_READONLY) + printf("pbkdf2_test: skipping passwd round-trip " + "(FSUTILS_PASSWD_READONLY)\n"); + return 0; +#elif !defined(CONFIG_DEV_URANDOM) + printf("pbkdf2_test: skipping passwd round-trip " + "(DEV_URANDOM)\n"); + return 0; +#else + FILE *stream; + char encrypted[MAX_ENCRYPTED + 1]; + int ret; + + unlink(CONFIG_FSUTILS_PASSWD_PATH); + + ret = passwd_encrypt(TEST_PASSWORD, encrypted); + if (ret < 0) + { + printf("pbkdf2_test: passwd_encrypt failed: %d\n", ret); + return 1; + } + + stream = fopen(CONFIG_FSUTILS_PASSWD_PATH, "w"); + if (stream == NULL) + { + printf("pbkdf2_test: cannot write %s: %d\n", + CONFIG_FSUTILS_PASSWD_PATH, errno); + return 1; + } + + if (fprintf(stream, "%s:%s:0:0:/\n", TEST_USERNAME, encrypted) < 0) + { + printf("pbkdf2_test: fprintf failed: %d\n", errno); + fclose(stream); + return 1; + } + + fclose(stream); + + ret = passwd_verify(TEST_USERNAME, TEST_PASSWORD); + if (ret != 0) + { + printf("pbkdf2_test: passwd_verify match failed: %d (expected 0)\n", + ret); + return 1; + } + + ret = passwd_verify(TEST_USERNAME, WRONG_PASSWORD); + if (ret != -1) + { + printf("pbkdf2_test: passwd_verify mismatch failed: %d " + "(expected -1)\n", ret); + return 1; + } + + printf("pbkdf2_test: passwd round-trip OK\n"); + return 0; +#endif +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + int failures = 0; + + (void)argc; + (void)argv; + + failures += test_pbkdf2_vectors(); + failures += test_passwd_roundtrip(); + + if (failures != 0) + { + printf("pbkdf2_test: FAILED (%d)\n", failures); + return 1; + } + + printf("pbkdf2_test: PASSED\n"); + return 0; +}