diff --git a/Documentation/applications/nsh/login.rst b/Documentation/applications/nsh/login.rst index b499a0ff4b40f..583790b37b27c 100644 --- a/Documentation/applications/nsh/login.rst +++ b/Documentation/applications/nsh/login.rst @@ -18,13 +18,30 @@ Logins for Telnet sessions can be enabled separately with:: Logins can be enabled for either or both session types. On a successful login, the user will have access to the NSH session:: - login: admin + login: root password: User Logged-in! NuttShell (NSH) nsh> +ROMFS password file (recommended) +================================== + +Boards with ROMFS ``/etc`` should auto-generate ``/etc/passwd`` at build time:: + + CONFIG_ETC_ROMFS=y + CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y + CONFIG_FSUTILS_PASSWD=y + CONFIG_FSUTILS_PASSWD_READONLY=y + CONFIG_NSH_CONSOLE_LOGIN=y + +Set **Board Selection → Auto-generate /etc/passwd at build time → Root password** +in menuconfig, in a local defconfig, or at the ``make`` prompt +(:ref:`mkpasswd_autogen`). Only a PBKDF2-HMAC-SHA256 hash is stored in flash. + +Do not use ``CONFIG_NSH_LOGIN_FIXED`` when a ROMFS passwd file is available. + When ``CONFIG_NSH_LOGIN_SETUID`` is enabled (the default when ``CONFIG_SCHED_USER_IDENTITY`` is selected), NSH looks up the authenticated user name in the passwd database and sets the session @@ -90,66 +107,52 @@ will be closed. That number is controlled by:: CONFIG_NSH_LOGIN_FAILCOUNT=3 +.. _nsh_login_verification: + Verification of Credentials =========================== -There are three ways that NSH can be configured to verify user -credentials at login time: - - #. The simplest implementation simply uses fixed login credentials and - is selected with:: - - CONFIG_NSH_LOGIN_FIXED=y - - The fixed login credentials are selected via:: - - CONFIG_NSH_LOGIN_USERNAME=admin - CONFIG_NSH_LOGIN_PASSWORD="Administrator" - - This is not very flexible since there can be only one user and the - password is fixed in the FLASH image. This option is also not very - secure because a malicious user could get the password by just - looking at the ``.text`` strings in the flash image. - - #. NSH can also be configured to defer the entire user credential - verification to platform-specific logic with this setting:: - - CONFIG_NSH_LOGIN_PLATFORM=y - - In this case, NSH will call a platform-specific function to perform - the verification of user credentials. The platform-specific logic - must provide a function with the following prototype: - - .. code-block:: c - - int platform_user_verify(FAR const char *username, FAR const char *password); - - which is prototyped an described in ``apps/include/nsh.h`` and which - may be included like: - - .. code-block:: c - - #include - - An appropriate place to implement this function might be in the - directory ``apps/platform/``. - - #. A final option is to use a password file contained encrypted password - information. This final option is selected with the following and - described in more detail in the following paragraph:: - - CONFIG_NSH_LOGIN_PASSWD=y +There are three ways to verify credentials at login: + +.. list-table:: NSH credential verification methods + :header-rows: 1 + :widths: 20 15 65 + + * - Method + - Kconfig + - Summary + * - Password file (recommended) + - ``CONFIG_NSH_LOGIN_PASSWD=y`` + - Verifies against ``/etc/passwd`` using PBKDF2-HMAC-SHA256 hashes. + Use with ``CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE`` for ROMFS boards. + * - Fixed username/password + - ``CONFIG_NSH_LOGIN_FIXED=y`` + - Single hard-coded user; plaintext may appear in the firmware image. + Not recommended when a passwd file is available. + * - Platform callback + - ``CONFIG_NSH_LOGIN_PLATFORM=y`` + - Board-specific ``platform_user_verify()`` function. + +When ``CONFIG_FSUTILS_PASSWD=y`` is enabled, NSH defaults to +``CONFIG_NSH_LOGIN_PASSWD=y`` automatically. + + #. **Fixed username/password.** Selected with ``CONFIG_NSH_LOGIN_FIXED=y``. + Credentials are set via ``CONFIG_NSH_LOGIN_USERNAME`` and + ``CONFIG_NSH_LOGIN_PASSWORD``. Not recommended when a ROMFS passwd file + is available; see :ref:`mkpasswd_autogen`. + + If ``CONFIG_NSH_LOGIN_PASSWORD`` is unset, ``tools/promptpasswd.sh`` may + prompt during an interactive build. + + #. **Platform-specific verification.** NSH calls ``platform_user_verify()`` + when ``CONFIG_NSH_LOGIN_PLATFORM=y``. Prototype in ``apps/include/nsh.h``. Password Files ============== -NuttX can also be configured to support a password file, by default at -``/etc/passwd``. This option enables support for a password file:: - - CONFIG_NSH_LOGIN_PASSWD=y - -This options requires that you have selected ``CONFIG_FSUTILS_PASSWD=y`` -to enable the access methods of ``apps/fsutils/passwd``:: +When ``CONFIG_NSH_LOGIN_PASSWD=y`` is selected, NSH reads user names and +password hashes from a passwd file (default ``/etc/passwd``). Enable the +file-access layer with:: CONFIG_FSUTILS_PASSWD=y @@ -176,34 +179,125 @@ specifically disabled. The password file logic requires a few additional settings: - #. The size of dynamically allocated and freed buffer that is used for + #. **I/O buffer size**: size of the dynamically allocated buffer used for file access:: CONFIG_FSUTILS_PASSWD_IOBUFFER_SIZE=512 - #. And the 128-bit encryption key. The password file currently uses the - Tiny Encryption Algorithm (TEA), but could be extended to use - something more powerful. + #. **PBKDF2 iteration count**: applied when **setting** new passwords + (via ``useradd``, ``passwd``, or build-time ``mkpasswd``):: + + CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS=10000 + + Valid range: 1000 to 200000. Higher values resist brute-force attacks + but increase login latency on low-MHz MCUs. The iteration count is + stored inside each hash string, so changing this option only affects + newly-created passwords. + + #. **Random salt source**: new salts require random bytes. Enable a + platform random source such as:: - CONFIG_FSUTILS_PASSWD_KEY1=0x12345678 - CONFIG_FSUTILS_PASSWD_KEY2=0x9abcdef0 - CONFIG_FSUTILS_PASSWD_KEY3=0x12345678 - CONFIG_FSUTILS_PASSWD_KEY4=0x9abcdef0 + CONFIG_DEV_URANDOM=y -Password can only be decrypted with access to this key. Note that this -key could potentially be fished out of your FLASH image, but without any -symbolic information, that would be a difficult job since the TEA KEY is -binary data and not distinguishable from other binary data in the FLASH -image. + ``passwd_encrypt()`` uses ``getrandom()`` or ``/dev/urandom`` when + generating salts for ``useradd`` and ``passwd``. + +Password complexity rules +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The same rules are enforced everywhere a **new** password is set: + +* Build-time ``tools/mkpasswd`` (ROMFS autogen) +* NSH commands ``useradd`` and ``passwd`` +* Runtime API ``passwd_encrypt()`` in ``apps/fsutils/passwd`` + +Rules: + +* At least **8** characters +* At least one **uppercase** letter (``A`` to ``Z``) +* At least one **lowercase** letter (``a`` to ``z``) +* At least one **digit** (``0`` to ``9``) +* At least one **special** character from:: + + ! @ # $ % ^ & * ( ) _ + - = [ ] { } | ; : , . < > ? + +If ``CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD`` is missing or does not meet +these rules, ``make`` prompts via ``tools/promptpasswd.sh`` on interactive +terminals. Non-interactive builds must set the password in ``.config`` or +menuconfig first. + +Password hash format +~~~~~~~~~~~~~~~~~~~~ + +Each password field uses **PBKDF2-HMAC-SHA256** in modular crypt format +(MCF). The hash string is self-contained: it stores the iteration count +and salt, so verification does not depend on separate key material in +firmware:: + + $pbkdf2-sha256$$$ + +Where: + +* ````: PBKDF2 round count (parsed at verify time) +* ````: 16-byte random salt (RFC 4648 section 5, no padding) +* ````: 32-byte PBKDF2-HMAC-SHA256 output (same encoding) + +Example ``/etc/passwd`` line:: + + root:$pbkdf2-sha256$10000$zhoo4phwEzyNFUAkB7asfw$P8qsjd9RQmZBLfM5zugiJeE5gKjI-CmTxyaVyOX2mE4:0:0:/ + +Full ``/etc/passwd`` record format:: + + user:hash:uid:gid:home + +.. note:: + + **Breaking change:** this replaces the former TEA-based password storage. + Existing TEA-encoded entries will **not** verify. Regenerate every entry + with ``mkpasswd``, NSH ``passwd``, or ``useradd`` after upgrading. + +``passwd_verify()`` returns ``0`` on match, ``-1`` on mismatch or invalid +hash format, and a negated ``errno`` on I/O errors. If the password file is enabled (``CONFIG_NSH_LOGIN_PASSWD=y``), then the fixed user credentials will not be used for the NSH session login. Instead, the password file will be consulted to verify the user credentials. +Notes on ``savedefconfig`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To avoid leaking credentials into board defconfigs, ``make savedefconfig`` +**omits** these options from the generated defconfig: + +* ``CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD`` +* ``CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS`` + +Add them manually to a local defconfig after ``make savedefconfig`` if +needed for development. + Creating a Password File for a ROMFS File System ================================================ +Boards with ``CONFIG_ETC_ROMFS`` can auto-generate ``/etc/passwd`` at +**build time** when ``CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y``. + +Build-time flow +~~~~~~~~~~~~~~~ + +1. You configure ``CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD`` (CMake requires + this in ``.config``; ``make`` can prompt if it is unset). +2. The build runs ``tools/mkpasswd`` (Makefile builds use + ``tools/board_romfs_mkpasswd.sh`` as a wrapper). +3. Only the **hash** is written into the ROMFS image at ``/etc/passwd``. +4. At boot, NSH login checks the typed password against that hash. + +See :ref:`mkpasswd_autogen` in :doc:`/components/tools/index` for the full +tool description, Kconfig list, and verification steps. + +The following describes the **manual** approach for creating or updating a +password file when build-time autogen is **not** used. + What we want to accomplish is a ROMFS file system, mounted at ``/etc`` and containing the password file, ``passwd`` like:: @@ -220,10 +314,10 @@ and containing the password file, ``passwd`` like:: nsh> Where ``/etc/init.d/rc.sysinit`` is the system init script and -``/etc/init.d/rcS`` is the start-up script; ``/etc/passwd`` is a -the password file. Note that here we assume that you are already using a +``/etc/init.d/rcS`` is the start-up script; ``/etc/passwd`` is the +password file. Note that here we assume that you are already using a start-up script. We can then piggyback the passwd file into the ``/etc`` -file system already mounted for the NSH start up file as described above +file system already mounted for the NSH start up file as described `above <#custinit>`__. The sim/nsh configuration can be used to create a new password file, but other @@ -267,7 +361,7 @@ new user passwords like:: nsh> useradd Do this as many times as you would like. Each time that you do this a -new entry with an encrypted password will be added to the ``passwd`` +new entry with a hashed password will be added to the ``passwd`` file at ``/tmp/passwd``. You can see the content of the password file like:: @@ -286,9 +380,10 @@ Then create/re-create the ``nsh_romfsimg.h`` file as described below. mkdir etc mkdir etc/init.d - And copy your existing startup script into ``etc/init.c`` as ``rcS``. + And copy your existing startup script into ``etc/init.d/`` as ``rcS``. #. Save your new password file in the ``etc/`` directory as ``passwd``. + Each line must use the PBKDF2 MCF format described above. #. Create the new ROMFS image:: @@ -298,12 +393,9 @@ Then create/re-create the ``nsh_romfsimg.h`` file as described below. xxd -i romfs_img >nsh_romfsimg.h - #. Edit ``nsh_romfsimg.h``: Mark both data definitions as ``const`` so + #. Edit ``nsh_romfsimg.h``: mark both data definitions as ``const`` so that the data will be stored in FLASH. - #. Edit nsh_romfsimg.h, mark both data definitions as ``const`` so that - that will be stored in FLASH. - There is a good example of how to do this in the NSH simulation configuration at `boards/sim/sim/sim/configs/nsh `__. diff --git a/Documentation/components/tools/index.rst b/Documentation/components/tools/index.rst index 83c868984fb55..b51abce5fcd3e 100644 --- a/Documentation/components/tools/index.rst +++ b/Documentation/components/tools/index.rst @@ -14,110 +14,83 @@ and host C programs that are important parts of the NuttX build system: .. _mkpasswd_autogen: -mkpasswd — Build-time ``/etc/passwd`` Generation -------------------------------------------------- +mkpasswd: Build-time ``/etc/passwd`` generation +=============================================== -``tools/mkpasswd`` is a C host tool (compiled from ``tools/mkpasswd.c``) that -generates a single ``/etc/passwd`` entry at build time. It is invoked -automatically by the ROMFS build step when -``CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y`` is set. +``tools/mkpasswd`` (``tools/mkpasswd.c``) writes one ``/etc/passwd`` line at +build time when ``CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y``. -Why build-time generation? -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Shipping a hard-coded default password in firmware is a well-known security -weakness (CWE-798). By generating the ``/etc/passwd`` entry from a -user-supplied plaintext password at build time, each firmware image carries -unique credentials. The build will fail if the password is left empty, -preventing accidental deployments with no credentials. - -For improved baseline security, the configured password must be at least -8 characters long. - -How it works -~~~~~~~~~~~~ - -1. The host tool reads the plaintext password from - ``CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD``. -2. The password is hashed using the Tiny Encryption Algorithm (TEA) — the - same implementation used at runtime in - ``libs/libc/misc/lib_tea_encrypt.c`` — with custom base64 encoding - matching ``apps/fsutils/passwd/passwd_encrypt.c``. -3. The resulting hashed entry is written to - ``etctmp//passwd`` and then embedded into the ROMFS image. -4. The **plaintext password is never stored in the firmware image**. - -Kconfig options -~~~~~~~~~~~~~~~ - -Enable the feature and configure credentials via ``make menuconfig``: +Quick start +~~~~~~~~~~~ .. code:: kconfig + CONFIG_ETC_ROMFS=y CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y - CONFIG_NSH_CONSOLE_LOGIN=y # required to enforce login prompt - CONFIG_BOARD_ETC_ROMFS_PASSWD_USER="root" # default: root - CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD="" # required, min length 8 - CONFIG_BOARD_ETC_ROMFS_PASSWD_UID=0 - CONFIG_BOARD_ETC_ROMFS_PASSWD_GID=0 - CONFIG_BOARD_ETC_ROMFS_PASSWD_HOME="/" + CONFIG_FSUTILS_PASSWD=y + CONFIG_FSUTILS_PASSWD_READONLY=y + CONFIG_NSH_CONSOLE_LOGIN=y + +Set **Board Selection → Auto-generate /etc/passwd at build time → Root password** +in menuconfig or at the ``make`` prompt, then build and log in as ``root``. -The TEA encryption keys can be changed from their defaults via -``CONFIG_FSUTILS_PASSWD_KEY1..4``. +See :doc:`/applications/nsh/login` for NSH login details. -``/etc/passwd`` file format -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Build flow +~~~~~~~~~~ -.. code:: text +**Makefile builds** use ``tools/board_romfs_mkpasswd.sh``, which validates the +password (``tools/promptpasswd.sh`` if needed) and invokes ``tools/mkpasswd``. - user:x:uid:gid:home +**CMake builds** invoke ``tools/mkpasswd`` directly; set +``CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD`` in ``.config`` (configure fails if +it is missing). -Where: +In both cases: -* ``user`` — user name -* ``x`` — TEA-hashed, base64-encoded password -* ``uid`` — numeric user ID -* ``gid`` — numeric group ID -* ``home`` — login directory +1. ``tools/mkpasswd`` hashes with PBKDF2-HMAC-SHA256 (same algorithm as + ``apps/fsutils/passwd/passwd_encrypt.c``). +2. The hash is written to ``etctmp/.../passwd`` and embedded in ROMFS. -Verifying the generated entry -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The plaintext password is used on the host during ``make`` only. The build +fails if ROMFS autogen is enabled and no valid password is configured. -After enabling ``CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE`` and setting a -password, rebuild and verify: +Password rules +~~~~~~~~~~~~~~ -1. **Configure and build**: +Minimum 8 characters; at least one uppercase, lowercase, digit, and special +character from ``!@#$%^&*()_+-=[]{}|;:,.<>?``. - .. code:: console +Hash format +~~~~~~~~~~~ - $ make menuconfig # enable BOARD_ETC_ROMFS_PASSWD_ENABLE and set password - $ make +:: -2. **Inspect the generated passwd line** (written to the board build tree): + $pbkdf2-sha256$$$ - .. code:: console +Example:: - $ cat boards////src/etctmp/etc/passwd - root:8Tv+Hbmr3pLVb5HHZgd26D:0:0:/ + root:$pbkdf2-sha256$10000$zhoo4phwEzyNFUAkB7asfw$P8qsjd9RQmZBLfM5zugiJeE5gKjI-CmTxyaVyOX2mE4 -3. **Verify the plaintext is absent from firmware**: +**Breaking change:** TEA-encoded entries are not compatible. Regenerate with +``mkpasswd`` or NSH ``passwd`` / ``useradd``. - .. code:: console +Kconfig +~~~~~~~ - $ grep boards////src/etctmp.c - # must print nothing +.. code:: kconfig -Notes on ``savedefconfig`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ + CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y + CONFIG_BOARD_ETC_ROMFS_PASSWD_USER="root" + CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD="" + CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS=10000 -To avoid leaking credentials into board defconfigs, ``make savedefconfig`` -does not save the following options in the generated defconfig: +``make savedefconfig`` omits ``CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD`` and +``CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS`` to avoid leaking credentials. -* ``CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD`` -* ``CONFIG_FSUTILS_PASSWD_KEY1`` -* ``CONFIG_FSUTILS_PASSWD_KEY2`` -* ``CONFIG_FSUTILS_PASSWD_KEY3`` -* ``CONFIG_FSUTILS_PASSWD_KEY4`` +Host files +~~~~~~~~~~ -If you need these values for local development, add them manually to your -local defconfig after running ``make savedefconfig``. \ No newline at end of file +* ``tools/mkpasswd.c`` - PBKDF2 hash generation +* ``tools/promptpasswd.sh`` - password prompt and validation +* ``tools/board_romfs_mkpasswd.sh`` - Makefile ROMFS build wrapper diff --git a/Documentation/platforms/renesas/rx65n/boards/rx65n-grrose/index.rst b/Documentation/platforms/renesas/rx65n/boards/rx65n-grrose/index.rst index 537542c9394b5..bd5a93ee26f23 100644 --- a/Documentation/platforms/renesas/rx65n/boards/rx65n-grrose/index.rst +++ b/Documentation/platforms/renesas/rx65n/boards/rx65n-grrose/index.rst @@ -499,13 +499,15 @@ credentials via ``make menuconfig``: * ``CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y`` * ``CONFIG_BOARD_ETC_ROMFS_PASSWD_USER`` (default: ``root``) -* ``CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD`` (required, build fails if empty) +* ``CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD`` (required; minimum 8 characters + with uppercase, lowercase, digit, and special character. See + :ref:`mkpasswd_autogen`) -The password is hashed with TEA at build time by the host tool +The password is hashed with PBKDF2-HMAC-SHA256 at build time by the host tool ``tools/mkpasswd``; the plaintext is **not** stored in the firmware. -For the full description of the mechanism, TEA key configuration, file format, -and verification steps, see :ref:`mkpasswd_autogen`. +For the full description of the mechanism, file format, and verification +steps, see :ref:`mkpasswd_autogen`. The format of the password file is: @@ -515,7 +517,7 @@ The format of the password file is: Where: user: User name - x: Encrypted password + x: PBKDF2-HMAC-SHA256 hash (modular crypt format) uid: User ID (0 for now) gid: Group ID (0 for now) home: Login directory (/ for now) @@ -525,7 +527,7 @@ The format of the password file is: .. code:: console nsh> cat /etc/group - root:*:0:root,admin + root:*:0:root The format of the group file is: diff --git a/Documentation/platforms/risc-v/esp32c3-legacy/boards/esp32c3-legacy-devkit/ROMFS.txt b/Documentation/platforms/risc-v/esp32c3-legacy/boards/esp32c3-legacy-devkit/ROMFS.txt index 2434964afdda5..3a4519cb740aa 100644 --- a/Documentation/platforms/risc-v/esp32c3-legacy/boards/esp32c3-legacy-devkit/ROMFS.txt +++ b/Documentation/platforms/risc-v/esp32c3-legacy/boards/esp32c3-legacy-devkit/ROMFS.txt @@ -31,13 +31,16 @@ README CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y CONFIG_BOARD_ETC_ROMFS_PASSWD_USER (default: root) - CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD (required, build fails if empty) + CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD (required; minimum 8 characters + with uppercase, lowercase, digit, and special character) - The password is hashed with TEA at build time by the host tool + The password is hashed with PBKDF2-HMAC-SHA256 at build time by the host tool tools/mkpasswd; the plaintext is NOT stored in the firmware image. + If the password is not set in .config, make prompts via tools/promptpasswd.sh + on an interactive terminal. - For the full description of the mechanism, TEA key configuration, file - format, and verification steps, see Documentation/components/tools/index.rst + For the full description of the mechanism, file format, complexity rules, + and verification steps, see Documentation/components/tools/index.rst (mkpasswd section). The format of the password file is: @@ -46,7 +49,7 @@ README Where: user: User name - x: Encrypted password + x: PBKDF2-HMAC-SHA256 hash (modular crypt format) uid: User ID (0 for now) gid: Group ID (0 for now) home: Login directory (/ for now) @@ -54,7 +57,7 @@ README /etc/group is a group file. It is not currently used. nsh> cat /etc/group - root:*:0:root,admin + root:*:0:root The format of the group file is: diff --git a/Documentation/platforms/sim/sim/boards/sim/index.rst b/Documentation/platforms/sim/sim/boards/sim/index.rst index c2487ba2ed937..8deeceb829165 100644 --- a/Documentation/platforms/sim/sim/boards/sim/index.rst +++ b/Documentation/platforms/sim/sim/boards/sim/index.rst @@ -1903,12 +1903,13 @@ This is a configuration with login password protection for NSH. .. note:: - This config has password protection enabled. The default login info is: + This config has password protection enabled. Before building, set a + password via ``make menuconfig`` at: **Board Selection** → + **Auto-generate /etc/passwd at build time** → **Root password**, or + enter it when ``make`` prompts. The default username is ``root``. + The password must meet complexity rules (8+ chars, upper, lower, digit, + special). Only the PBKDF2-HMAC-SHA256 hash is stored in ``/etc/passwd``. - * USERNAME: root - * PASSWORD: Administrator - - The encrypted password is retained in ``/etc/passwd``. You can disable the password protection by de-selecting ``CONFIG_NSH_CONSOLE_LOGIN=y``. @@ -2030,13 +2031,15 @@ credentials via ``make menuconfig``: * ``CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y`` * ``CONFIG_NSH_CONSOLE_LOGIN=y`` (required, otherwise login is not enforced) * ``CONFIG_BOARD_ETC_ROMFS_PASSWD_USER`` (default: ``root``) -* ``CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD`` (required, build fails if empty or shorter than 8 characters) +* ``CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD`` (required; minimum 8 characters + with uppercase, lowercase, digit, and special character. See + :ref:`mkpasswd_autogen`) -The password is hashed with TEA at build time by the host tool +The password is hashed with PBKDF2-HMAC-SHA256 at build time by the host tool ``tools/mkpasswd``; the plaintext is **not** stored in the firmware. For the full description of the build-time password generation mechanism, -TEA key configuration, file format, and verification steps, see +file format, and verification steps, see :ref:`mkpasswd_autogen`. The format of the password file is: @@ -2048,12 +2051,12 @@ The format of the password file is: Where: * user: User name -* x: Encrypted password +* x: PBKDF2-HMAC-SHA256 hash (modular crypt format) * uid: User ID (0 for now) * gid: Group ID (0 for now) * home: Login directory (/ for now) -For configuration, verification steps, and TEA key details, see +For configuration, verification steps, and password complexity rules, see :ref:`mkpasswd_autogen`. Login test inside the simulator @@ -2073,7 +2076,7 @@ Login test inside the simulator .. code:: console nsh> cat /etc/group - root:*:0:root,admin + root:*:0:root The format of the group file is: diff --git a/boards/Board.mk b/boards/Board.mk index 580a18caf73a7..aee2a13aef50e 100644 --- a/boards/Board.mk +++ b/boards/Board.mk @@ -36,21 +36,16 @@ $(ETCSRC): $(foreach raw,$(RCRAWS), $(if $(wildcard $(BOARD_DIR)$(DELIM)src$(DEL $(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) \ + $(Q) set -e; \ + mkdir -p $(ETCDIR)$(DELIM)$(CONFIG_ETC_ROMFSMOUNTPT); \ + $(TOPDIR)$(DELIM)tools$(DELIM)board_romfs_mkpasswd.sh \ + $(TOPDIR) $(ETCDIR)$(DELIM).romfs_passwd.txt \ + $(TOPDIR)$(DELIM)tools$(DELIM)mkpasswd$(HOSTEXEEXT) \ + $(ETCDIR)$(DELIM)$(CONFIG_ETC_ROMFSMOUNTPT)$(DELIM)passwd \ --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 + --home $(CONFIG_BOARD_ETC_ROMFS_PASSWD_HOME) endif $(Q) genromfs -f romfs.img -d $(ETCDIR)$(DELIM)$(CONFIG_ETC_ROMFSMOUNTPT) -V "NSHInitVol" $(Q) echo "#include " > $@ diff --git a/boards/Kconfig b/boards/Kconfig index 5196f18cbc205..407740111594d 100644 --- a/boards/Kconfig +++ b/boards/Kconfig @@ -5554,41 +5554,48 @@ config BOARD_ETC_ROMFS_PASSWD_ENABLE 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 + tools/mkpasswd (compiled from tools/mkpasswd.c) using + PBKDF2-HMAC-SHA256 — the same algorithm used at runtime in + apps/fsutils/passwd/passwd_encrypt.c. The plaintext password is never stored in the firmware image. - See Documentation/components/passwd_autogen.rst for details. + The iteration count is taken from + CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS (default 10000). Higher + values slow brute-force attacks but increase login latency on + low-MHz MCUs. + + See Documentation/components/tools/index.rst (mkpasswd section) for + details. if BOARD_ETC_ROMFS_PASSWD_ENABLE config BOARD_ETC_ROMFS_PASSWD_USER - string "Admin username" + string "Root 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" + string "Root password (required)" ---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'. + This value is hashed with PBKDF2-HMAC-SHA256 at build time; the + plaintext is NOT stored in the firmware image. There is no default; + set CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD in the board defconfig, + via menuconfig, or enter it when prompted during an interactive + build. Password must be at least 8 characters and contain: + uppercase letter, lowercase letter, digit, and special character. config BOARD_ETC_ROMFS_PASSWD_UID - int "Admin user ID" + int "Root user ID" default 0 config BOARD_ETC_ROMFS_PASSWD_GID - int "Admin group ID" + int "Root group ID" default 0 config BOARD_ETC_ROMFS_PASSWD_HOME - string "Admin home directory" + string "Root home directory" default "/" endif # BOARD_ETC_ROMFS_PASSWD_ENABLE diff --git a/boards/risc-v/esp32c3-legacy/esp32c3-legacy-devkit/src/etc/group b/boards/risc-v/esp32c3-legacy/esp32c3-legacy-devkit/src/etc/group index 1eca6970c9db2..f0565ff64b431 100644 --- a/boards/risc-v/esp32c3-legacy/esp32c3-legacy-devkit/src/etc/group +++ b/boards/risc-v/esp32c3-legacy/esp32c3-legacy-devkit/src/etc/group @@ -1 +1 @@ -root:*:0:root,admin +root:*:0:root diff --git a/boards/sim/sim/sim/src/etc/group b/boards/sim/sim/sim/src/etc/group index 1eca6970c9db2..f0565ff64b431 100644 --- a/boards/sim/sim/sim/src/etc/group +++ b/boards/sim/sim/sim/src/etc/group @@ -1 +1 @@ -root:*:0:root,admin +root:*:0:root diff --git a/cmake/nuttx_add_romfs.cmake b/cmake/nuttx_add_romfs.cmake index baca00fb8cf80..5776ec4d2ea59 100644 --- a/cmake/nuttx_add_romfs.cmake +++ b/cmake/nuttx_add_romfs.cmake @@ -292,6 +292,12 @@ function(process_all_directory_romfs) "to set a password.") endif() + if(CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS) + set(MKPASSWD_ITERATIONS ${CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS}) + else() + set(MKPASSWD_ITERATIONS 10000) + endif() + # Determine host executable suffix (.exe on Windows, empty elsewhere) if(CMAKE_HOST_WIN32) set(HOST_EXE_SUFFIX .exe) @@ -317,21 +323,6 @@ function(process_all_directory_romfs) 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} @@ -341,8 +332,8 @@ function(process_all_directory_romfs) --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} + "${CONFIG_BOARD_ETC_ROMFS_PASSWD_HOME}" --iterations + ${MKPASSWD_ITERATIONS} -o ${GENPASSWD_OUTPUT} DEPENDS ${MKPASSWD_BIN} ${NUTTX_DIR}/.config COMMENT "Generating /etc/passwd from Kconfig values") add_custom_target(generate_passwd DEPENDS ${GENPASSWD_OUTPUT}) diff --git a/cmake/savedefconfig.cmake b/cmake/savedefconfig.cmake index 692a5f1b2d87d..db6742a3621b5 100644 --- a/cmake/savedefconfig.cmake +++ b/cmake/savedefconfig.cmake @@ -76,7 +76,7 @@ list(SORT LINES) foreach(LINE IN LISTS LINES) decode_brackets(LINE) decode_semicolon(LINE) - if(NOT "${LINE}" MATCHES "^CONFIG_FSUTILS_PASSWD_KEY[0-9]" + if(NOT "${LINE}" MATCHES "^CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS=" AND NOT "${LINE}" MATCHES "^CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD=") file(APPEND ${OUTPUT_FILE} "${LINE}\n") endif() @@ -85,7 +85,8 @@ endforeach() if(PASSWD_AUTOGEN_ENABLED) message( WARNING - "CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD and CONFIG_FSUTILS_PASSWD_KEY1-4 " + "CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD and " + "CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS " "were intentionally excluded from defconfig by savedefconfig. Add them " "manually in local defconfig if needed.") endif() diff --git a/tools/Makefile.host b/tools/Makefile.host index 410dca776ad2f..3a2926cd659ed 100644 --- a/tools/Makefile.host +++ b/tools/Makefile.host @@ -107,7 +107,7 @@ ifdef HOSTEXEEXT mkversion: mkversion$(HOSTEXEEXT) endif -# mkpasswd - Generate a NuttX /etc/passwd entry with TEA-encrypted password +# mkpasswd - Generate a NuttX /etc/passwd entry with PBKDF2-HMAC-SHA256 hash mkpasswd$(HOSTEXEEXT): mkpasswd.c $(Q) $(HOSTCC) $(HOSTCFLAGS) -o mkpasswd$(HOSTEXEEXT) mkpasswd.c diff --git a/tools/Unix.mk b/tools/Unix.mk index d421205c1e6be..8424763d8d832 100644 --- a/tools/Unix.mk +++ b/tools/Unix.mk @@ -780,7 +780,7 @@ 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.bak -e '/^CONFIG_FSUTILS_PASSWD_KEY[0-9]/d' defconfig.tmp + $(Q) sed -i.bak -e '/^CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS=/d' defconfig.tmp $(Q) sed -i.bak -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 @@ -803,7 +803,7 @@ savedefconfig: apps_preconfig $(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: CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS was not saved in defconfig."; \ echo "WARNING: This is intentional to avoid leaking credentials. Add them manually in local defconfig if needed."; \ fi diff --git a/tools/board_romfs_mkpasswd.sh b/tools/board_romfs_mkpasswd.sh new file mode 100755 index 0000000000000..2256bf91c988c --- /dev/null +++ b/tools/board_romfs_mkpasswd.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# tools/board_romfs_mkpasswd.sh +# +# 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. +# +# Ensure the ROMFS root password is configured, then run mkpasswd. +# Arguments: +# board_romfs_mkpasswd.sh [mkpasswd args...] + +set -e + +TOPDIR=$1 +PASSFILE=$2 +MKPASSWD=$3 +OUTPUT=$4 +shift 4 + +CONFIG_FILE="${TOPDIR}/.config" + +read_int_config() { + local symbol=$1 + local default=$2 + local value + + value=$(grep "^${symbol}=" "${CONFIG_FILE}" 2>/dev/null | cut -d= -f2- | tr -d '"') + if [ -z "${value}" ]; then + echo "${default}" + else + echo "${value}" + fi +} + +ITERATIONS=$(read_int_config CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS 10000) + +"${TOPDIR}/tools/promptpasswd.sh" \ + --min 8 \ + --config CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD \ + --config-file "${CONFIG_FILE}" \ + --update-config \ + --prompt "ROMFS root password (min 8 characters): " \ + --output-file "${PASSFILE}" + +PASSWORD=$(cat "${PASSFILE}") +"${MKPASSWD}" --password "${PASSWORD}" \ + --iterations "${ITERATIONS}" \ + "$@" -o "${OUTPUT}" +rm -f "${PASSFILE}" diff --git a/tools/mkpasswd.c b/tools/mkpasswd.c index cfde64f3b405a..bd5a9f8fc7598 100644 --- a/tools/mkpasswd.c +++ b/tools/mkpasswd.c @@ -23,31 +23,27 @@ /**************************************************************************** * 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 + * PBKDF2-HMAC-SHA256 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 + * The hash format is identical to that used at runtime by: * apps/fsutils/passwd/passwd_encrypt.c + * apps/fsutils/passwd/passwd_verify.c * * Usage: * mkpasswd --user --password [options] [-o ] * * Options: - * --user Username (required) - * --password Plaintext password (required, not stored in output) - * --uid User ID (default: 0) - * --gid Group ID (default: 0) - * --home Home directory (default: /) - * --key1 TEA key word 1 (default: 0x12345678) - * --key2 TEA key word 2 (default: 0x9abcdef0) - * --key3 TEA key word 3 (default: 0x12345678) - * --key4 TEA key word 4 (default: 0x9abcdef0) - * -o Output file (default: stdout) + * --user Username (required) + * --password Plaintext password (required, not stored in output) + * --uid User ID (default: 0) + * --gid Group ID (default: 0) + * --home Home directory (default: /) + * --iterations PBKDF2 iterations (default: 10000) + * -o Output file (default: stdout) * * Output format (matches NuttX passwd file format): - * username:encrypted_hash:uid:gid:home + * username:$pbkdf2-sha256$$$:uid:gid:home * ****************************************************************************/ @@ -55,11 +51,6 @@ * 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 @@ -68,9 +59,13 @@ #include #include #include +#include #include #ifndef CONFIG_WINDOWS_NATIVE +# include +# include # include +# include #else # include #endif @@ -79,266 +74,503 @@ * 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 */ +#define MKPASSWD_NL "\n\n" +#define PASSWD_MCF_PREFIX "$pbkdf2-sha256$" +#define PASSWD_SALT_BYTES 16 +#define PASSWD_HASH_BYTES 32 +#define MAX_ENCRYPTED 96 +#define MAX_PASSWORD 256 +#define MIN_PASSWORD 8 +#define DEFAULT_ITERATIONS 10000 +#define MIN_ITERATIONS 1000 +#define MAX_ITERATIONS 200000 -/* 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 +static const char g_base64url[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; /**************************************************************************** - * Private Types + * Private Types (minimal SHA-256) ****************************************************************************/ -/* 8-byte block interpreted as bytes, 16-bit halves, or 32-bit words */ - -union block_u +struct sha256_ctx { - char b[8]; - uint16_t h[4]; - uint32_t l[2]; + uint32_t state[8]; + uint64_t bitlen; + uint8_t data[64]; + uint32_t datalen; }; /**************************************************************************** - * Private Functions + * Private Functions (SHA-256 + HMAC-SHA256 + PBKDF2) ****************************************************************************/ -/**************************************************************************** - * 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 uint32_t rotr32(uint32_t x, uint32_t n) +{ + return (x >> n) | (x << (32 - n)); +} -static void tea_encrypt(uint32_t *value, const uint32_t *key) +static void sha256_transform(struct sha256_ctx *ctx, + const uint8_t data[64]) { - uint32_t v0 = value[0]; - uint32_t v1 = value[1]; - uint32_t sum = 0; + static const uint32_t k[64] = + { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + }; + + uint32_t m[64]; + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; + uint32_t e; + uint32_t f; + uint32_t g; + uint32_t h; + uint32_t t1; + uint32_t t2; + uint32_t s0; + uint32_t s1; int i; - for (i = 0; i < 32; i++) + for (i = 0; i < 16; 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]); + m[i] = ((uint32_t)data[i * 4] << 24) | + ((uint32_t)data[i * 4 + 1] << 16) | + ((uint32_t)data[i * 4 + 2] << 8) | + ((uint32_t)data[i * 4 + 3]); } - value[0] = v0; - value[1] = v1; + for (i = 16; i < 64; i++) + { + s0 = rotr32(m[i - 15], 7) ^ rotr32(m[i - 15], 18) ^ + (m[i - 15] >> 3); + s1 = rotr32(m[i - 2], 17) ^ rotr32(m[i - 2], 19) ^ + (m[i - 2] >> 10); + + m[i] = m[i - 16] + s0 + m[i - 7] + s1; + } + + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + e = ctx->state[4]; + f = ctx->state[5]; + g = ctx->state[6]; + h = ctx->state[7]; + + for (i = 0; i < 64; i++) + { + t1 = h + (rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25)) + + ((e & f) ^ ((~e) & g)) + k[i] + m[i]; + t2 = (rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22)) + + ((a & b) ^ (a & c) ^ (b & c)); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; + ctx->state[4] += e; + ctx->state[5] += f; + ctx->state[6] += g; + ctx->state[7] += h; } -/**************************************************************************** - * 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 void sha256_init(struct sha256_ctx *ctx) +{ + ctx->datalen = 0; + ctx->bitlen = 0; + ctx->state[0] = 0x6a09e667; + ctx->state[1] = 0xbb67ae85; + ctx->state[2] = 0x3c6ef372; + ctx->state[3] = 0xa54ff53a; + ctx->state[4] = 0x510e527f; + ctx->state[5] = 0x9b05688c; + ctx->state[6] = 0x1f83d9ab; + ctx->state[7] = 0x5be0cd19; +} -static char passwd_base64(uint8_t binary) +static void sha256_update(struct sha256_ctx *ctx, + const uint8_t *data, size_t len) { - binary &= 63; + size_t i; - if (binary < 26) + for (i = 0; i < len; i++) { - return (char)('A' + binary); + ctx->data[ctx->datalen] = data[i]; + ctx->datalen++; + if (ctx->datalen == 64) + { + sha256_transform(ctx, ctx->data); + ctx->bitlen += 512; + ctx->datalen = 0; + } } +} - binary -= 26; - if (binary < 26) +static void sha256_final(struct sha256_ctx *ctx, uint8_t hash[32]) +{ + uint32_t i; + uint32_t j; + + i = ctx->datalen; + + if (ctx->datalen < 56) { - return (char)('a' + binary); + ctx->data[i++] = 0x80; + while (i < 56) + { + ctx->data[i++] = 0x00; + } } + else + { + ctx->data[i++] = 0x80; + while (i < 64) + { + ctx->data[i++] = 0x00; + } - binary -= 26; - if (binary < 10) + sha256_transform(ctx, ctx->data); + memset(ctx->data, 0, 56); + } + + ctx->bitlen += (uint64_t)ctx->datalen * 8; + ctx->data[63] = (uint8_t)(ctx->bitlen); + ctx->data[62] = (uint8_t)(ctx->bitlen >> 8); + ctx->data[61] = (uint8_t)(ctx->bitlen >> 16); + ctx->data[60] = (uint8_t)(ctx->bitlen >> 24); + ctx->data[59] = (uint8_t)(ctx->bitlen >> 32); + ctx->data[58] = (uint8_t)(ctx->bitlen >> 40); + ctx->data[57] = (uint8_t)(ctx->bitlen >> 48); + ctx->data[56] = (uint8_t)(ctx->bitlen >> 56); + sha256_transform(ctx, ctx->data); + + for (i = 0; i < 4; i++) { - return (char)('0' + binary); + for (j = 0; j < 8; j++) + { + hash[i + (j * 4)] = (uint8_t)((ctx->state[j] >> + (24 - i * 8)) & 0xff); + } } +} + +static void hmac_sha256(const uint8_t *key, size_t keylen, + const uint8_t *data, size_t datalen, + uint8_t mac[32]) +{ + struct sha256_ctx ctx; + uint8_t k_ipad[64]; + uint8_t k_opad[64]; + uint8_t tk[32]; + size_t i; - binary -= 10; - if (binary == 0) + if (keylen > 64) { - return '+'; + sha256_init(&ctx); + sha256_update(&ctx, key, keylen); + sha256_final(&ctx, tk); + key = tk; + keylen = 32; } - return '/'; -} + memset(k_ipad, 0, sizeof(k_ipad)); + memset(k_opad, 0, sizeof(k_opad)); + memcpy(k_ipad, key, keylen); + memcpy(k_opad, key, keylen); -/**************************************************************************** - * 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). - * - ****************************************************************************/ + for (i = 0; i < 64; i++) + { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + + sha256_init(&ctx); + sha256_update(&ctx, k_ipad, 64); + sha256_update(&ctx, data, datalen); + sha256_final(&ctx, mac); -static int passwd_encrypt(const char *password, - const uint32_t *key, - char encrypted[MAX_ENCRYPTED + 1]) + sha256_init(&ctx); + sha256_update(&ctx, k_opad, 64); + sha256_update(&ctx, mac, 32); + sha256_final(&ctx, mac); +} + +static int pbkdf2_hmac_sha256(const uint8_t *pass, size_t passlen, + const uint8_t *salt, size_t saltlen, + uint32_t iterations, + uint8_t *out, size_t outlen) { - 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); + uint8_t u[32]; + uint8_t t[32]; + uint8_t saltblk[64]; + size_t generated = 0; + uint32_t block; + uint32_t i; + uint32_t j; + + if (iterations == 0 || outlen == 0 || saltlen + 4 > sizeof(saltblk)) + { return -1; } - src = password; - dest = encrypted; - *dest = '\0'; - remainder = 0; - nbits = 0; - - for (; remaining > 0; remaining -= gulpsize) + for (block = 1; generated < outlen; block++) { - /* Copy up to 8 bytes into the block, padding the rest with spaces */ + memcpy(saltblk, salt, saltlen); + saltblk[saltlen + 0] = (uint8_t)((block >> 24) & 0xff); + saltblk[saltlen + 1] = (uint8_t)((block >> 16) & 0xff); + saltblk[saltlen + 2] = (uint8_t)((block >> 8) & 0xff); + saltblk[saltlen + 3] = (uint8_t)(block & 0xff); + + hmac_sha256(pass, passlen, saltblk, saltlen + 4, u); + memcpy(t, u, sizeof(t)); - gulpsize = 8; - if (gulpsize > remaining) + for (i = 1; i < iterations; i++) { - gulpsize = remaining; + hmac_sha256(pass, passlen, u, sizeof(u), u); + for (j = 0; j < 32; j++) + { + t[j] ^= u[j]; + } } - for (i = 0; i < gulpsize; i++) + if (outlen - generated >= 32) { - value.b[i] = *src++; + memcpy(out + generated, t, 32); + generated += 32; } - - for (; i < 8; i++) + else { - value.b[i] = ' '; + memcpy(out + generated, t, outlen - generated); + generated = outlen; } + } + + return 0; +} + +static int fill_random(uint8_t *buf, size_t len) +{ +#ifndef CONFIG_WINDOWS_NATIVE + ssize_t nread; + int fd; - /* TEA-encrypt the block in-place */ +# ifdef SYS_getrandom + nread = getrandom(buf, len, 0); + if (nread == (ssize_t)len) + { + return 0; + } +# endif - tea_encrypt(value.l, key); + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + { + return -1; + } - /* Stream-encode the four 16-bit half-words into base64 */ + nread = read(fd, buf, len); + close(fd); - tmp = remainder; + return nread == (ssize_t)len ? 0 : -1; +#else + (void)buf; + (void)len; + return -1; +#endif +} - for (i = 0; i < 4; i++) - { - tmp = ((uint32_t)value.h[i] << nbits) | tmp; - nbits += 16; +static int base64url_encode(const uint8_t *in, size_t inlen, + char *out, size_t outlen) +{ + uint32_t acc = 0; + size_t i; + size_t o = 0; + int bits = 0; - while (nbits >= 6) + for (i = 0; i < inlen; i++) + { + acc = (acc << 8) | in[i]; + bits += 8; + + while (bits >= 6) + { + if (o + 1 >= outlen) { - *dest++ = passwd_base64((uint8_t)(tmp & 0x3f)); - tmp >>= 6; - nbits -= 6; + return -1; } - } - remainder = (uint8_t)tmp; - *dest = '\0'; + bits -= 6; + out[o++] = g_base64url[(acc >> bits) & 0x3f]; + } } - /* Flush any remaining bits */ + if (bits > 0) + { + if (o + 1 >= outlen) + { + return -1; + } + + out[o++] = g_base64url[(acc << (6 - bits)) & 0x3f]; + } - if (nbits > 0) + if (o >= outlen) { - *dest++ = passwd_base64(remainder); - *dest = '\0'; + return -1; } + out[o] = '\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) +static int validate_password_complexity(const char *password) { - char *endptr; - unsigned long val; + const char *specials = "!@#$%^&*()_+-=[]{}|;:,.<>?"; + const char *p; + int has_upper = 0; + int has_lower = 0; + int has_digit = 0; + int has_special = 0; - if (str == NULL || *str == '\0') + if (strlen(password) < MIN_PASSWORD) { + fprintf(stderr, "\nError: password must be at least 8 characters\n\n"); return -1; } - errno = 0; - val = strtoul(str, &endptr, 0); /* base 0: auto-detect 0x prefix */ - if (errno != 0 || *endptr != '\0') + if (strlen(password) > MAX_PASSWORD) + { + fprintf(stderr, "\nError: password must be at most %d characters\n\n", + MAX_PASSWORD); + return -1; + } + + for (p = password; *p; 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(specials, *p)) + { + has_special = 1; + } + } + + if (!has_upper) { + fprintf(stderr, + "\nError: password must contain at least one uppercase " + "letter (A-Z)\n\n"); + return -1; + } + + if (!has_lower) + { + fprintf(stderr, + "\nError: password must contain at least one lowercase " + "letter (a-z)\n\n"); + return -1; + } + + if (!has_digit) + { + fprintf(stderr, + "\nError: password must contain at least one digit " + "(0-9)\n\n"); + return -1; + } + + if (!has_special) + { + fprintf(stderr, + "\nError: password must contain at least one special " + "character (!@#$%%^&*()_+-=[]{}|;:,.<>?)\n\n"); 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 passwd_hash(const char *password, + uint32_t iterations, + char encrypted[MAX_ENCRYPTED + 1]) +{ + 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; + + passlen = strlen(password); + + if (fill_random(salt, sizeof(salt)) < 0) + { + fputs(MKPASSWD_NL, stderr); + fprintf(stderr, "mkpasswd: cannot obtain random salt\n"); + return -1; + } + + if (pbkdf2_hmac_sha256((const uint8_t *)password, passlen, + salt, sizeof(salt), iterations, + hash, sizeof(hash)) < 0) + { + return -1; + } + + if (base64url_encode(salt, sizeof(salt), salt_b64, + sizeof(salt_b64)) < 0 || + base64url_encode(hash, sizeof(hash), hash_b64, + sizeof(hash_b64)) < 0) + { + return -1; + } + + ret = snprintf(encrypted, MAX_ENCRYPTED + 1, + PASSWD_MCF_PREFIX "%u$%s$%s", + iterations, salt_b64, hash_b64); + if (ret < 0 || (size_t)ret > MAX_ENCRYPTED) + { + return -1; + } + + return 0; +} static int mkdir_p(const char *path) { @@ -354,8 +586,6 @@ static int mkdir_p(const char *path) len = strlen(tmp); - /* Strip trailing slash */ - if (len > 0 && tmp[len - 1] == '/') { tmp[len - 1] = '\0'; @@ -384,30 +614,22 @@ static int mkdir_p(const char *path) return 0; } -/**************************************************************************** - * Name: show_usage - ****************************************************************************/ - static void show_usage(const char *progname) { fprintf(stderr, "Usage: %s --user --password [options] [-o ]\n" "\n" "Options:\n" - " --user Username (required)\n" - " --password Plaintext password (required)\n" - " --uid User ID (default: 0)\n" - " --gid Group ID (default: 0)\n" - " --home Home directory (default: /)\n" - " --key1 TEA key word 1 (default: 0x%08x)\n" - " --key2 TEA key word 2 (default: 0x%08x)\n" - " --key3 TEA key word 3 (default: 0x%08x)\n" - " --key4 TEA key word 4 (default: 0x%08x)\n" - " -o Output file (default: stdout)\n" + " --user Username (required)\n" + " --password Plaintext password (required)\n" + " --uid User ID (default: 0)\n" + " --gid Group ID (default: 0)\n" + " --home Home directory (default: /)\n" + " --iterations PBKDF2 iterations (default: %d)\n" + " -o Output file (default: stdout)\n" "\n" - "Output format: username:encrypted_hash:uid:gid:home\n", - progname, - DEFAULT_KEY1, DEFAULT_KEY2, DEFAULT_KEY3, DEFAULT_KEY4); + "Output format: username:$pbkdf2-sha256$...:uid:gid:home\n", + progname, DEFAULT_ITERATIONS); } /**************************************************************************** @@ -416,23 +638,25 @@ static void show_usage(const char *progname) 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]; + const char *user; + const char *password; + const char *home; + const char *outpath; FILE *out; - int i; - int ret; + char encrypted[MAX_ENCRYPTED + 1]; + int uid; + int gid; + uint32_t iterations; + int i; + int ret; - /* Simple long-option parser (avoids getopt_long portability concerns) */ + user = NULL; + password = NULL; + home = "/"; + outpath = NULL; + uid = 0; + gid = 0; + iterations = DEFAULT_ITERATIONS; for (i = 1; i < argc; i++) { @@ -456,41 +680,9 @@ int main(int argc, char **argv) { 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) + else if (strcmp(argv[i], "--iterations") == 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; - } + iterations = (uint32_t)strtoul(argv[++i], NULL, 10); } else if ((strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--output") == 0) && i + 1 < argc) @@ -505,16 +697,16 @@ int main(int argc, char **argv) } else { + fputs(MKPASSWD_NL, stderr); fprintf(stderr, "mkpasswd: unknown option: %s\n", argv[i]); show_usage(argv[0]); return 1; } } - /* Validate required arguments */ - if (user == NULL) { + fputs(MKPASSWD_NL, stderr); fprintf(stderr, "mkpasswd: --user is required\n"); show_usage(argv[0]); return 1; @@ -522,70 +714,39 @@ int main(int argc, char **argv) if (password == NULL) { + fputs(MKPASSWD_NL, stderr); fprintf(stderr, "mkpasswd: --password is required\n"); show_usage(argv[0]); return 1; } - if (password[0] == '\0') + if (validate_password_complexity(password) < 0) { - fprintf(stderr, "mkpasswd: --password must not be empty\n"); return 1; } - if (strlen(password) < MIN_PASSWORD) + if (iterations < MIN_ITERATIONS || iterations > MAX_ITERATIONS) { + fputs(MKPASSWD_NL, stderr); fprintf(stderr, - "mkpasswd: --password must be at least %d characters\n", - MIN_PASSWORD); + "mkpasswd: --iterations must be between %d and %d\n", + MIN_ITERATIONS, MAX_ITERATIONS); 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); + ret = passwd_hash(password, iterations, encrypted); if (ret < 0) { return 1; } - /* Open the output stream */ - if (outpath != NULL) { - /* Create parent directory if it does not exist */ + char *dir; + char *last; - char *dir = strdup(outpath); - char *last = strrchr(dir, '/'); + dir = strdup(outpath); + last = strrchr(dir, '/'); if (last != NULL && last != dir) { @@ -598,6 +759,7 @@ int main(int argc, char **argv) out = fopen(outpath, "w"); if (out == NULL) { + fputs(MKPASSWD_NL, stderr); fprintf(stderr, "mkpasswd: cannot open output file '%s': %s\n", outpath, strerror(errno)); return 1; @@ -608,12 +770,6 @@ int main(int argc, char **argv) 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) diff --git a/tools/promptpasswd.sh b/tools/promptpasswd.sh new file mode 100755 index 0000000000000..bdbe8d99d7e1d --- /dev/null +++ b/tools/promptpasswd.sh @@ -0,0 +1,164 @@ +#!/usr/bin/env bash +# tools/promptpasswd.sh +# +# 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. +# +# Prompt for a Kconfig password when it is unset or invalid. If no +# terminal is available, print an error and exit. +# +# Usage: +# promptpasswd.sh --min [--config ] [--config-file ] +# [--update-config] [--prompt ] [--output-file ] + +set -e + +MIN=8 +VALUE="" +PROMPT="Password: " +CONFIG_SYMBOL="" +CONFIG_FILE=".config" +UPDATE_CONFIG=0 +OUTPUT_FILE="" + +while [ $# -gt 0 ]; do + case "$1" in + --min) + MIN=$2 + shift 2 + ;; + --value) + VALUE=$2 + shift 2 + ;; + --prompt) + PROMPT=$2 + shift 2 + ;; + --config) + CONFIG_SYMBOL=$2 + shift 2 + ;; + --config-file) + CONFIG_FILE=$2 + shift 2 + ;; + --update-config) + UPDATE_CONFIG=1 + shift + ;; + --output-file) + OUTPUT_FILE=$2 + shift 2 + ;; + *) + echo "promptpasswd.sh: unknown option: $1" >&2 + exit 1 + ;; + esac +done + +validate_password() { + local pw="$1" + local ok=0 + + if [ ${#pw} -lt "${MIN}" ]; then + echo "Error: password must be at least ${MIN} characters" >&2 + ok=1 + fi + + if ! printf '%s' "$pw" | grep -q '[A-Z]'; then + echo "Error: password must contain at least one uppercase letter (A-Z)" >&2 + ok=1 + fi + + if ! printf '%s' "$pw" | grep -q '[a-z]'; then + echo "Error: password must contain at least one lowercase letter (a-z)" >&2 + ok=1 + fi + + if ! printf '%s' "$pw" | grep -q '[0-9]'; then + echo "Error: password must contain at least one digit (0-9)" >&2 + ok=1 + fi + + if ! printf '%s' "$pw" | grep -q '[^a-zA-Z0-9]'; then + echo "Error: password must contain at least one special character" \ + "(!@#\$%^&*()_+-=[]{}|;:,.<>?)" >&2 + ok=1 + fi + + return "${ok}" +} + +if [ -n "${CONFIG_SYMBOL}" ] && [ -z "${VALUE}" ] && [ -f "${CONFIG_FILE}" ]; then + VALUE=$(grep "^${CONFIG_SYMBOL}=" "${CONFIG_FILE}" 2>/dev/null | cut -d= -f2- | tr -d '"') +fi + +if [ -n "${VALUE}" ] && validate_password "${VALUE}"; then + if [ -n "${OUTPUT_FILE}" ]; then + umask 077 + printf '%s' "${VALUE}" > "${OUTPUT_FILE}" + else + printf '%s' "${VALUE}" + fi + exit 0 +fi + +# Make recipe shells are not connected to the terminal on stdin, so test /dev/tty +# instead of [ -t 0 ] when deciding whether an interactive prompt is possible. + +INTERACTIVE=0 +if [ -r /dev/tty ] && [ -w /dev/tty ]; then + INTERACTIVE=1 +fi + +if [ "${INTERACTIVE}" -eq 0 ]; then + echo "" >&2 + if [ -n "${CONFIG_SYMBOL}" ]; then + echo "ERROR: ${CONFIG_SYMBOL} must be at least ${MIN} characters and" >&2 + echo "contain uppercase, lowercase, digit, and special character." >&2 + else + echo "ERROR: Password must be at least ${MIN} characters and contain" >&2 + echo "uppercase, lowercase, digit, and special character." >&2 + fi + echo "Set it with 'make menuconfig' or edit .config, then rebuild." >&2 + exit 1 +fi + +PASSWORD="" +while true; do + printf '%s' "${PROMPT}" >/dev/tty + IFS= read -r -s PASSWORD /dev/tty + if validate_password "${PASSWORD}"; then + break + fi + echo "Please try again." >&2 + PASSWORD="" +done + +if [ "${UPDATE_CONFIG}" -eq 1 ] && [ -n "${CONFIG_SYMBOL}" ]; then + kconfig-tweak --file "${CONFIG_FILE}" --set-str "${CONFIG_SYMBOL}" "${PASSWORD}" +fi + +if [ -n "${OUTPUT_FILE}" ]; then + umask 077 + printf '%s' "${PASSWORD}" > "${OUTPUT_FILE}" +else + printf '%s' "${PASSWORD}" +fi