This commit introduces a new command, password_argon2, which allows
verifying a user-provided password against a stored Argon2 hash.

Signed-off-by: Gary Lin <[email protected]>
---
 grub-core/Makefile.core.def          |   5 +
 grub-core/commands/password_argon2.c | 247 +++++++++++++++++++++++++++
 2 files changed, 252 insertions(+)
 create mode 100644 grub-core/commands/password_argon2.c

diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index fa4bc54aa..f8593b33d 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1087,6 +1087,11 @@ module = {
   common = commands/password.c;
 };
 
+module = {
+  name = password_argon2;
+  common = commands/password_argon2.c;
+};
+
 module = {
   name = password_pbkdf2;
   common = commands/password_pbkdf2.c;
diff --git a/grub-core/commands/password_argon2.c 
b/grub-core/commands/password_argon2.c
new file mode 100644
index 000000000..a245a84b4
--- /dev/null
+++ b/grub-core/commands/password_argon2.c
@@ -0,0 +1,247 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *  Copyright (C) 2025  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/auth.h>
+#include <grub/crypto.h>
+#include <grub/list.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/env.h>
+#include <grub/normal.h>
+#include <grub/dl.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static grub_dl_t my_mod;
+
+struct argon2_password
+{
+  grub_uint64_t iterations;
+  grub_uint64_t memory;
+  grub_uint64_t parallelism;
+  grub_uint8_t *salt;
+  grub_size_t saltlen;
+  grub_uint8_t *expected;
+  grub_size_t buflen;
+};
+
+static grub_err_t
+check_password (const char *user, const char *entered, void *pin)
+{
+  grub_uint8_t *buf;
+  grub_uint64_t param[4];
+  struct argon2_password *pass = pin;
+  gcry_err_code_t err;
+  grub_err_t ret;
+
+  param[0] = pass->buflen;
+  param[1] = pass->iterations;
+  param[2] = pass->memory;
+  param[3] = pass->parallelism;
+
+  buf = grub_malloc (pass->buflen);
+  if (!buf)
+    return grub_crypto_gcry_error (GPG_ERR_OUT_OF_MEMORY);
+
+  err = grub_crypto_argon2 (GRUB_GCRY_KDF_ARGON2ID, param, 4,
+                           entered, grub_strlen (entered),
+                           pass->salt, pass->saltlen,
+                           NULL, 0, NULL, 0,
+                           pass->buflen, buf);
+
+  if (err)
+      ret = grub_crypto_gcry_error (err);
+  else if (grub_crypto_memcmp (buf, pass->expected, pass->buflen) != 0)
+      ret = GRUB_ACCESS_DENIED;
+  else
+    {
+      grub_auth_authenticate (user);
+      ret = GRUB_ERR_NONE;
+    }
+
+  grub_free (buf);
+  return ret;
+}
+
+static inline int
+hex2val (char hex)
+{
+  if ('0' <= hex && hex <= '9')
+    return hex - '0';
+  if ('a' <= hex && hex <= 'f')
+    return hex - 'a' + 10;
+  if ('A' <= hex && hex <= 'F')
+    return hex - 'A' + 10;
+  return -1;
+}
+
+static grub_err_t
+grub_cmd_password (grub_command_t cmd __attribute__ ((unused)),
+                  int argc, char **args)
+{
+  grub_err_t err;
+  const char *ptr, *ptr2;
+  grub_uint8_t *ptro;
+  struct argon2_password *pass;
+
+  if (argc != 2)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("two arguments expected"));
+
+  /* Expected format: 
grub.argon2.<iterations>.<memory>.<parallelism>.<salt>.<hash> */
+
+  if (grub_memcmp (args[1], "grub.argon2.",
+                  sizeof ("grub.argon2.") - 1) != 0)
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("invalid Argon2 password"));
+
+  ptr = args[1] + sizeof ("grub.argon2.") - 1;
+
+  pass = grub_malloc (sizeof (*pass));
+  if (!pass)
+    return grub_errno;
+
+  pass->iterations = grub_strtoul (ptr, &ptr, 0);
+  if (grub_errno)
+    {
+      grub_free (pass);
+      return grub_errno;
+    }
+  if (*ptr != '.')
+    {
+      grub_free (pass);
+      return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("invalid Argon2 password"));
+    }
+  ptr++;
+
+  pass->memory = grub_strtoul (ptr, &ptr, 0);
+  if (grub_errno)
+    {
+      grub_free (pass);
+      return grub_errno;
+    }
+  if (*ptr != '.')
+    {
+      grub_free (pass);
+      return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("invalid Argon2 password"));
+    }
+  ptr++;
+
+  pass->parallelism = grub_strtoul (ptr, &ptr, 0);
+  if (grub_errno)
+    {
+      grub_free (pass);
+      return grub_errno;
+    }
+  if (*ptr != '.')
+    {
+      grub_free (pass);
+      return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("invalid Argon2 password"));
+    }
+  ptr++;
+
+  ptr2 = grub_strchr (ptr, '.');
+  if (!ptr2 || ((ptr2 - ptr) & 1) || grub_strlen (ptr2 + 1) & 1)
+    {
+      grub_free (pass);
+      return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("invalid Argon2 password"));
+    }
+
+  pass->saltlen = (ptr2 - ptr) >> 1;
+  pass->buflen = grub_strlen (ptr2 + 1) >> 1;
+  ptro = pass->salt = grub_malloc (pass->saltlen);
+  if (!ptro)
+    {
+      grub_free (pass);
+      return grub_errno;
+    }
+  while (ptr < ptr2)
+    {
+      int hex1, hex2;
+      hex1 = hex2val (*ptr);
+      ptr++;
+      hex2 = hex2val (*ptr);
+      ptr++;
+      if (hex1 < 0 || hex2 < 0)
+       {
+         grub_free (pass->salt);
+         grub_free (pass);
+         return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                            /* TRANSLATORS: it means that the string which
+                               was supposed to be a password hash doesn't
+                               have a correct format, not to password
+                               mismatch.  */
+                            N_("invalid Argon2 password"));
+       }
+
+      *ptro = (hex1 << 4) | hex2;
+      ptro++;
+    }
+
+  ptro = pass->expected = grub_malloc (pass->buflen);
+  if (!ptro)
+    {
+      grub_free (pass->salt);
+      grub_free (pass);
+      return grub_errno;
+    }
+  ptr = ptr2 + 1;
+  ptr2 += grub_strlen (ptr2);
+  while (ptr < ptr2)
+    {
+      int hex1, hex2;
+      hex1 = hex2val (*ptr);
+      ptr++;
+      hex2 = hex2val (*ptr);
+      ptr++;
+      if (hex1 < 0 || hex2 < 0)
+       {
+         grub_free (pass->expected);
+         grub_free (pass->salt);
+         grub_free (pass);
+         return grub_error (GRUB_ERR_BAD_ARGUMENT,
+                            N_("invalid Argon2 password"));
+       }
+
+      *ptro = (hex1 << 4) | hex2;
+      ptro++;
+    }
+
+  err = grub_auth_register_authentication (args[0], check_password, pass);
+  if (err)
+    {
+      grub_free (pass);
+      return err;
+    }
+  grub_dl_ref (my_mod);
+  return GRUB_ERR_NONE;
+}
+
+static grub_command_t cmd;
+
+GRUB_MOD_INIT(password_argon2)
+{
+  my_mod = mod;
+  cmd = grub_register_command ("password_argon2", grub_cmd_password,
+                              N_("USER ARGON2_PASSWORD"),
+                              N_("Set user password (Argon2). "));
+}
+
+GRUB_MOD_FINI(password_argon2)
+{
+  grub_unregister_command (cmd);
+}
-- 
2.51.0


_______________________________________________
Grub-devel mailing list
[email protected]
https://lists.gnu.org/mailman/listinfo/grub-devel

Reply via email to