From 7ab3fa7abb2828e206ae2f9b6cc8598a00df3af4 Mon Sep 17 00:00:00 2001 From: Abhishek Mishra Date: Thu, 25 Jun 2026 16:55:03 +0000 Subject: [PATCH] !fsutils/passwd: Replace TEA with PBKDF2-HMAC-SHA256 Add apps/crypto/pbkdf2, migrate passwd encrypt/verify to modular crypt format with complexity validation, share base64url helpers, and add pbkdf2_test for RFC 6070 vector coverage. Change NSH_LOGIN_USERNAME default to root and remove fixed-login password defaults. BREAKING CHANGE: TEA-encoded /etc/passwd entries no longer verify. Regenerate each entry after upgrading. Pair with the nuttx host mkpasswd changes in apache/nuttx#19209. When CONFIG_NSH_LOGIN_FIXED=y, set CONFIG_NSH_LOGIN_PASSWORD in the board defconfig or menuconfig; there is no default password. Signed-off-by: Abhishek Mishra --- crypto/pbkdf2/CMakeLists.txt | 32 +++ crypto/pbkdf2/Kconfig | 15 ++ crypto/pbkdf2/Make.defs | 27 ++ crypto/pbkdf2/Makefile | 29 +++ crypto/pbkdf2/pbkdf2_hmac_sha256.c | 396 +++++++++++++++++++++++++++++ crypto/pbkdf2/pbkdf2_hmac_sha256.h | 64 +++++ fsutils/passwd/CMakeLists.txt | 10 +- fsutils/passwd/Kconfig | 31 ++- fsutils/passwd/Makefile | 3 +- fsutils/passwd/passwd.h | 26 +- fsutils/passwd/passwd_base64.c | 187 ++++++++++++++ fsutils/passwd/passwd_base64.h | 45 ++++ fsutils/passwd/passwd_encrypt.c | 277 ++++++++++---------- fsutils/passwd/passwd_verify.c | 124 +++++++-- include/fsutils/passwd.h | 11 +- nshlib/Kconfig | 13 +- nshlib/nsh.h | 11 +- testing/pbkdf2/CMakeLists.txt | 40 +++ testing/pbkdf2/Kconfig | 34 +++ testing/pbkdf2/Make.defs | 25 ++ testing/pbkdf2/Makefile | 35 +++ testing/pbkdf2/pbkdf2_test.c | 288 +++++++++++++++++++++ 22 files changed, 1520 insertions(+), 203 deletions(-) create mode 100644 crypto/pbkdf2/CMakeLists.txt create mode 100644 crypto/pbkdf2/Kconfig create mode 100644 crypto/pbkdf2/Make.defs create mode 100644 crypto/pbkdf2/Makefile create mode 100644 crypto/pbkdf2/pbkdf2_hmac_sha256.c create mode 100644 crypto/pbkdf2/pbkdf2_hmac_sha256.h create mode 100644 fsutils/passwd/passwd_base64.c create mode 100644 fsutils/passwd/passwd_base64.h create mode 100644 testing/pbkdf2/CMakeLists.txt create mode 100644 testing/pbkdf2/Kconfig create mode 100644 testing/pbkdf2/Make.defs create mode 100644 testing/pbkdf2/Makefile create mode 100644 testing/pbkdf2/pbkdf2_test.c diff --git a/crypto/pbkdf2/CMakeLists.txt b/crypto/pbkdf2/CMakeLists.txt new file mode 100644 index 00000000000..698adf7abb8 --- /dev/null +++ b/crypto/pbkdf2/CMakeLists.txt @@ -0,0 +1,32 @@ +# ############################################################################## +# apps/crypto/pbkdf2/CMakeLists.txt +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_CRYPTO_PBKDF2 OR CONFIG_FSUTILS_PASSWD) + nuttx_add_library(pbkdf2 STATIC) + + target_sources(pbkdf2 PRIVATE pbkdf2_hmac_sha256.c) + target_include_directories(pbkdf2 PUBLIC ${CMAKE_CURRENT_LIST_DIR}) + + if(CONFIG_CRYPTO_MBEDTLS) + nuttx_add_dependencies(TARGET pbkdf2 DEPENDS mbedtls) + endif() +endif() diff --git a/crypto/pbkdf2/Kconfig b/crypto/pbkdf2/Kconfig new file mode 100644 index 00000000000..424363c6928 --- /dev/null +++ b/crypto/pbkdf2/Kconfig @@ -0,0 +1,15 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config CRYPTO_PBKDF2 + bool "PBKDF2-HMAC-SHA256 support" + default n + ---help--- + Enable a small, heap-free PBKDF2-HMAC-SHA256 implementation in + apps/crypto/pbkdf2. When Mbed TLS with MBEDTLS_PKCS5_C is also + enabled, that backend is used; otherwise a self-contained SHA-256 + and HMAC implementation is compiled. + + Selected automatically when password file support is enabled. diff --git a/crypto/pbkdf2/Make.defs b/crypto/pbkdf2/Make.defs new file mode 100644 index 00000000000..0c56877119d --- /dev/null +++ b/crypto/pbkdf2/Make.defs @@ -0,0 +1,27 @@ +############################################################################ +# apps/crypto/pbkdf2/Make.defs +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_CRYPTO_PBKDF2),) +CONFIGURED_APPS += $(APPDIR)/crypto/pbkdf2 +else ifneq ($(CONFIG_FSUTILS_PASSWD),) +CONFIGURED_APPS += $(APPDIR)/crypto/pbkdf2 +endif diff --git a/crypto/pbkdf2/Makefile b/crypto/pbkdf2/Makefile new file mode 100644 index 00000000000..ae5b589d9dd --- /dev/null +++ b/crypto/pbkdf2/Makefile @@ -0,0 +1,29 @@ +############################################################################ +# apps/crypto/pbkdf2/Makefile +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +CSRCS = pbkdf2_hmac_sha256.c + +CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/crypto/pbkdf2 + +include $(APPDIR)/Application.mk diff --git a/crypto/pbkdf2/pbkdf2_hmac_sha256.c b/crypto/pbkdf2/pbkdf2_hmac_sha256.c new file mode 100644 index 00000000000..97381dae3b9 --- /dev/null +++ b/crypto/pbkdf2/pbkdf2_hmac_sha256.c @@ -0,0 +1,396 @@ +/**************************************************************************** + * apps/crypto/pbkdf2/pbkdf2_hmac_sha256.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include "pbkdf2_hmac_sha256.h" + +#if defined(CONFIG_CRYPTO_MBEDTLS) && defined(CONFIG_MBEDTLS_PKCS5_C) +# include +# include +#endif + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define PBKDF2_SHA256_DIGESTLEN 32 +#define PBKDF2_SHA256_BLOCKLEN 64 +#define PBKDF2_SHA256_MAXSALT 60 + +/**************************************************************************** + * Private Types (self-contained SHA-256 + HMAC backend) + ****************************************************************************/ + +struct pbkdf2_sha256_ctx_s +{ + uint32_t state[8]; + uint64_t bitlen; + uint8_t data[64]; + uint32_t datalen; +}; + +/**************************************************************************** + * Private Functions (self-contained SHA-256 + HMAC backend) + ****************************************************************************/ + +static uint32_t pbkdf2_rotr32(uint32_t x, uint32_t n) +{ + return (x >> n) | (x << (32 - n)); +} + +static void pbkdf2_sha256_transform(FAR struct pbkdf2_sha256_ctx_s *ctx, + FAR const uint8_t data[64]) +{ + 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 < 16; i++) + { + 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]); + } + + for (i = 16; i < 64; i++) + { + s0 = pbkdf2_rotr32(m[i - 15], 7) ^ + pbkdf2_rotr32(m[i - 15], 18) ^ + (m[i - 15] >> 3); + s1 = pbkdf2_rotr32(m[i - 2], 17) ^ + pbkdf2_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 + (pbkdf2_rotr32(e, 6) ^ pbkdf2_rotr32(e, 11) ^ + pbkdf2_rotr32(e, 25)) + + ((e & f) ^ ((~e) & g)) + k[i] + m[i]; + t2 = (pbkdf2_rotr32(a, 2) ^ pbkdf2_rotr32(a, 13) ^ + pbkdf2_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; +} + +static void pbkdf2_sha256_init(FAR struct pbkdf2_sha256_ctx_s *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 void pbkdf2_sha256_update(FAR struct pbkdf2_sha256_ctx_s *ctx, + FAR const uint8_t *data, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + { + ctx->data[ctx->datalen] = data[i]; + ctx->datalen++; + if (ctx->datalen == 64) + { + pbkdf2_sha256_transform(ctx, ctx->data); + ctx->bitlen += 512; + ctx->datalen = 0; + } + } +} + +static void pbkdf2_sha256_final(FAR struct pbkdf2_sha256_ctx_s *ctx, + FAR uint8_t hash[32]) +{ + uint32_t i; + uint32_t j; + + i = ctx->datalen; + + if (ctx->datalen < 56) + { + ctx->data[i++] = 0x80; + while (i < 56) + { + ctx->data[i++] = 0x00; + } + } + else + { + ctx->data[i++] = 0x80; + while (i < 64) + { + ctx->data[i++] = 0x00; + } + + pbkdf2_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); + pbkdf2_sha256_transform(ctx, ctx->data); + + for (i = 0; i < 4; i++) + { + for (j = 0; j < 8; j++) + { + hash[i + (j * 4)] = (uint8_t)((ctx->state[j] >> + (24 - i * 8)) & 0xff); + } + } +} + +static void pbkdf2_hmac_sha256_once(FAR const uint8_t *key, size_t keylen, + FAR const uint8_t *data, size_t datalen, + FAR uint8_t mac[32]) +{ + struct pbkdf2_sha256_ctx_s ctx; + uint8_t k_ipad[64]; + uint8_t k_opad[64]; + uint8_t tk[32]; + size_t i; + + if (keylen > 64) + { + pbkdf2_sha256_init(&ctx); + pbkdf2_sha256_update(&ctx, key, keylen); + pbkdf2_sha256_final(&ctx, tk); + key = tk; + keylen = 32; + } + + memset(k_ipad, 0, sizeof(k_ipad)); + memset(k_opad, 0, sizeof(k_opad)); + memcpy(k_ipad, key, keylen); + memcpy(k_opad, key, keylen); + + for (i = 0; i < 64; i++) + { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + + pbkdf2_sha256_init(&ctx); + pbkdf2_sha256_update(&ctx, k_ipad, 64); + pbkdf2_sha256_update(&ctx, data, datalen); + pbkdf2_sha256_final(&ctx, mac); + + pbkdf2_sha256_init(&ctx); + pbkdf2_sha256_update(&ctx, k_opad, 64); + pbkdf2_sha256_update(&ctx, mac, 32); + pbkdf2_sha256_final(&ctx, mac); + + memset(k_ipad, 0, sizeof(k_ipad)); + memset(k_opad, 0, sizeof(k_opad)); + memset(tk, 0, sizeof(tk)); +} + +static int pbkdf2_hmac_sha256_sw(FAR const uint8_t *pass, size_t passlen, + FAR const uint8_t *salt, size_t saltlen, + uint32_t iterations, + FAR uint8_t *out, size_t outlen) +{ + uint8_t u[PBKDF2_SHA256_DIGESTLEN]; + uint8_t t[PBKDF2_SHA256_DIGESTLEN]; + uint8_t saltblk[PBKDF2_SHA256_MAXSALT + 4]; + size_t generated = 0; + uint32_t block; + uint32_t i; + uint32_t j; + + if (iterations == 0 || outlen == 0) + { + return -EINVAL; + } + + if (saltlen > PBKDF2_SHA256_MAXSALT) + { + return -EINVAL; + } + + for (block = 1; generated < outlen; block++) + { + 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); + + pbkdf2_hmac_sha256_once(pass, passlen, saltblk, saltlen + 4, u); + memcpy(t, u, sizeof(t)); + + for (i = 1; i < iterations; i++) + { + pbkdf2_hmac_sha256_once(pass, passlen, u, sizeof(u), u); + for (j = 0; j < PBKDF2_SHA256_DIGESTLEN; j++) + { + t[j] ^= u[j]; + } + } + + if (outlen - generated >= PBKDF2_SHA256_DIGESTLEN) + { + memcpy(out + generated, t, PBKDF2_SHA256_DIGESTLEN); + generated += PBKDF2_SHA256_DIGESTLEN; + } + else + { + memcpy(out + generated, t, outlen - generated); + generated = outlen; + } + } + + memset(u, 0, sizeof(u)); + memset(t, 0, sizeof(t)); + memset(saltblk, 0, sizeof(saltblk)); + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int pbkdf2_hmac_sha256(FAR const uint8_t *pass, size_t passlen, + FAR const uint8_t *salt, size_t saltlen, + uint32_t iterations, + FAR uint8_t *out, size_t outlen) +{ +#if defined(CONFIG_CRYPTO_MBEDTLS) && defined(CONFIG_MBEDTLS_PKCS5_C) + + const mbedtls_md_info_t *info; + int ret; + + if (pass == NULL || salt == NULL || out == NULL) + { + return -EINVAL; + } + + if (iterations == 0 || outlen == 0) + { + return -EINVAL; + } + + info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + if (info == NULL) + { + return -ENOTSUP; + } + + ret = mbedtls_pkcs5_pbkdf2_hmac(info, pass, passlen, salt, saltlen, + iterations, (uint32_t)outlen, out); + return ret == 0 ? 0 : -EIO; + +#else + + if (pass == NULL || salt == NULL || out == NULL) + { + return -EINVAL; + } + + return pbkdf2_hmac_sha256_sw(pass, passlen, salt, saltlen, + iterations, out, outlen); + +#endif +} diff --git a/crypto/pbkdf2/pbkdf2_hmac_sha256.h b/crypto/pbkdf2/pbkdf2_hmac_sha256.h new file mode 100644 index 00000000000..c13318a83f4 --- /dev/null +++ b/crypto/pbkdf2/pbkdf2_hmac_sha256.h @@ -0,0 +1,64 @@ +/**************************************************************************** + * apps/crypto/pbkdf2/pbkdf2_hmac_sha256.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __APPS_CRYPTO_PBKDF2_PBKDF2_HMAC_SHA256_H +#define __APPS_CRYPTO_PBKDF2_PBKDF2_HMAC_SHA256_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: pbkdf2_hmac_sha256 + * + * Description: + * Derive key material using PBKDF2 with HMAC-SHA256 (RFC 2898 / RFC 8018). + * Uses no heap allocation. + * + * Input Parameters: + * pass - Password bytes + * passlen - Length of password + * salt - Salt bytes + * saltlen - Length of salt (must be <= 60) + * iterations - PBKDF2 iteration count (must be > 0) + * out - Output buffer + * outlen - Desired output length in bytes + * + * Returned Value: + * 0 on success; a negated errno value on failure. + * + ****************************************************************************/ + +int pbkdf2_hmac_sha256(FAR const uint8_t *pass, size_t passlen, + FAR const uint8_t *salt, size_t saltlen, + uint32_t iterations, + FAR uint8_t *out, size_t outlen); + +#endif /* __APPS_CRYPTO_PBKDF2_PBKDF2_HMAC_SHA256_H */ diff --git a/fsutils/passwd/CMakeLists.txt b/fsutils/passwd/CMakeLists.txt index e2e0ebce488..1ba2bbd885c 100644 --- a/fsutils/passwd/CMakeLists.txt +++ b/fsutils/passwd/CMakeLists.txt @@ -23,7 +23,15 @@ if(CONFIG_FSUTILS_PASSWD) set(CSRCS) - list(APPEND CSRCS passwd_verify.c passwd_find.c passwd_encrypt.c) + list(APPEND CSRCS passwd_verify.c passwd_find.c passwd_encrypt.c + passwd_base64.c) + + target_include_directories( + apps PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../../crypto/pbkdf2) + + if(CONFIG_CRYPTO_PBKDF2 OR CONFIG_FSUTILS_PASSWD) + nuttx_add_dependencies(TARGET apps DEPENDS pbkdf2) + endif() if(NOT CONFIG_FSUTILS_PASSWD_READONLY) list( diff --git a/fsutils/passwd/Kconfig b/fsutils/passwd/Kconfig index 4f40349fcea..0d86a35f155 100644 --- a/fsutils/passwd/Kconfig +++ b/fsutils/passwd/Kconfig @@ -6,8 +6,13 @@ config FSUTILS_PASSWD bool "Password file support" default n + select CRYPTO_PBKDF2 ---help--- - Enables support for /etc/passwd file access routines + Enables support for /etc/passwd file access routines. + + NOTE: Password hashes use PBKDF2-HMAC-SHA256 (modular crypt format). + Existing TEA-encrypted /etc/passwd entries are NOT compatible and + must be regenerated. if FSUTILS_PASSWD @@ -23,20 +28,14 @@ config FSUTILS_PASSWD_IOBUFFER_SIZE int "Allocated I/O buffer size" default 512 -config FSUTILS_PASSWD_KEY1 - hex "Encryption key value 1" - default 0x12345678 - -config FSUTILS_PASSWD_KEY2 - hex "Encryption key value 2" - default 0x9abcdef0 - -config FSUTILS_PASSWD_KEY3 - hex "Encryption key value 3" - default 0x12345678 - -config FSUTILS_PASSWD_KEY4 - hex "Encryption key value 4" - default 0x9abcdef0 + config FSUTILS_PASSWD_PBKDF2_ITERATIONS + int "Default PBKDF2 iteration count for new passwords" + default 10000 + range 1000 200000 + ---help--- + Number of PBKDF2-HMAC-SHA256 iterations applied when setting a new + password. Higher values slow brute-force attacks but also increase + login latency on low-MHz MCUs. The iteration count is stored in each + hash string, so changing this option only affects newly-set passwords. endif # FSUTILS_PASSWD diff --git a/fsutils/passwd/Makefile b/fsutils/passwd/Makefile index b11148ffd6d..2f29900d387 100644 --- a/fsutils/passwd/Makefile +++ b/fsutils/passwd/Makefile @@ -25,7 +25,8 @@ include $(APPDIR)/Make.defs # Password file access library ifeq ($(CONFIG_FSUTILS_PASSWD),y) -CSRCS += passwd_verify.c passwd_find.c passwd_encrypt.c +CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/crypto/pbkdf2 +CSRCS += passwd_verify.c passwd_find.c passwd_encrypt.c passwd_base64.c ifneq ($(CONFIG_FSUTILS_PASSWD_READONLY),y) CSRCS += passwd_adduser.c passwd_deluser.c passwd_update.c passwd_append.c CSRCS += passwd_delete.c passwd_lock.c diff --git a/fsutils/passwd/passwd.h b/fsutils/passwd/passwd.h index 6dd1785e385..e617cef37c8 100644 --- a/fsutils/passwd/passwd.h +++ b/fsutils/passwd/passwd.h @@ -36,17 +36,18 @@ * Pre-processor Definitions ****************************************************************************/ -#define MAX_ENCRYPTED 48 /* Maximum size of a password (encrypted, ASCII) */ -#define MAX_USERNAME 48 /* Maximum size of a username */ -#define MAX_RECORD (MAX_USERNAME + MAX_ENCRYPTED + 1) +/* MCF format: $pbkdf2-sha256$$$ */ -/* The TEA incryption algorithm generates 8 bytes of encrypted data per - * 8 bytes of unencrypted data. The encrypted presentation is base64 which - * is 8-bits of ASCII for each 6 bits of data. That is a 3-to-4 expansion - * ratio. MAX_ENCRYPTED must be a multiple of 8 bytes. - */ +#define PASSWD_MCF_PREFIX "$pbkdf2-sha256$" +#define PASSWD_SALT_BYTES 16 +#define PASSWD_HASH_BYTES 32 -#define MAX_PASSWORD (3 * MAX_ENCRYPTED / 4) +/* 15 + 6 + 1 + 22 + 1 + 43 = 88 bytes for default parameters */ + +#define MAX_ENCRYPTED 96 +#define MAX_USERNAME 48 +#define MAX_RECORD (MAX_USERNAME + MAX_ENCRYPTED + 1) +#define MAX_PASSWORD 256 /**************************************************************************** * Public Types @@ -55,7 +56,7 @@ struct passwd_s { off_t offset; /* File offset (start of record) */ - char encrypted[MAX_ENCRYPTED + 1]; /* Encrtyped password in file */ + char encrypted[MAX_ENCRYPTED + 1]; /* Password hash in file */ }; /**************************************************************************** @@ -94,10 +95,11 @@ void passwd_unlock(FAR sem_t *sem); * Name: passwd_encrypt * * Description: - * Encrypt a password. Currently uses the Tiny Encryption Algorithm. + * Hash a password with PBKDF2-HMAC-SHA256 and encode the result in modular + * crypt format for storage in /etc/passwd. * * Input Parameters: - * password -- The password string to be encrypted + * password -- The password string to be hashed * * Returned Value: * Zero (OK) is returned on success; a negated errno value is returned on diff --git a/fsutils/passwd/passwd_base64.c b/fsutils/passwd/passwd_base64.c new file mode 100644 index 00000000000..3b3d0a89cdd --- /dev/null +++ b/fsutils/passwd/passwd_base64.c @@ -0,0 +1,187 @@ +/**************************************************************************** + * apps/fsutils/passwd/passwd_base64.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include "passwd_base64.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* RFC 4648 section 5 base64url alphabet (no padding). */ + +static const char g_base64url[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int passwd_base64url_val(char c) +{ + if (c >= 'A' && c <= 'Z') + { + return c - 'A'; + } + + if (c >= 'a' && c <= 'z') + { + return c - 'a' + 26; + } + + if (c >= '0' && c <= '9') + { + return c - '0' + 52; + } + + if (c == '-') + { + return 62; + } + + if (c == '_') + { + return 63; + } + + return -1; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: passwd_base64url_encode + * + * Description: + * Encode binary data as unpadded base64url (RFC 4648 section 5). + * + ****************************************************************************/ + +int passwd_base64url_encode(FAR const uint8_t *in, size_t inlen, + FAR char *out, size_t outlen) +{ + uint32_t acc = 0; + size_t i; + size_t o = 0; + int bits = 0; + + for (i = 0; i < inlen; i++) + { + acc = (acc << 8) | in[i]; + bits += 8; + + while (bits >= 6) + { + if (o + 1 >= outlen) + { + return -E2BIG; + } + + bits -= 6; + out[o++] = g_base64url[(acc >> bits) & 0x3f]; + } + } + + if (bits > 0) + { + if (o + 1 >= outlen) + { + return -E2BIG; + } + + out[o++] = g_base64url[(acc << (6 - bits)) & 0x3f]; + } + + if (o >= outlen) + { + return -E2BIG; + } + + out[o] = '\0'; + return OK; +} + +/**************************************************************************** + * Name: passwd_base64url_decode + * + * Description: + * Decode unpadded base64url (RFC 4648 section 5). + * + ****************************************************************************/ + +int passwd_base64url_decode(FAR const char *in, + FAR uint8_t *out, size_t outmax, + FAR size_t *outlen) +{ + uint32_t acc = 0; + size_t o = 0; + int bits = 0; + int v; + + *outlen = 0; + + while (*in != '\0') + { + if (*in == '$' || *in == ':') + { + break; + } + + v = passwd_base64url_val(*in++); + if (v < 0) + { + return -EINVAL; + } + + acc = (acc << 6) | (uint32_t)v; + bits += 6; + + if (bits >= 8) + { + bits -= 8; + if (o >= outmax) + { + return -E2BIG; + } + + out[o++] = (uint8_t)((acc >> bits) & 0xff); + } + } + + if (bits >= 6) + { + return -EINVAL; + } + + *outlen = o; + return OK; +} diff --git a/fsutils/passwd/passwd_base64.h b/fsutils/passwd/passwd_base64.h new file mode 100644 index 00000000000..ff0bbddef8c --- /dev/null +++ b/fsutils/passwd/passwd_base64.h @@ -0,0 +1,45 @@ +/**************************************************************************** + * apps/fsutils/passwd/passwd_base64.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __APPS_FSUTILS_PASSWD_PASSWD_BASE64_H +#define __APPS_FSUTILS_PASSWD_PASSWD_BASE64_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +int passwd_base64url_encode(FAR const uint8_t *in, size_t inlen, + FAR char *out, size_t outlen); +int passwd_base64url_decode(FAR const char *in, + FAR uint8_t *out, size_t outmax, + FAR size_t *outlen); + +#endif /* __APPS_FSUTILS_PASSWD_PASSWD_BASE64_H */ diff --git a/fsutils/passwd/passwd_encrypt.c b/fsutils/passwd/passwd_encrypt.c index 50e516f77c2..b247113a93d 100644 --- a/fsutils/passwd/passwd_encrypt.c +++ b/fsutils/passwd/passwd_encrypt.c @@ -25,85 +25,145 @@ ****************************************************************************/ #include +#include -#include +#include +#include +#include #include #include -#include +#include +#include -#include +#include #include "passwd.h" +#include "passwd_base64.h" /**************************************************************************** - * Private Data + * Pre-processor Definitions ****************************************************************************/ -/* This should be better protected */ +#ifndef CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS +# define CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS 10000 +#endif -static uint32_t g_tea_key[4] = -{ - CONFIG_FSUTILS_PASSWD_KEY1, - CONFIG_FSUTILS_PASSWD_KEY2, - CONFIG_FSUTILS_PASSWD_KEY3, - CONFIG_FSUTILS_PASSWD_KEY4 -}; +#define PASSWD_MIN_LENGTH 8 -/**************************************************************************** - * Private Functions - ****************************************************************************/ +static const char g_password_specials[] = + "!@#$%^&*()_+-=[]{}|;:,.<>?"; /**************************************************************************** - * Name: passwd_base64 - * - * Description: - * Encode a 5 bit value as a base64 character. - * - * Input Parameters: - * binary - 5 bit value - * - * Returned Value: - * The ASCII base64 character. Must not return the field delimiter ':' - * + * Private Functions ****************************************************************************/ -static char passwd_base64(uint8_t binary) +static int validate_password_complexity(FAR const char *password) { - /* 0-26 -> 'A'-'Z' */ + FAR const char *p; + size_t passlen; + int has_upper = 0; + int has_lower = 0; + int has_digit = 0; + int has_special = 0; + + passlen = strlen(password); + if (passlen < PASSWD_MIN_LENGTH) + { + _err("ERROR: password must be at least %d characters\n", + PASSWD_MIN_LENGTH); + return -EINVAL; + } - binary &= 63; - if (binary < 26) + if (passlen > MAX_PASSWORD) { - return 'A' + binary; + _err("ERROR: password must be at most %d characters\n", MAX_PASSWORD); + return -EINVAL; } - /* 26-51 -> 'a'-'z' */ + for (p = password; *p != '\0'; p++) + { + if (isupper((unsigned char)*p)) + { + has_upper = 1; + } + else if (islower((unsigned char)*p)) + { + has_lower = 1; + } + else if (isdigit((unsigned char)*p)) + { + has_digit = 1; + } + else if (strchr(g_password_specials, *p) != NULL) + { + has_special = 1; + } + } - binary -= 26; - if (binary < 26) + if (!has_upper) { - return 'a' + binary; + _err("ERROR: password must contain at least one uppercase " + "letter (A-Z)\n"); + return -EINVAL; } - /* 52->61 -> '0'-'9' */ + if (!has_lower) + { + _err("ERROR: password must contain at least one lowercase " + "letter (a-z)\n"); + return -EINVAL; + } + + if (!has_digit) + { + _err("ERROR: password must contain at least one digit (0-9)\n"); + return -EINVAL; + } - binary -= 26; - if (binary < 10) + if (!has_special) { - return '0' + binary; + _err("ERROR: password must contain at least one special " + "character (!@#$%%^&*()_+-=[]{}|;:,.<>?)\n"); + return -EINVAL; } - /* 62 -> '+' */ + return OK; +} + +/**************************************************************************** + * Name: passwd_fill_random + * + * Description: + * Fill a buffer with random bytes using getrandom() or /dev/urandom. + * + ****************************************************************************/ + +static int passwd_fill_random(FAR uint8_t *buf, size_t len) +{ + ssize_t nread; + int fd; + + nread = getrandom(buf, len, 0); + if (nread == (ssize_t)len) + { + return OK; + } - binary -= 10; - if (binary == 0) + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { - return '+'; + return -errno; } - /* 63 -> '/' */ + nread = read(fd, buf, len); + close(fd); - return '/'; + if (nread != (ssize_t)len) + { + return nread < 0 ? -errno : -EIO; + } + + return OK; } /**************************************************************************** @@ -114,108 +174,65 @@ static char passwd_base64(uint8_t binary) * Name: passwd_encrypt * * Description: - * Encrypt a password. Currently uses the Tiny Encryption Algorithm. - * - * Input Parameters: - * password -- The password string to be encrypted - * - * Returned Value: - * Zero (OK) is returned on success; a negated errno value is returned on - * failure. + * Hash a password with PBKDF2-HMAC-SHA256 and encode as modular crypt + * format: $pbkdf2-sha256$$$ * ****************************************************************************/ int passwd_encrypt(FAR const char *password, char encrypted[MAX_ENCRYPTED + 1]) { - union - { - char b[8]; - uint16_t h[4]; - uint32_t l[2]; - } value; - - FAR const char *src; - FAR char *bptr; - FAR char *dest; - uint32_t tmp; - uint8_t remainder; - int remaining; - int gulpsize; - int nbits; - int i; - - /* How long is the password? */ - - remaining = strlen(password); - if (remaining > MAX_PASSWORD) + uint8_t salt[PASSWD_SALT_BYTES]; + uint8_t hash[PASSWD_HASH_BYTES]; + char salt_b64[32]; + char hash_b64[48]; + size_t passlen; + int ret; + + ret = validate_password_complexity(password); + if (ret < 0) { - return -E2BIG; + return ret; } - /* Convert the password in 8-byte TEA cycles */ - - src = password; - dest = encrypted; - *dest = '\0'; + passlen = strlen(password); - remainder = 0; - nbits = 0; - - for (; remaining > 0; remaining -= gulpsize) + ret = passwd_fill_random(salt, sizeof(salt)); + if (ret < 0) { - /* Copy bytes */ - - gulpsize = sizeof(value.b); - if (gulpsize > remaining) - { - gulpsize = remaining; - } - - bptr = value.b; - for (i = 0; i < gulpsize; i++) - { - *bptr++ = *src++; - } - - /* Pad with spaces if necessary */ - - for (; i < sizeof(value.b); i++) - { - *bptr++ = ' '; - } - - /* Perform the conversion for this cycle */ - - tea_encrypt(value.l, g_tea_key); - - /* Generate the base64 output string from this cycle */ - - tmp = remainder; + return ret; + } - for (i = 0; i < 4; i++) - { - tmp = (uint32_t)value.h[i] << nbits | tmp; - nbits += 16; - - while (nbits >= 6) - { - *dest++ = passwd_base64((uint8_t)(tmp & 0x3f)); - tmp >>= 6; - nbits -= 6; - } - } + ret = pbkdf2_hmac_sha256((FAR const uint8_t *)password, passlen, + salt, sizeof(salt), + CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS, + hash, sizeof(hash)); + if (ret < 0) + { + return ret; + } - remainder = (uint8_t)tmp; - *dest = '\0'; + ret = passwd_base64url_encode(salt, sizeof(salt), salt_b64, + sizeof(salt_b64)); + if (ret < 0) + { + return ret; } - /* Handle any remainder */ + ret = passwd_base64url_encode(hash, sizeof(hash), hash_b64, + sizeof(hash_b64)); + if (ret < 0) + { + return ret; + } - if (nbits > 0) + ret = snprintf(encrypted, MAX_ENCRYPTED + 1, + PASSWD_MCF_PREFIX "%u$%s$%s", + CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS, + salt_b64, hash_b64); + if (ret < 0 || (size_t)ret > MAX_ENCRYPTED) { - *dest++ = passwd_base64(remainder); - *dest = '\0'; + return -E2BIG; } return OK; diff --git a/fsutils/passwd/passwd_verify.c b/fsutils/passwd/passwd_verify.c index 2c63f37591d..0b6c988cdfa 100644 --- a/fsutils/passwd/passwd_verify.c +++ b/fsutils/passwd/passwd_verify.c @@ -24,11 +24,105 @@ * Included Files ****************************************************************************/ +#include + +#include +#include +#include #include -#include + +#include #include "fsutils/passwd.h" #include "passwd.h" +#include "passwd_base64.h" + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: passwd_verify_hash + * + * Description: + * Verify a password against a stored PBKDF2-SHA256 modular crypt hash. + * Rejects any hash not in $pbkdf2-sha256$ format. + * + * Returned Value: + * 0 on match, -1 on mismatch or parse error. + * + ****************************************************************************/ + +static int passwd_verify_hash(FAR const char *stored, + FAR const char *password) +{ + FAR const char *p; + FAR const char *salt_b64; + FAR const char *hash_b64; + char *endptr; + uint8_t salt[PASSWD_SALT_BYTES]; + uint8_t expected[PASSWD_HASH_BYTES]; + uint8_t actual[PASSWD_HASH_BYTES]; + size_t saltlen; + size_t hashlen; + size_t passlen; + unsigned long iterations; + int ret; + + if (strncmp(stored, PASSWD_MCF_PREFIX, strlen(PASSWD_MCF_PREFIX)) != 0) + { + return -1; + } + + p = stored + strlen(PASSWD_MCF_PREFIX); + iterations = strtoul(p, &endptr, 10); + if (endptr == p || *endptr != '$' || iterations < 1 || + iterations > 200000) + { + return -1; + } + + salt_b64 = endptr + 1; + hash_b64 = strchr(salt_b64, '$'); + if (hash_b64 == NULL) + { + return -1; + } + + ret = passwd_base64url_decode(salt_b64, salt, sizeof(salt), &saltlen); + if (ret < 0 || saltlen == 0) + { + return -1; + } + + ret = passwd_base64url_decode(hash_b64 + 1, expected, sizeof(expected), + &hashlen); + if (ret < 0 || hashlen != PASSWD_HASH_BYTES) + { + return -1; + } + + passlen = strlen(password); + if (passlen == 0 || passlen > MAX_PASSWORD) + { + return -1; + } + + ret = pbkdf2_hmac_sha256((FAR const uint8_t *)password, passlen, + salt, saltlen, (uint32_t)iterations, + actual, sizeof(actual)); + if (ret < 0) + { + return -1; + } + + if (timingsafe_bcmp(actual, expected, sizeof(expected)) != 0) + { + return -1; + } + + return 0; +} /**************************************************************************** * Public Functions @@ -39,53 +133,33 @@ * * Description: * Return true if the username exists in the /etc/passwd file and if the - * password matches the user password in that failed. - * - * Input Parameters: + * password matches the user password in that file. * * Returned Value: - * One (1) is returned on success match, Zero (OK) is returned on an - * unsuccessful match; a negated errno value is returned on any other - * failure. + * Zero (0) is returned on a successful match, -1 on mismatch or invalid + * hash format; a negated errno value is returned on other failures. * ****************************************************************************/ int passwd_verify(FAR const char *username, FAR const char *password) { struct passwd_s passwd; - char encrypted[MAX_ENCRYPTED + 1]; PASSWD_SEM_DECL(sem); int ret; - /* Get exclusive access to the /etc/passwd file */ - ret = passwd_lock(&sem); if (ret < 0) { return ret; } - /* Verify that the username exists in the /etc/passwd file */ - ret = passwd_find(username, &passwd); if (ret < 0) { - /* The username does not exist in the /etc/passwd file */ - goto errout_with_lock; } - /* Encrypt the provided password */ - - ret = passwd_encrypt(password, encrypted); - if (ret < 0) - { - goto errout_with_lock; - } - - /* Compare the encrypted passwords */ - - ret = (strcmp(passwd.encrypted, encrypted) == 0) ? 1 : 0; + ret = passwd_verify_hash(passwd.encrypted, password); errout_with_lock: passwd_unlock(sem); diff --git a/include/fsutils/passwd.h b/include/fsutils/passwd.h index 996bcf5097f..93fed255db8 100644 --- a/include/fsutils/passwd.h +++ b/include/fsutils/passwd.h @@ -36,9 +36,9 @@ /* passwd_verify() return value tests */ -#define PASSWORD_VERIFY_MATCH(ret) (ret == 1) -#define PASSWORD_VERIFY_NOMATCH(ret) (ret == 0) -#define PASSWORD_VERIFY_ERROR(ret) (ret < 0) +#define PASSWORD_VERIFY_MATCH(ret) ((ret) == 0) +#define PASSWORD_VERIFY_NOMATCH(ret) ((ret) == -1) +#define PASSWORD_VERIFY_ERROR(ret) ((ret) < -1) /**************************************************************************** * Public Function Prototypes @@ -115,9 +115,8 @@ int passwd_update(FAR const char *username, FAR const char *password); * password - The password to be verified * * Returned Value: - * One (1) is returned on success match, Zero (OK) is returned on an - * unsuccessful match; a negated errno value is returned on any other - * failure. + * Zero (0) is returned on a successful match, -1 on mismatch or invalid + * hash format; a negated errno value is returned on other failures. * ****************************************************************************/ diff --git a/nshlib/Kconfig b/nshlib/Kconfig index 1fc34cb213e..d138a214ee1 100644 --- a/nshlib/Kconfig +++ b/nshlib/Kconfig @@ -1222,17 +1222,20 @@ endchoice # Verification method config NSH_LOGIN_USERNAME string "Login username" - default "admin" + default "root" depends on !NSH_LOGIN_PASSWD ---help--- - Login user name. Default: "admin" + Login user name. Default: "root" config NSH_LOGIN_PASSWORD string "Login password" - default "Administrator" - depends on !NSH_LOGIN_PASSWD + depends on NSH_LOGIN_FIXED ---help--- - Login password: Default: "Administrator" + The plaintext login password used when fixed username/password + verification is selected (CONFIG_NSH_LOGIN_FIXED). There is no + default; set CONFIG_NSH_LOGIN_PASSWORD in the board defconfig, + via menuconfig, or enter it when prompted during an interactive + build. config NSH_LOGIN_FAILDELAY int "Login failure delay" diff --git a/nshlib/nsh.h b/nshlib/nsh.h index 2fb3cf867cc..b09eb9b1e58 100644 --- a/nshlib/nsh.h +++ b/nshlib/nsh.h @@ -266,8 +266,9 @@ * If CONFIG_NSH_TELNET_LOGIN is defined, then these additional * options may be specified: * - * CONFIG_NSH_LOGIN_USERNAME - Login user name. Default: "admin" - * CONFIG_NSH_LOGIN_PASSWORD - Login password: Default: "Administrator" + * CONFIG_NSH_LOGIN_USERNAME - Login user name. Default: "root" + * CONFIG_NSH_LOGIN_PASSWORD - Login password (required when using fixed + * credentials; no default is provided). * CONFIG_NSH_LOGIN_FAILCOUNT - Number of login retry attempts. * Default 3. */ @@ -275,11 +276,7 @@ #ifdef CONFIG_NSH_TELNET_LOGIN # ifndef CONFIG_NSH_LOGIN_USERNAME -# define CONFIG_NSH_LOGIN_USERNAME "admin" -# endif - -# ifndef CONFIG_NSH_LOGIN_PASSWORD -# define CONFIG_NSH_LOGIN_PASSWORD "nuttx" +# define CONFIG_NSH_LOGIN_USERNAME "root" # endif # ifndef CONFIG_NSH_LOGIN_FAILCOUNT diff --git a/testing/pbkdf2/CMakeLists.txt b/testing/pbkdf2/CMakeLists.txt new file mode 100644 index 00000000000..3120106c2a3 --- /dev/null +++ b/testing/pbkdf2/CMakeLists.txt @@ -0,0 +1,40 @@ +# ############################################################################## +# apps/testing/pbkdf2/CMakeLists.txt +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_TESTING_PBKDF2) + nuttx_add_application( + NAME + pbkdf2_test + PRIORITY + ${CONFIG_TESTING_PBKDF2_PRIORITY} + STACKSIZE + ${CONFIG_TESTING_PBKDF2_STACKSIZE} + MODULE + ${CONFIG_TESTING_PBKDF2} + SRCS + pbkdf2_test.c + INCLUDE_DIRECTORIES + ${CMAKE_CURRENT_LIST_DIR}/../../crypto/pbkdf2 + ${CMAKE_CURRENT_LIST_DIR}/../../fsutils/passwd + DEPENDS + pbkdf2) +endif() diff --git a/testing/pbkdf2/Kconfig b/testing/pbkdf2/Kconfig new file mode 100644 index 00000000000..79b0a8e9cf0 --- /dev/null +++ b/testing/pbkdf2/Kconfig @@ -0,0 +1,34 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config TESTING_PBKDF2 + tristate "PBKDF2 and passwd hash test" + default n + depends on FSUTILS_PASSWD && CRYPTO_PBKDF2 + ---help--- + Enable the PBKDF2-HMAC-SHA256 unit test (apps/testing/pbkdf2). + Always runs RFC 6070 SHA-256 vectors. The passwd_encrypt / + passwd_verify round-trip runs only when FSUTILS_PASSWD_READONLY + is disabled and DEV_URANDOM is enabled; otherwise it is skipped + with an explanatory message. + +if TESTING_PBKDF2 + +config TESTING_PBKDF2_PRIORITY + int "pbkdf2_test task priority" + default 100 + +config TESTING_PBKDF2_STACKSIZE + int "pbkdf2_test stack size" + default DEFAULT_TASK_STACKSIZE + +config TESTING_PBKDF2_SLOW_VECTOR + bool "Run RFC 6070 vector with 16777216 iterations" + default n + ---help--- + Include RFC 6070 test vector #4 (16,777,216 iterations). This + takes a long time on embedded targets; enable only for manual runs. + +endif diff --git a/testing/pbkdf2/Make.defs b/testing/pbkdf2/Make.defs new file mode 100644 index 00000000000..415edd25d12 --- /dev/null +++ b/testing/pbkdf2/Make.defs @@ -0,0 +1,25 @@ +############################################################################ +# apps/testing/pbkdf2/Make.defs +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_TESTING_PBKDF2),) +CONFIGURED_APPS += $(APPDIR)/testing/pbkdf2 +endif diff --git a/testing/pbkdf2/Makefile b/testing/pbkdf2/Makefile new file mode 100644 index 00000000000..e6bda6ee74b --- /dev/null +++ b/testing/pbkdf2/Makefile @@ -0,0 +1,35 @@ +############################################################################ +# apps/testing/pbkdf2/Makefile +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +PRIORITY = $(CONFIG_TESTING_PBKDF2_PRIORITY) +STACKSIZE = $(CONFIG_TESTING_PBKDF2_STACKSIZE) +MODULE = $(CONFIG_TESTING_PBKDF2) + +MAINSRC = pbkdf2_test.c +PROGNAME = pbkdf2_test + +CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/crypto/pbkdf2 +CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/fsutils/passwd + +include $(APPDIR)/Application.mk diff --git a/testing/pbkdf2/pbkdf2_test.c b/testing/pbkdf2/pbkdf2_test.c new file mode 100644 index 00000000000..ccc211fc03e --- /dev/null +++ b/testing/pbkdf2/pbkdf2_test.c @@ -0,0 +1,288 @@ +/**************************************************************************** + * apps/testing/pbkdf2/pbkdf2_test.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include +#include + +#include "passwd.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define TEST_USERNAME "testuser" +#define TEST_PASSWORD "MySecret1!" +#define WRONG_PASSWORD "WrongPass" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct pbkdf2_vector_s +{ + FAR const char *password; + size_t passwordlen; + FAR const char *salt; + size_t saltlen; + uint32_t iterations; + size_t dklen; + FAR const uint8_t *expected; +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* PBKDF2-HMAC-SHA256 test vectors from RFC 6070 (appendix B). */ + +static const uint8_t g_vector1[] = +{ + 0x12, 0x0f, 0xb6, 0xcf, 0xfc, 0xf8, 0xb3, 0x2c, + 0x43, 0xe7, 0x22, 0x52, 0x56, 0xc4, 0xf8, 0x37, + 0xa8, 0x65, 0x48, 0xc9 +}; + +static const uint8_t g_vector2[] = +{ + 0xae, 0x4d, 0x0c, 0x95, 0xaf, 0x6b, 0x46, 0xd3, + 0x2d, 0x0a, 0xdf, 0xf9, 0x28, 0xf0, 0x6d, 0xd0, + 0x2a, 0x30, 0x3f, 0x8e +}; + +static const uint8_t g_vector3[] = +{ + 0xc5, 0xe4, 0x78, 0xd5, 0x92, 0x88, 0xc8, 0x41, + 0xaa, 0x53, 0x0d, 0xb6, 0x84, 0x5c, 0x4c, 0x8d, + 0x96, 0x28, 0x93, 0xa0 +}; + +static const uint8_t g_vector4[] = +{ + 0xcf, 0x81, 0xc6, 0x6f, 0xe8, 0xcf, 0xc0, 0x4d, + 0x1f, 0x31, 0xec, 0xb6, 0x5d, 0xab, 0x40, 0x89, + 0xf7, 0xf1, 0x79, 0xe8 +}; + +static const uint8_t g_vector5[] = +{ + 0x34, 0x8c, 0x89, 0xdb, 0xcb, 0xd3, 0x2b, 0x2f, + 0x32, 0xd8, 0x14, 0xb8, 0x11, 0x6e, 0x84, 0xcf, + 0x2b, 0x17, 0x34, 0x7e, 0xbc, 0x18, 0x00, 0x18, + 0x1c +}; + +static const uint8_t g_vector6[] = +{ + 0x89, 0xb6, 0x9d, 0x05, 0x16, 0xf8, 0x29, 0x89, + 0x3c, 0x69, 0x62, 0x26, 0x65, 0x0a, 0x86, 0x87 +}; + +static const struct pbkdf2_vector_s g_vectors[] = +{ + { + "password", 8, + "salt", 4, + 1, 20, + g_vector1 + }, + { + "password", 8, + "salt", 4, + 2, 20, + g_vector2 + }, + { + "password", 8, + "salt", 4, + 4096, 20, + g_vector3 + }, + { + "password", 8, + "salt", 4, + 16777216, 20, + g_vector4 + }, + { + "passwordPASSWORDpassword", 24, + "saltSALTsaltSALTsaltSALTsaltSALTsalt", 36, + 4096, 25, + g_vector5 + }, + { + "pass\0word", 9, + "sa\0lt", 5, + 4096, 16, + g_vector6 + }, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int test_pbkdf2_vectors(void) +{ + FAR const struct pbkdf2_vector_s *vec; + uint8_t output[32]; + int failures = 0; + int i; + int ret; + + for (i = 0; i < (int)(sizeof(g_vectors) / sizeof(g_vectors[0])); i++) + { + vec = &g_vectors[i]; + +#ifndef CONFIG_TESTING_PBKDF2_SLOW_VECTOR + if (vec->iterations > 100000) + { + printf("pbkdf2_test: skipping slow vector %d (enable " + "TESTING_PBKDF2_SLOW_VECTOR)\n", i); + continue; + } +#endif + + ret = pbkdf2_hmac_sha256((FAR const uint8_t *)vec->password, + vec->passwordlen, + (FAR const uint8_t *)vec->salt, + vec->saltlen, + vec->iterations, + output, vec->dklen); + if (ret != 0) + { + printf("pbkdf2_test: vector %d pbkdf2_hmac_sha256 failed: %d\n", + i, ret); + failures++; + continue; + } + + if (memcmp(output, vec->expected, vec->dklen) != 0) + { + printf("pbkdf2_test: vector %d output mismatch\n", i); + failures++; + } + } + + if (failures == 0) + { + printf("pbkdf2_test: RFC 6070 SHA-256 vectors OK\n"); + } + + return failures; +} + +static int test_passwd_roundtrip(void) +{ +#if defined(CONFIG_FSUTILS_PASSWD_READONLY) + printf("pbkdf2_test: skipping passwd round-trip " + "(FSUTILS_PASSWD_READONLY)\n"); + return 0; +#elif !defined(CONFIG_DEV_URANDOM) + printf("pbkdf2_test: skipping passwd round-trip " + "(DEV_URANDOM)\n"); + return 0; +#else + FILE *stream; + char encrypted[MAX_ENCRYPTED + 1]; + int ret; + + unlink(CONFIG_FSUTILS_PASSWD_PATH); + + ret = passwd_encrypt(TEST_PASSWORD, encrypted); + if (ret < 0) + { + printf("pbkdf2_test: passwd_encrypt failed: %d\n", ret); + return 1; + } + + stream = fopen(CONFIG_FSUTILS_PASSWD_PATH, "w"); + if (stream == NULL) + { + printf("pbkdf2_test: cannot write %s: %d\n", + CONFIG_FSUTILS_PASSWD_PATH, errno); + return 1; + } + + if (fprintf(stream, "%s:%s:0:0:/\n", TEST_USERNAME, encrypted) < 0) + { + printf("pbkdf2_test: fprintf failed: %d\n", errno); + fclose(stream); + return 1; + } + + fclose(stream); + + ret = passwd_verify(TEST_USERNAME, TEST_PASSWORD); + if (ret != 0) + { + printf("pbkdf2_test: passwd_verify match failed: %d (expected 0)\n", + ret); + return 1; + } + + ret = passwd_verify(TEST_USERNAME, WRONG_PASSWORD); + if (ret != -1) + { + printf("pbkdf2_test: passwd_verify mismatch failed: %d " + "(expected -1)\n", ret); + return 1; + } + + printf("pbkdf2_test: passwd round-trip OK\n"); + return 0; +#endif +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + int failures = 0; + + (void)argc; + (void)argv; + + failures += test_pbkdf2_vectors(); + failures += test_passwd_roundtrip(); + + if (failures != 0) + { + printf("pbkdf2_test: FAILED (%d)\n", failures); + return 1; + } + + printf("pbkdf2_test: PASSED\n"); + return 0; +}