From 23ade142e708ef9c225f0ccfd519e48132793888 Mon Sep 17 00:00:00 2001
From: liuxin <liuxin264@huawei.com>
Date: Thu, 2 Sep 2021 17:30:39 +0800
Subject: [PATCH] grub2-set-password prompts to enter the current password and
 add the password complexity check

---
 util/grub-mkpasswd-pbkdf2.c | 105 ++++++++++++++++++++++++++++++---
 util/grub-set-password.in   | 114 ++++++++++++++++++++++++++++++++++++
 2 files changed, 212 insertions(+), 7 deletions(-)

diff --git a/util/grub-mkpasswd-pbkdf2.c b/util/grub-mkpasswd-pbkdf2.c
index 5805f3c..c9f39a5 100644
--- a/util/grub-mkpasswd-pbkdf2.c
+++ b/util/grub-mkpasswd-pbkdf2.c
@@ -42,10 +42,14 @@
 
 #include "progname.h"
 
+#define GRUB_PARAM_ERROR 1
+#define GRUB_PARAM_SUCCESS 0
+
 static struct argp_option options[] = {
   {"iteration-count",  'c', N_("NUM"), 0, N_("Number of PBKDF2 iterations"), 0},
   {"buflen",  'l', N_("NUM"), 0, N_("Length of generated hash"), 0},
   {"salt",  's', N_("NUM"), 0, N_("Length of salt"), 0},
+  {"salt arg",  'a', N_("VARCHAR"), 0, N_("preset salt var(hex code)"), 0},
   { 0, 0, 0, 0, 0, 0 }
 };
 
@@ -54,8 +58,45 @@ struct arguments
   unsigned int count;
   unsigned int buflen;
   unsigned int saltlen;
+  char * salt;
 };
 
+static int illegal_char(char t)
+{
+  int illegal = GRUB_PARAM_ERROR;
+  char legal[] = "0123456789ABCDEF";
+  for (int i = 0; i < grub_strlen(legal); ++i) {
+    if (t == legal[i]) {
+      illegal = GRUB_PARAM_SUCCESS;
+      break;
+    }
+  }
+  return illegal;
+}
+
+static int check_salt_verify(const char * arg)
+{
+  grub_size_t len = grub_strlen(arg);
+  if (len <= 0 || len >= GRUB_SIZE_MAX)
+  {
+    fprintf(stderr, "salt length may be empty or too long!\n");
+    return GRUB_PARAM_ERROR;
+  }
+  if (len % 2 != 0)
+  {
+    fprintf(stderr, "the salt value length is an even number!\n");
+    return GRUB_PARAM_ERROR;
+  }
+  for (int i = 0; i < len; ++i)
+  {
+    if (illegal_char(arg[i]))
+    {
+      return GRUB_PARAM_ERROR;
+    }
+  }
+  return GRUB_PARAM_SUCCESS;
+}
+
 static error_t
 argp_parser (int key, char *arg, struct argp_state *state)
 {
@@ -76,6 +117,16 @@ argp_parser (int key, char *arg, struct argp_state *state)
     case 's':
       arguments->saltlen = strtoul (arg, NULL, 0);
       break;
+
+    case 'a':
+      if (check_salt_verify(arg))
+      {
+        fprintf(stderr, "only hexadecimal numbers consisting of digits and uppercase letters are supported\n");
+        return ARGP_ERR_UNKNOWN;
+      }
+      arguments->saltlen = grub_strlen(arg) / 2;
+      arguments->salt = arg;
+      break;
     default:
       return ARGP_ERR_UNKNOWN;
     }
@@ -110,13 +161,44 @@ hexify (char *hex, grub_uint8_t *bin, grub_size_t n)
   *hex = 0;
 }
 
+static void
+hextobyte(const char *hex, grub_uint8_t *bin, grub_size_t n)
+{
+    while(n)
+    {
+        grub_uint8_t tmp = 0x00;
+        if (((*hex) <= '9') && ((*hex) >= '0'))
+        {
+            tmp += (grub_uint8_t)((*hex) - '0') << 4 & 0xf0;
+        }
+        else
+        {
+            tmp += (grub_uint8_t)((*hex) - 'A' + 10) << 4 & 0xf0;
+        }
+        hex++;
+        if (((*hex) <= '9') && ((*hex) >= '0'))
+        {
+            tmp += (grub_uint8_t)((*hex) - '0') & 0x0f;
+        }
+        else
+        {
+            tmp += (grub_uint8_t)((*hex) - 'A' + 10) & 0x0f;
+        }
+        *bin = tmp;
+        bin++;
+        hex++;
+        n -= 2;
+    }
+}
+
 int
 main (int argc, char *argv[])
 {
   struct arguments arguments = {
     .count = 10000,
     .buflen = 64,
-    .saltlen = 64
+    .saltlen = 64,
+    .salt = NULL
   };
   char *result, *ptr;
   gcry_err_code_t gcry_err;
@@ -133,6 +215,12 @@ main (int argc, char *argv[])
       exit(1);
     }
 
+  if (arguments.salt != NULL && grub_strlen(arguments.salt) != 2 * arguments.saltlen)
+  {
+    fprintf(stderr, "%s", _("If the -a parameter is set, don't set the -s parameter again\n"));
+    exit(1);
+  }
+
   buf = xmalloc (arguments.buflen);
   salt = xmalloc (arguments.saltlen);
   
@@ -161,13 +249,16 @@ main (int argc, char *argv[])
     }
   memset (pass2, 0, sizeof (pass2));
 
-  if (grub_get_random (salt, arguments.saltlen))
+  if (arguments.salt != NULL)
     {
-      memset (pass1, 0, sizeof (pass1));
-      free (buf);
-      free (salt);
-      grub_util_error ("%s", _("couldn't retrieve random data for salt"));
-    }
+      hextobyte(arguments.salt, salt, arguments.saltlen * 2);
+    } else if (grub_get_random (salt, arguments.saltlen))
+        {
+          memset (pass1, 0, sizeof (pass1));
+          free (buf);
+          free (salt);
+          grub_util_error ("%s", _("couldn't retrieve random data for salt"));
+        }
 
   gcry_err = grub_crypto_pbkdf2 (GRUB_MD_SHA512,
 				 (grub_uint8_t *) pass1, strlen (pass1),
diff --git a/util/grub-set-password.in b/util/grub-set-password.in
index d8005e5..9369b40 100644
--- a/util/grub-set-password.in
+++ b/util/grub-set-password.in
@@ -82,16 +82,130 @@ fixtty() {
 }
 
 trap fixtty EXIT
+
+getsaltpass() {
+	local P0
+	local P1
+	P0="$1" && shift
+	P1="$1" && shift
+	P2="$1" && shift
+
+	( echo ${P0} ; echo ${P1} ) | \
+    	LC_ALL=C ${grub_mkpasswd} -a ${P2} | \
+    	grep -v '[eE]nter password:' | \
+    	sed -e "s/PBKDF2 hash of your password is //"
+}
+
+verifyusercfgoldpasswd() {
+    # get old password salt
+    expectsalt=`cat ${grubdir}/user.cfg | cut -d "." -f 5`
+    # get expect password
+    expectpass=`cat ${grubdir}/user.cfg`
+    prefix="GRUB2_PASSWORD="
+
+    stty -echo
+    echo -n "Enter Current password: "
+    read PASSWORD_CURRENT
+    echo
+
+    needcheckpass="${prefix}$(getsaltpass "${PASSWORD_CURRENT}" "${PASSWORD_CURRENT}" "${expectsalt}")"
+    if [ "$expectpass" != "$needcheckpass" ]; then
+        echo "Authentication failed"
+        exit 1
+    fi
+
+    stty ${ttyopt}
+}
+
+verifygrubcfgoldpasswd() {
+    # get old password line
+    expectpass=`cat ${grubdir}/grub.cfg | grep "password_pbkdf2 root grub.pbkdf2.sha512" | cut -d " " -f 3`
+    # if not get password, try a quotation mark match
+    if [ -z "$expectpass" ];then
+        expectpass=`cat ${grubdir}/grub.cfg | grep "password_pbkdf2 root \"grub.pbkdf2.sha512" | cut -d " " -f 3 | cut -d "\"" -f 2`
+    fi
+    if [ -z "$expectpass" ];then
+        expectpass=`cat ${grubdir}/grub.cfg | grep "password_pbkdf2 root 'grub.pbkdf2.sha512" | cut -d " " -f 3 | cut -d "'" -f 2`
+    fi
+    if [ -n "$expectpass" ];then
+        # get old password salt
+        expectsalt=`echo ${expectpass} | cut -d "." -f 5`
+        stty -echo
+        echo -n "Enter Current password: "
+        read PASSWORD_CURRENT
+        echo
+
+        needcheckpass="$(getsaltpass "${PASSWORD_CURRENT}" "${PASSWORD_CURRENT}" "${expectsalt}")"
+        if [ "$expectpass" != "$needcheckpass" ]; then
+            echo "Authentication failed"
+            exit 1
+        fi
+    fi
+
+}
+
+if [ -e ${grubdir}/user.cfg ];then
+    verifyusercfgoldpasswd
+else
+    verifygrubcfgoldpasswd
+fi
+
+checkcomplexity() {
+    set +e
+    USERNAME=`cat ${grubdir}/grub.cfg | grep "set superusers=" | cut -d "\"" -f 2 |tail -1`
+    local P1="$1" && shift
+    if [ "$P1" = "$USERNAME" ];then
+        echo "The password contains the user name in some form"
+        exit 1
+    fi
+    # password len >= 8
+    strlen=`echo "$P1" | grep -E '^(.{8,}).*$'`
+    if [ -z "$strlen" ];then
+        echo "The password is shorter than 8 characters"
+        exit 1
+    fi
+    # lowercase
+    strlow=`echo "$P1" | grep -E --color '^(.*[a-z]+).*$'`
+    # uppercase
+    strupp=`echo $P1 | grep -E --color '^(.*[A-Z]).*$'`
+    # special character
+    strts=`echo $P1 | grep -E --color '^(.*\W).*$'`
+    # num
+    strnum=`echo $P1 | grep -E --color '^(.*[0-9]).*$'`
+    complexity=0
+    if [ -n "$strlow" ];then
+        complexity=`expr $complexity + 1`
+    fi
+    if [ -n "$strupp" ];then
+        complexity=`expr $complexity + 1`
+    fi
+    if [ -n "$strts" ];then
+        complexity=`expr $complexity + 1`
+    fi
+    if [ -n "$strnum" ];then
+        complexity=`expr $complexity + 1`
+    fi
+    if [ $complexity -lt 3 ];then
+        echo "The password contains less than 3 character classes"
+        exit 1
+    fi
+    set -e
+}
+
 stty -echo
 
 # prompt & confirm new grub2 root user password
 echo -n "Enter password: "
 read PASSWORD
 echo
+stty ${ttyopt}
+checkcomplexity $PASSWORD
+stty -echo
 echo -n "Confirm password: "
 read PASSWORD_CONFIRM
 echo
 stty ${ttyopt}
+checkcomplexity $PASSWORD_CONFIRM
 
 getpass() {
     local P0
-- 
2.19.1

