!build: add build-time password generation with mkpasswd tool.

Introduce mkpasswd, a pure-C host tool for generating encrypted password
files at build time using TEA encryption. This enables secure,
credential-free firmware images while allowing build-time password
configuration.

Changes:
* Add mkpasswd.c host tool for TEA-based password hashing and encryption
* Integrate mkpasswd into Make build system (tools/Makefile.host)
* Add CMake support for mkpasswd compilation and ROMFS passwd generation
* Add CONFIG_BOARD_ETC_ROMFS_PASSWD_* configuration options to Kconfig
* Implement credential exclusion from defconfig to prevent password leaking
* Update savedefconfig.cmake to strip sensitive credentials
* Fix mkdir() portability for Windows Native builds (CONFIG_WINDOWS_NATIVE)
* Change default username from "admin" to "root" (POSIX convention)
* Improve build-failure error message with full menuconfig navigation path

BREAKING CHANGE: Boards enabling CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE
must set CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD to a non-empty string
of at least 8 characters. The build now fails with an explicit error if
this config is left empty. To fix: run 'make menuconfig' and navigate to:
  Board Selection --->
    Auto-generate /etc/passwd at build time --->
      Admin password

Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
This commit is contained in:
Abhishek Mishra 2026-03-20 13:54:21 +00:00 committed by Xiang Xiao
parent ea161e4fa9
commit ab6b1fd6f9
9 changed files with 813 additions and 8 deletions

1
.gitignore vendored
View file

@ -9,6 +9,7 @@
*.dSYM
*.elf
*.exe
etctmp/
*.gcno
*.gcda
*.hex

View file

@ -30,11 +30,28 @@ $(RCOBJS): $(ETCDIR)$(DELIM)%: %
$(Q) mkdir -p $(dir $@)
$(call PREPROCESS, $<, $@)
$(ETCSRC): $(foreach raw,$(RCRAWS), $(if $(wildcard $(BOARD_DIR)$(DELIM)src$(DELIM)$(raw)), $(BOARD_DIR)$(DELIM)src$(DELIM)$(raw), $(if $(wildcard $(BOARD_COMMON_DIR)$(DELIM)$(raw)), $(BOARD_COMMON_DIR)$(DELIM)$(raw), $(BOARD_DIR)$(DELIM)src$(DELIM)$(raw)))) $(RCOBJS)
$(ETCSRC): $(foreach raw,$(RCRAWS), $(if $(wildcard $(BOARD_DIR)$(DELIM)src$(DELIM)$(raw)), $(BOARD_DIR)$(DELIM)src$(DELIM)$(raw), $(if $(wildcard $(BOARD_COMMON_DIR)$(DELIM)$(raw)), $(BOARD_COMMON_DIR)$(DELIM)$(raw), $(BOARD_DIR)$(DELIM)src$(DELIM)$(raw)))) $(RCOBJS) $(TOPDIR)$(DELIM).config
$(foreach raw, $(RCRAWS), \
$(shell rm -rf $(ETCDIR)$(DELIM)$(raw)) \
$(shell mkdir -p $(dir $(ETCDIR)$(DELIM)$(raw))) \
$(shell cp -rfp $(if $(wildcard $(BOARD_DIR)$(DELIM)src$(DELIM)$(raw)), $(BOARD_DIR)$(DELIM)src$(DELIM)$(raw), $(if $(wildcard $(BOARD_COMMON_DIR)$(DELIM)$(raw)), $(BOARD_COMMON_DIR)$(DELIM)$(raw), $(BOARD_DIR)$(DELIM)src$(DELIM)$(raw))) $(ETCDIR)$(DELIM)$(raw)))
ifeq ($(CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE),y)
ifeq ($(strip $(patsubst "%",%,$(CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD))),)
$(error CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD must be set when BOARD_ETC_ROMFS_PASSWD_ENABLE is enabled. Run 'make menuconfig' and select a password at: Board Selection ---> Auto-generate /etc/passwd at build time ---> Admin password)
endif
$(Q) mkdir -p $(ETCDIR)$(DELIM)$(CONFIG_ETC_ROMFSMOUNTPT)
$(Q) $(TOPDIR)$(DELIM)tools$(DELIM)mkpasswd$(HOSTEXEEXT) \
--user $(CONFIG_BOARD_ETC_ROMFS_PASSWD_USER) \
--password $(CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD) \
--uid $(CONFIG_BOARD_ETC_ROMFS_PASSWD_UID) \
--gid $(CONFIG_BOARD_ETC_ROMFS_PASSWD_GID) \
--home $(CONFIG_BOARD_ETC_ROMFS_PASSWD_HOME) \
$(if $(CONFIG_FSUTILS_PASSWD_KEY1),--key1 $(CONFIG_FSUTILS_PASSWD_KEY1)) \
$(if $(CONFIG_FSUTILS_PASSWD_KEY2),--key2 $(CONFIG_FSUTILS_PASSWD_KEY2)) \
$(if $(CONFIG_FSUTILS_PASSWD_KEY3),--key3 $(CONFIG_FSUTILS_PASSWD_KEY3)) \
$(if $(CONFIG_FSUTILS_PASSWD_KEY4),--key4 $(CONFIG_FSUTILS_PASSWD_KEY4)) \
-o $(ETCDIR)$(DELIM)$(CONFIG_ETC_ROMFSMOUNTPT)$(DELIM)passwd
endif
$(Q) genromfs -f romfs.img -d $(ETCDIR)$(DELIM)$(CONFIG_ETC_ROMFSMOUNTPT) -V "NSHInitVol"
$(Q) echo "#include <nuttx/compiler.h>" > $@
$(Q) xxd -i romfs.img | sed -e "s/^unsigned char/const unsigned char aligned_data(4)/g" >> $@

View file

@ -5446,3 +5446,53 @@ config BOARD_MEMORY_RANGE
end: end address of memory range
flags: Executable 0x1, Writable 0x2, Readable 0x4
example:{0x1000,0x2000,0x4},{0x2000,0x3000,0x6},{0x3000,0x4000,0x7} ... {0x0,0x0,0x0}
config BOARD_ETC_ROMFS_PASSWD_ENABLE
bool "Auto-generate /etc/passwd at build time"
default n
depends on ETC_ROMFS
---help---
Generate the /etc/passwd file at build time from a user-supplied
password. This avoids shipping a hard-coded default password
(CWE-798). When enabled, the build will fail if no password
is configured, forcing each build to set its own credentials.
The password is hashed at build time by the host tool
tools/mkpasswd (compiled from tools/mkpasswd.c) using the Tiny
Encryption Algorithm (TEA) — the same algorithm used at runtime
in libs/libc/misc/lib_tea_encrypt.c. The plaintext password is
never stored in the firmware image.
See Documentation/components/passwd_autogen.rst for details.
if BOARD_ETC_ROMFS_PASSWD_ENABLE
config BOARD_ETC_ROMFS_PASSWD_USER
string "Admin username"
default "root"
---help---
The username for the auto-generated /etc/passwd entry.
config BOARD_ETC_ROMFS_PASSWD_PASSWORD
string "Admin password (required)"
default "Administrator"
---help---
The plaintext password for the auto-generated /etc/passwd entry.
This value is hashed with TEA at build time; the plaintext is NOT
stored in the firmware image. The build will fail if this is left
empty or shorter than 8 characters. Set this via
'make menuconfig'.
config BOARD_ETC_ROMFS_PASSWD_UID
int "Admin user ID"
default 0
config BOARD_ETC_ROMFS_PASSWD_GID
int "Admin group ID"
default 0
config BOARD_ETC_ROMFS_PASSWD_HOME
string "Admin home directory"
default "/"
endif # BOARD_ETC_ROMFS_PASSWD_ENABLE

View file

@ -282,6 +282,75 @@ function(process_all_directory_romfs)
list(PREPEND RCSRCS ${board_rcsrcs} ${dyn_rcsrcs})
list(PREPEND RCRAWS ${board_rcraws} ${dyn_rcraws})
# Auto-generate /etc/passwd at build time if configured
if(CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE)
if("${CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD}" STREQUAL "")
message(
FATAL_ERROR
"CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD must be set when "
"BOARD_ETC_ROMFS_PASSWD_ENABLE is enabled. Run 'make menuconfig' "
"to set a password.")
endif()
# Determine host executable suffix (.exe on Windows, empty elsewhere)
if(CMAKE_HOST_WIN32)
set(HOST_EXE_SUFFIX .exe)
else()
set(HOST_EXE_SUFFIX "")
endif()
# Locate a host C compiler to build the mkpasswd tool
find_program(HOST_CC NAMES cc gcc clang REQUIRED)
# Build mkpasswd.c as a host binary in the CMake build directory and keep
# the source tree clean.
set(MKPASSWD_SRC ${NUTTX_DIR}/tools/mkpasswd.c)
set(MKPASSWD_BIN ${CMAKE_BINARY_DIR}/tools/mkpasswd${HOST_EXE_SUFFIX})
if(NOT TARGET build_host_mkpasswd)
add_custom_command(
OUTPUT ${MKPASSWD_BIN}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/tools
COMMAND ${HOST_CC} -o ${MKPASSWD_BIN} ${MKPASSWD_SRC}
DEPENDS ${MKPASSWD_SRC}
COMMENT "Building host tool: mkpasswd")
add_custom_target(build_host_mkpasswd DEPENDS ${MKPASSWD_BIN})
endif()
# Pass TEA key overrides when the user has changed them from defaults
set(MKPASSWD_KEY_ARGS "")
if(CONFIG_FSUTILS_PASSWD_KEY1)
list(APPEND MKPASSWD_KEY_ARGS --key1 ${CONFIG_FSUTILS_PASSWD_KEY1})
endif()
if(CONFIG_FSUTILS_PASSWD_KEY2)
list(APPEND MKPASSWD_KEY_ARGS --key2 ${CONFIG_FSUTILS_PASSWD_KEY2})
endif()
if(CONFIG_FSUTILS_PASSWD_KEY3)
list(APPEND MKPASSWD_KEY_ARGS --key3 ${CONFIG_FSUTILS_PASSWD_KEY3})
endif()
if(CONFIG_FSUTILS_PASSWD_KEY4)
list(APPEND MKPASSWD_KEY_ARGS --key4 ${CONFIG_FSUTILS_PASSWD_KEY4})
endif()
set(GENPASSWD_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/etc/passwd)
add_custom_command(
OUTPUT ${GENPASSWD_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/etc
COMMAND
${MKPASSWD_BIN} --user "${CONFIG_BOARD_ETC_ROMFS_PASSWD_USER}"
--password "${CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD}" --uid
${CONFIG_BOARD_ETC_ROMFS_PASSWD_UID} --gid
${CONFIG_BOARD_ETC_ROMFS_PASSWD_GID} --home
"${CONFIG_BOARD_ETC_ROMFS_PASSWD_HOME}" ${MKPASSWD_KEY_ARGS} -o
${GENPASSWD_OUTPUT}
DEPENDS ${MKPASSWD_BIN} ${NUTTX_DIR}/.config
COMMENT "Generating /etc/passwd from Kconfig values")
add_custom_target(generate_passwd DEPENDS ${GENPASSWD_OUTPUT})
add_dependencies(generate_passwd build_host_mkpasswd)
list(APPEND RCRAWS ${GENPASSWD_OUTPUT})
list(APPEND dyn_deps generate_passwd)
endif()
# init dynamic dependencies
get_property(

View file

@ -27,10 +27,14 @@ set(TARGET_FILE ${CMAKE_ARGV4})
file(STRINGS ${SOURCE_FILE} ConfigContents)
encode_brackets(ConfigContents)
set(PASSWD_AUTOGEN_ENABLED FALSE)
foreach(NameAndValue ${ConfigContents})
decode_brackets(NameAndValue)
encode_semicolon(NameAndValue)
if("${NameAndValue}" MATCHES "^CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y$")
set(PASSWD_AUTOGEN_ENABLED TRUE)
endif()
if("${NameAndValue}" MATCHES "CONFIG_ARCH="
OR "${NameAndValue}" MATCHES "^CONFIG_ARCH_CHIP_"
OR "${NameAndValue}" MATCHES "CONFIG_ARCH_CHIP="
@ -72,9 +76,20 @@ list(SORT LINES)
foreach(LINE IN LISTS LINES)
decode_brackets(LINE)
decode_semicolon(LINE)
file(APPEND ${OUTPUT_FILE} "${LINE}\n")
if(NOT "${LINE}" MATCHES "^CONFIG_FSUTILS_PASSWD_KEY[0-9]"
AND NOT "${LINE}" MATCHES "^CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD=")
file(APPEND ${OUTPUT_FILE} "${LINE}\n")
endif()
endforeach()
if(PASSWD_AUTOGEN_ENABLED)
message(
WARNING
"CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD and CONFIG_FSUTILS_PASSWD_KEY1-4 "
"were intentionally excluded from defconfig by savedefconfig. Add them "
"manually in local defconfig if needed.")
endif()
# Converts the newline style for the output file.
configure_file(${OUTPUT_FILE} ${OUTPUT_FILE} @ONLY NEWLINE_STYLE LF)

1
tools/.gitignore vendored
View file

@ -14,6 +14,7 @@
/mksymtab
/mksyscall
/mkversion
/mkpasswd
/nxstyle
/rmcr
/incdir

View file

@ -35,8 +35,9 @@ endif
all: b16$(HOSTEXEEXT) bdf-converter$(HOSTEXEEXT) cmpconfig$(HOSTEXEEXT) \
configure$(HOSTEXEEXT) mkconfig$(HOSTEXEEXT) mkdeps$(HOSTEXEEXT) \
mksymtab$(HOSTEXEEXT) mksyscall$(HOSTEXEEXT) mkversion$(HOSTEXEEXT) \
cnvwindeps$(HOSTEXEEXT) nxstyle$(HOSTEXEEXT) initialconfig$(HOSTEXEEXT) \
gencromfs$(HOSTEXEEXT) convert-comments$(HOSTEXEEXT) lowhex$(HOSTEXEEXT) \
mkpasswd$(HOSTEXEEXT) cnvwindeps$(HOSTEXEEXT) nxstyle$(HOSTEXEEXT) \
initialconfig$(HOSTEXEEXT) gencromfs$(HOSTEXEEXT) \
convert-comments$(HOSTEXEEXT) lowhex$(HOSTEXEEXT) \
detab$(HOSTEXEEXT) rmcr$(HOSTEXEEXT) incdir$(HOSTEXEEXT) \
jlink-nuttx$(HOSTDYNEXT)
default: mkconfig$(HOSTEXEEXT) mksyscall$(HOSTEXEEXT) mkdeps$(HOSTEXEEXT) \
@ -44,8 +45,8 @@ default: mkconfig$(HOSTEXEEXT) mksyscall$(HOSTEXEEXT) mkdeps$(HOSTEXEEXT) \
ifdef HOSTEXEEXT
.PHONY: b16 bdf-converter cmpconfig clean configure kconfig2html mkconfig \
mkdeps mksymtab mksyscall mkversion cnvwindeps nxstyle initialconfig \
gencromfs convert-comments lowhex detab rmcr incdir
mkdeps mksymtab mksyscall mkversion mkpasswd cnvwindeps nxstyle \
initialconfig gencromfs convert-comments lowhex detab rmcr incdir
endif
ifdef HOSTDYNEXT
.PHONY: jlink-nuttx
@ -106,6 +107,15 @@ ifdef HOSTEXEEXT
mkversion: mkversion$(HOSTEXEEXT)
endif
# mkpasswd - Generate a NuttX /etc/passwd entry with TEA-encrypted password
mkpasswd$(HOSTEXEEXT): mkpasswd.c
$(Q) $(HOSTCC) $(HOSTCFLAGS) -o mkpasswd$(HOSTEXEEXT) mkpasswd.c
ifdef HOSTEXEEXT
mkpasswd: mkpasswd$(HOSTEXEEXT)
endif
# mksyscall - Convert a CSV file into syscall stubs and proxies
mksyscall$(HOSTEXEEXT): mksyscall.c csvparser.c
@ -268,6 +278,8 @@ clean:
$(call DELFILE, mksyscall.exe)
$(call DELFILE, mkversion)
$(call DELFILE, mkversion.exe)
$(call DELFILE, mkpasswd)
$(call DELFILE, mkpasswd.exe)
$(call DELFILE, nxstyle)
$(call DELFILE, nxstyle.exe)
$(call DELFILE, rmcr)

View file

@ -282,6 +282,9 @@ tools/mkdeps$(HOSTEXEEXT):
tools/cnvwindeps$(HOSTEXEEXT):
$(Q) $(MAKE) -C tools -f Makefile.host cnvwindeps$(HOSTEXEEXT)
tools/mkpasswd$(HOSTEXEEXT):
$(Q) $(MAKE) -C tools -f Makefile.host mkpasswd$(HOSTEXEEXT)
# .dirlinks, and helpers
#
# Directories links. Most of establishing the NuttX configuration involves
@ -661,12 +664,17 @@ host_info: checkpython3
# pass1dep: Create pass1 build dependencies
# pass2dep: Create pass2 build dependencies
pass1dep: context tools/mkdeps$(HOSTEXEEXT) tools/cnvwindeps$(HOSTEXEEXT)
PASSWD_TOOL_DEP =
ifeq ($(CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE),y)
PASSWD_TOOL_DEP += tools/mkpasswd$(HOSTEXEEXT)
endif
pass1dep: context tools/mkdeps$(HOSTEXEEXT) tools/cnvwindeps$(HOSTEXEEXT) $(PASSWD_TOOL_DEP)
$(Q) for dir in $(USERDEPDIRS) ; do \
$(MAKE) -C $$dir depend || exit; \
done
pass2dep: context tools/mkdeps$(HOSTEXEEXT) tools/cnvwindeps$(HOSTEXEEXT)
pass2dep: context tools/mkdeps$(HOSTEXEEXT) tools/cnvwindeps$(HOSTEXEEXT) $(PASSWD_TOOL_DEP)
$(Q) for dir in $(KERNDEPDIRS) ; do \
$(MAKE) -C $$dir EXTRAFLAGS="$(KDEFINE) $(EXTRAFLAGS)" depend || exit; \
done
@ -759,6 +767,8 @@ savedefconfig: apps_preconfig
$(Q) ${KCONFIG_ENV} ${KCONFIG_SAVEDEFCONFIG}
$(Q) $(call kconfig_tweak_disable,defconfig.tmp,CONFIG_APPS_DIR)
$(Q) $(call kconfig_tweak_disable,defconfig.tmp,CONFIG_BASE_DEFCONFIG)
$(Q) sed -i -e '/^CONFIG_FSUTILS_PASSWD_KEY[0-9]/d' defconfig.tmp
$(Q) sed -i -e '/^CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD=/d' defconfig.tmp
$(Q) grep "CONFIG_ARCH=" .config >> defconfig.tmp
$(Q) grep "^CONFIG_ARCH_CHIP_" .config >> defconfig.tmp; true
$(Q) grep "CONFIG_ARCH_CHIP=" .config >> defconfig.tmp; true
@ -778,6 +788,11 @@ savedefconfig: apps_preconfig
$(Q) rm -f warning.tmp
$(Q) rm -f defconfig.tmp
$(Q) rm -f sortedconfig.tmp
$(Q) if grep -q '^CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y' .config; then \
echo "WARNING: CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD was not saved in defconfig."; \
echo "WARNING: CONFIG_FSUTILS_PASSWD_KEY1-4 were not saved in defconfig."; \
echo "WARNING: This is intentional to avoid leaking credentials. Add them manually in local defconfig if needed."; \
fi
# export
#

625
tools/mkpasswd.c Normal file
View file

@ -0,0 +1,625 @@
/****************************************************************************
* tools/mkpasswd.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.
*
****************************************************************************/
/****************************************************************************
* Description:
* Host build tool that generates a NuttX /etc/passwd entry with a
* TEA-encrypted password hash. This is a pure C replacement for the
* former tools/mkpasswd.py, removing the Python dependency from the build.
*
* The encryption algorithm and base64 encoding are identical to those
* used at runtime by:
* libs/libc/misc/lib_tea_encrypt.c
* apps/fsutils/passwd/passwd_encrypt.c
*
* Usage:
* mkpasswd --user <name> --password <pass> [options] [-o <output>]
*
* Options:
* --user <str> Username (required)
* --password <str> Plaintext password (required, not stored in output)
* --uid <int> User ID (default: 0)
* --gid <int> Group ID (default: 0)
* --home <str> Home directory (default: /)
* --key1 <hex> TEA key word 1 (default: 0x12345678)
* --key2 <hex> TEA key word 2 (default: 0x9abcdef0)
* --key3 <hex> TEA key word 3 (default: 0x12345678)
* --key4 <hex> TEA key word 4 (default: 0x9abcdef0)
* -o <path> Output file (default: stdout)
*
* Output format (matches NuttX passwd file format):
* username:encrypted_hash:uid:gid:home
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
/* Expose strdup(), mkdir() and other POSIX.1-2008 extensions when
* compiling with strict C99 mode (-std=c99). Has no effect on C11/GNU
* builds or MSVC.
*/
#ifndef _POSIX_C_SOURCE
# define _POSIX_C_SOURCE 200809L
#endif
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifndef CONFIG_WINDOWS_NATIVE
# include <sys/stat.h>
#else
# include <direct.h>
#endif
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* TEA key schedule constant (derived from the golden ratio) */
#define TEA_KEY_SCHEDULE_CONSTANT 0x9e3779b9u
/* Password size limits - must match apps/fsutils/passwd/passwd.h */
#define MAX_ENCRYPTED 48 /* Max size of encrypted password (ASCII) */
#define MAX_PASSWORD (3 * MAX_ENCRYPTED / 4) /* Max plaintext length */
#define MIN_PASSWORD 8 /* Minimum plaintext length for security */
/* Default TEA key values - must match CONFIG_FSUTILS_PASSWD_KEY1-4 defaults
* in apps/fsutils/passwd/Kconfig so that the generated hash verifies
* correctly at runtime when the user has not changed the key config.
*/
#define DEFAULT_KEY1 0x12345678u
#define DEFAULT_KEY2 0x9abcdef0u
#define DEFAULT_KEY3 0x12345678u
#define DEFAULT_KEY4 0x9abcdef0u
/****************************************************************************
* Private Types
****************************************************************************/
/* 8-byte block interpreted as bytes, 16-bit halves, or 32-bit words */
union block_u
{
char b[8];
uint16_t h[4];
uint32_t l[2];
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: tea_encrypt
*
* Description:
* Encrypt two 32-bit words in-place using the Tiny Encryption Algorithm.
* This is an exact copy of the algorithm in
* libs/libc/misc/lib_tea_encrypt.c (public-domain TEA by Wheeler &
* Needham), inlined here so that the host tool has no NuttX dependencies.
*
* Input Parameters:
* value - Two-element array [v0, v1] to encrypt (modified in-place)
* key - Four-element 128-bit key array
*
****************************************************************************/
static void tea_encrypt(uint32_t *value, const uint32_t *key)
{
uint32_t v0 = value[0];
uint32_t v1 = value[1];
uint32_t sum = 0;
int i;
for (i = 0; i < 32; i++)
{
sum += TEA_KEY_SCHEDULE_CONSTANT;
v0 += ((v1 << 4) + key[0]) ^ (v1 + sum) ^ ((v1 >> 5) + key[1]);
v1 += ((v0 << 4) + key[2]) ^ (v0 + sum) ^ ((v0 >> 5) + key[3]);
}
value[0] = v0;
value[1] = v1;
}
/****************************************************************************
* Name: passwd_base64
*
* Description:
* Encode the low 6 bits of a byte as a custom base64 character.
* Alphabet: A-Z (0-25), a-z (26-51), 0-9 (52-61), + (62), / (63).
* The colon ':' is deliberately absent so it never collides with the
* passwd field separator.
*
* This matches passwd_base64() in apps/fsutils/passwd/passwd_encrypt.c.
*
****************************************************************************/
static char passwd_base64(uint8_t binary)
{
binary &= 63;
if (binary < 26)
{
return (char)('A' + binary);
}
binary -= 26;
if (binary < 26)
{
return (char)('a' + binary);
}
binary -= 26;
if (binary < 10)
{
return (char)('0' + binary);
}
binary -= 10;
if (binary == 0)
{
return '+';
}
return '/';
}
/****************************************************************************
* Name: passwd_encrypt
*
* Description:
* Encrypt a plaintext password string and store the result as a
* NUL-terminated base64 string in `encrypted`.
*
* Algorithm (identical to apps/fsutils/passwd/passwd_encrypt.c):
* 1. Process the password in 8-byte gulps, padding short gulps with
* ASCII spaces.
* 2. TEA-encrypt each 8-byte gulp as two uint32_t words.
* 3. Interpret the result as four uint16_t half-words.
* 4. Stream-encode those half-words 6 bits at a time using the custom
* base64 alphabet above.
*
* Input Parameters:
* password - NUL-terminated plaintext password
* key - Four-element 128-bit TEA key
* encrypted - Output buffer (at least MAX_ENCRYPTED + 1 bytes)
*
* Returned Value:
* 0 on success, -1 on error (password too long).
*
****************************************************************************/
static int passwd_encrypt(const char *password,
const uint32_t *key,
char encrypted[MAX_ENCRYPTED + 1])
{
union block_u value;
const char *src;
char *dest;
uint32_t tmp;
uint8_t remainder;
int remaining;
int gulpsize;
int nbits;
int i;
remaining = (int)strlen(password);
if (remaining > MAX_PASSWORD)
{
fprintf(stderr, "mkpasswd: password too long (max %d characters)\n",
MAX_PASSWORD);
return -1;
}
src = password;
dest = encrypted;
*dest = '\0';
remainder = 0;
nbits = 0;
for (; remaining > 0; remaining -= gulpsize)
{
/* Copy up to 8 bytes into the block, padding the rest with spaces */
gulpsize = 8;
if (gulpsize > remaining)
{
gulpsize = remaining;
}
for (i = 0; i < gulpsize; i++)
{
value.b[i] = *src++;
}
for (; i < 8; i++)
{
value.b[i] = ' ';
}
/* TEA-encrypt the block in-place */
tea_encrypt(value.l, key);
/* Stream-encode the four 16-bit half-words into base64 */
tmp = remainder;
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;
}
}
remainder = (uint8_t)tmp;
*dest = '\0';
}
/* Flush any remaining bits */
if (nbits > 0)
{
*dest++ = passwd_base64(remainder);
*dest = '\0';
}
return 0;
}
/****************************************************************************
* Name: parse_uint32_hex
*
* Description:
* Parse a hex string (with or without leading "0x"/"0X") into a uint32_t.
* Returns 0 on success, -1 on parse error.
*
****************************************************************************/
static int parse_uint32_hex(const char *str, uint32_t *out)
{
char *endptr;
unsigned long val;
if (str == NULL || *str == '\0')
{
return -1;
}
errno = 0;
val = strtoul(str, &endptr, 0); /* base 0: auto-detect 0x prefix */
if (errno != 0 || *endptr != '\0')
{
return -1;
}
*out = (uint32_t)val;
return 0;
}
/****************************************************************************
* Name: mkdir_p
*
* Description:
* Create all directory components in `path`, like "mkdir -p".
* Returns 0 on success, -1 on error.
*
****************************************************************************/
static int mkdir_p(const char *path)
{
char *tmp;
char *p;
size_t len;
tmp = strdup(path);
if (tmp == NULL)
{
return -1;
}
len = strlen(tmp);
/* Strip trailing slash */
if (len > 0 && tmp[len - 1] == '/')
{
tmp[len - 1] = '\0';
}
for (p = tmp + 1; *p != '\0'; p++)
{
if (*p == '/')
{
*p = '\0';
#ifndef CONFIG_WINDOWS_NATIVE
mkdir(tmp, 0755);
#else
mkdir(tmp);
#endif
*p = '/';
}
}
#ifndef CONFIG_WINDOWS_NATIVE
mkdir(tmp, 0755);
#else
mkdir(tmp);
#endif
free(tmp);
return 0;
}
/****************************************************************************
* Name: show_usage
****************************************************************************/
static void show_usage(const char *progname)
{
fprintf(stderr,
"Usage: %s --user <name> --password <pass> [options] [-o <file>]\n"
"\n"
"Options:\n"
" --user <str> Username (required)\n"
" --password <str> Plaintext password (required)\n"
" --uid <int> User ID (default: 0)\n"
" --gid <int> Group ID (default: 0)\n"
" --home <str> Home directory (default: /)\n"
" --key1 <hex> TEA key word 1 (default: 0x%08x)\n"
" --key2 <hex> TEA key word 2 (default: 0x%08x)\n"
" --key3 <hex> TEA key word 3 (default: 0x%08x)\n"
" --key4 <hex> TEA key word 4 (default: 0x%08x)\n"
" -o <path> Output file (default: stdout)\n"
"\n"
"Output format: username:encrypted_hash:uid:gid:home\n",
progname,
DEFAULT_KEY1, DEFAULT_KEY2, DEFAULT_KEY3, DEFAULT_KEY4);
}
/****************************************************************************
* Public Functions
****************************************************************************/
int main(int argc, char **argv)
{
const char *user = NULL;
const char *password = NULL;
const char *home = "/";
const char *outpath = NULL;
int uid = 0;
int gid = 0;
uint32_t key[4] =
{
DEFAULT_KEY1, DEFAULT_KEY2, DEFAULT_KEY3, DEFAULT_KEY4
};
char encrypted[MAX_ENCRYPTED + 1];
FILE *out;
int i;
int ret;
/* Simple long-option parser (avoids getopt_long portability concerns) */
for (i = 1; i < argc; i++)
{
if (strcmp(argv[i], "--user") == 0 && i + 1 < argc)
{
user = argv[++i];
}
else if (strcmp(argv[i], "--password") == 0 && i + 1 < argc)
{
password = argv[++i];
}
else if (strcmp(argv[i], "--uid") == 0 && i + 1 < argc)
{
uid = atoi(argv[++i]);
}
else if (strcmp(argv[i], "--gid") == 0 && i + 1 < argc)
{
gid = atoi(argv[++i]);
}
else if (strcmp(argv[i], "--home") == 0 && i + 1 < argc)
{
home = argv[++i];
}
else if (strcmp(argv[i], "--key1") == 0 && i + 1 < argc)
{
if (parse_uint32_hex(argv[++i], &key[0]) < 0)
{
fprintf(stderr, "mkpasswd: invalid --key1 value: %s\n",
argv[i]);
return 1;
}
}
else if (strcmp(argv[i], "--key2") == 0 && i + 1 < argc)
{
if (parse_uint32_hex(argv[++i], &key[1]) < 0)
{
fprintf(stderr, "mkpasswd: invalid --key2 value: %s\n",
argv[i]);
return 1;
}
}
else if (strcmp(argv[i], "--key3") == 0 && i + 1 < argc)
{
if (parse_uint32_hex(argv[++i], &key[2]) < 0)
{
fprintf(stderr, "mkpasswd: invalid --key3 value: %s\n",
argv[i]);
return 1;
}
}
else if (strcmp(argv[i], "--key4") == 0 && i + 1 < argc)
{
if (parse_uint32_hex(argv[++i], &key[3]) < 0)
{
fprintf(stderr, "mkpasswd: invalid --key4 value: %s\n",
argv[i]);
return 1;
}
}
else if ((strcmp(argv[i], "-o") == 0 ||
strcmp(argv[i], "--output") == 0) && i + 1 < argc)
{
outpath = argv[++i];
}
else if (strcmp(argv[i], "--help") == 0 ||
strcmp(argv[i], "-h") == 0)
{
show_usage(argv[0]);
return 0;
}
else
{
fprintf(stderr, "mkpasswd: unknown option: %s\n", argv[i]);
show_usage(argv[0]);
return 1;
}
}
/* Validate required arguments */
if (user == NULL)
{
fprintf(stderr, "mkpasswd: --user is required\n");
show_usage(argv[0]);
return 1;
}
if (password == NULL)
{
fprintf(stderr, "mkpasswd: --password is required\n");
show_usage(argv[0]);
return 1;
}
if (password[0] == '\0')
{
fprintf(stderr, "mkpasswd: --password must not be empty\n");
return 1;
}
if (strlen(password) < MIN_PASSWORD)
{
fprintf(stderr,
"mkpasswd: --password must be at least %d characters\n",
MIN_PASSWORD);
return 1;
}
/* Warn if the board Kconfig default password is still being used. */
if (strcmp(password, "Administrator") == 0)
{
fprintf(stderr,
">>>> WARNING: YOU ARE USING THE DEFAULT ADMIN PASSWORD "
"(CONFIG_BOARD_ETC_ROMFS_"
"PASSWD_PASSWORD=\"Administrator\")!!! PLEASE CHANGE "
"IT!!! <<<<\n");
}
/* Warn when the user has not changed the default TEA keys.
* The default values are identical across all NuttX builds, so any
* attacker with access to the firmware image can recover the plaintext
* password. This is a warning only; the build is not aborted.
*/
if (key[0] == DEFAULT_KEY1 && key[1] == DEFAULT_KEY2 &&
key[2] == DEFAULT_KEY3 && key[3] == DEFAULT_KEY4)
{
fprintf(stderr,
">>>> WARNING: YOU ARE USING DEFAULT PASSWORD KEYS "
"(CONFIG_FSUTILS_"
"PASSWD_KEY1-4)!!! PLEASE CHANGE IT!!! <<<<\n");
}
/* Encrypt the password using TEA + custom base64.
* Only the hash is written to the output file; the plaintext is never
* stored in firmware.
*/
ret = passwd_encrypt(password, key, encrypted);
if (ret < 0)
{
return 1;
}
/* Open the output stream */
if (outpath != NULL)
{
/* Create parent directory if it does not exist */
char *dir = strdup(outpath);
char *last = strrchr(dir, '/');
if (last != NULL && last != dir)
{
*last = '\0';
mkdir_p(dir);
}
free(dir);
out = fopen(outpath, "w");
if (out == NULL)
{
fprintf(stderr, "mkpasswd: cannot open output file '%s': %s\n",
outpath, strerror(errno));
return 1;
}
}
else
{
out = stdout;
}
/* Write the passwd entry.
* Format: username:encrypted_hash:uid:gid:home
* This matches the format expected by apps/fsutils/passwd/passwd_find.c
* and the existing NuttX /etc/passwd files.
*/
fprintf(out, "%s:%s:%d:%d:%s\n", user, encrypted, uid, gid, home);
if (outpath != NULL)
{
fclose(out);
}
return 0;
}