From 59daac692a796fac4f241ec0ef358fd023b671ef Mon Sep 17 00:00:00 2001
From: modric <wangyu283@huawei.com>
Date: Fri, 14 Jan 2022 11:46:20 +0800
Subject: [PATCH] add sm3 crypt support

---
 modules/pam_unix/pam_unix.8     |   9 +-
 modules/pam_unix/pam_unix.8.xml |  16 +++-
 modules/pam_unix/passverify.c   |   5 +-
 modules/pam_unix/support.c      |   4 +-
 modules/pam_unix/support.h      |   6 +-
 xtests/Makefile.am              |   6 +-
 xtests/tst-pam_unix5.c          | 151 ++++++++++++++++++++++++++++++++
 xtests/tst-pam_unix5.pamd       |   5 ++
 xtests/tst-pam_unix5.sh         |  41 +++++++++
 9 files changed, 233 insertions(+), 10 deletions(-)
 create mode 100644 xtests/tst-pam_unix5.c
 create mode 100644 xtests/tst-pam_unix5.pamd
 create mode 100644 xtests/tst-pam_unix5.sh

diff --git a/modules/pam_unix/pam_unix.8 b/modules/pam_unix/pam_unix.8
index 438717f..6f31c10 100644
--- a/modules/pam_unix/pam_unix.8
+++ b/modules/pam_unix/pam_unix.8
@@ -201,9 +201,16 @@ When a user changes their password next, encrypt it with the yescrypt algorithm\
 function\&.
 .RE
 .PP
+sm3
+.RS 4
+When a user changes their password next, encrypt it with the SM3 algorithm\&. The SM3 algorithm must be supported by the
+\fBcrypt\fR(3)
+function\&.
+.RE
+.PP
 rounds=n
 .RS 4
-Set the optional number of rounds of the SHA256, SHA512, blowfish, gost\-yescrypt, and yescrypt password hashing algorithms to
+Set the optional number of rounds of the SHA256, SHA512, blowfish, gost\-yescrypt, yescrypt and SM3 password hashing algorithms to
 \fIn\fR\&.
 .RE
 .PP
diff --git a/modules/pam_unix/pam_unix.8.xml b/modules/pam_unix/pam_unix.8.xml
index dfc0427..7d61869 100644
--- a/modules/pam_unix/pam_unix.8.xml
+++ b/modules/pam_unix/pam_unix.8.xml
@@ -366,6 +366,20 @@
           </para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term>
+          sm3
+        </term>
+        <listitem>
+          <para>
+            When a user changes their password next,
+            encrypt it with the SM3 algorithm. The
+            SM3 algorithm must be supported by the <citerefentry>
+	    <refentrytitle>crypt</refentrytitle><manvolnum>3</manvolnum>
+            </citerefentry> function.
+          </para>
+        </listitem>
+      </varlistentry>
       <varlistentry>
         <term>
           rounds=n
@@ -373,7 +387,7 @@
         <listitem>
           <para>
             Set the optional number of rounds of the SHA256, SHA512,
-            blowfish, gost-yescrypt, and yescrypt password hashing
+            blowfish, gost-yescrypt, yescrypt and SM3 password hashing
             algorithms to
             <replaceable>n</replaceable>.
           </para>
diff --git a/modules/pam_unix/passverify.c b/modules/pam_unix/passverify.c
index 81b10d8..1aee153 100644
--- a/modules/pam_unix/passverify.c
+++ b/modules/pam_unix/passverify.c
@@ -445,6 +445,8 @@ PAMH_ARG_DECL(char * create_password_hash,
 		algoid = "$5$";
 	} else if (on(UNIX_SHA512_PASS, ctrl)) {
 		algoid = "$6$";
+	} else if (on(UNIX_SM3_PASS, ctrl)) {
+		algoid = "$sm3$";
 	} else { /* must be crypt/bigcrypt */
 		char tmppass[9];
 		char *hashed;
@@ -492,7 +494,8 @@ PAMH_ARG_DECL(char * create_password_hash,
 			   on(UNIX_GOST_YESCRYPT_PASS, ctrl) ? "gost_yescrypt" :
 			   on(UNIX_BLOWFISH_PASS, ctrl) ? "blowfish" :
 			   on(UNIX_SHA256_PASS, ctrl) ? "sha256" :
-			   on(UNIX_SHA512_PASS, ctrl) ? "sha512" : algoid);
+			   on(UNIX_SHA512_PASS, ctrl) ? "sha512" :
+			   on(UNIX_SM3_PASS, ctrl) ? "sm3" : algoid);
 		if(sp) {
 		   pam_overwrite_string(sp);
 		}
diff --git a/modules/pam_unix/support.c b/modules/pam_unix/support.c
index 043273d..4052868 100644
--- a/modules/pam_unix/support.c
+++ b/modules/pam_unix/support.c
@@ -99,7 +99,7 @@ unsigned long long _set_ctrl(pam_handle_t *pamh, int flags, int *remember,
 	  free (val);
 
 	  /* read number of rounds for crypt algo */
-	  if (rounds && (on(UNIX_SHA256_PASS, ctrl) || on(UNIX_SHA512_PASS, ctrl))) {
+	  if (rounds && (on(UNIX_SHA256_PASS, ctrl) || on(UNIX_SHA512_PASS, ctrl) || on(UNIX_SM3_PASS, ctrl))) {
 	    val = pam_modutil_search_key(pamh, LOGIN_DEFS, "SHA_CRYPT_MAX_ROUNDS");
 
 	    if (val) {
@@ -194,7 +194,7 @@ unsigned long long _set_ctrl(pam_handle_t *pamh, int flags, int *remember,
 		} else if (on(UNIX_BLOWFISH_PASS, ctrl)) {
 			if (*rounds < 4 || *rounds > 31)
 				*rounds = 5;
-		} else if (on(UNIX_SHA256_PASS, ctrl) || on(UNIX_SHA512_PASS, ctrl)) {
+		} else if (on(UNIX_SHA256_PASS, ctrl) || on(UNIX_SHA512_PASS, ctrl) || on(UNIX_SM3_PASS, ctrl)) {
 			if ((*rounds < 1000) || (*rounds == INT_MAX)) {
 				/* don't care about bogus values */
 				*rounds = 0;
diff --git a/modules/pam_unix/support.h b/modules/pam_unix/support.h
index 8105400..b5712b5 100644
--- a/modules/pam_unix/support.h
+++ b/modules/pam_unix/support.h
@@ -101,10 +101,11 @@ typedef struct {
 #define UNIX_GOST_YESCRYPT_PASS  31     /* new password hashes will use gost-yescrypt */
 #define UNIX_YESCRYPT_PASS       32     /* new password hashes will use yescrypt */
 #define UNIX_NULLRESETOK         33     /* allow empty password if password reset is enforced */
+#define UNIX_SM3_PASS            34     /* new password hashes will use SM3 */
 /* -------------- */
-#define UNIX_CTRLS_              34	/* number of ctrl arguments defined */
+#define UNIX_CTRLS_              35	/* number of ctrl arguments defined */
 
-#define UNIX_DES_CRYPT(ctrl)	(off(UNIX_MD5_PASS,ctrl)&&off(UNIX_BIGCRYPT,ctrl)&&off(UNIX_SHA256_PASS,ctrl)&&off(UNIX_SHA512_PASS,ctrl)&&off(UNIX_BLOWFISH_PASS,ctrl)&&off(UNIX_GOST_YESCRYPT_PASS,ctrl)&&off(UNIX_YESCRYPT_PASS,ctrl))
+#define UNIX_DES_CRYPT(ctrl)	(off(UNIX_MD5_PASS,ctrl)&&off(UNIX_BIGCRYPT,ctrl)&&off(UNIX_SHA256_PASS,ctrl)&&off(UNIX_SHA512_PASS,ctrl)&&off(UNIX_BLOWFISH_PASS,ctrl)&&off(UNIX_GOST_YESCRYPT_PASS,ctrl)&&off(UNIX_YESCRYPT_PASS,ctrl)&&off(UNIX_SM3_PASS,ctrl))
 
 static const UNIX_Ctrls unix_args[UNIX_CTRLS_] =
 {
@@ -145,6 +146,7 @@ static const UNIX_Ctrls unix_args[UNIX_CTRLS_] =
 /* UNIX_GOST_YESCRYPT_PASS */  {"gost_yescrypt",    _ALL_ON_^(015660420000ULL),   04000000000, 1},
 /* UNIX_YESCRYPT_PASS */       {"yescrypt",         _ALL_ON_^(015660420000ULL),  010000000000, 1},
 /* UNIX_NULLRESETOK */         {"nullresetok",      _ALL_ON_,                    020000000000, 0},
+/* UNIX_SM3_PASS */            {"sm3",              _ALL_ON_^(015660420000ULL),  040000000000, 1},
 };
 
 #define UNIX_DEFAULTS  (unix_args[UNIX__NONULL].flag)
diff --git a/xtests/Makefile.am b/xtests/Makefile.am
index acf9746..33693de 100644
--- a/xtests/Makefile.am
+++ b/xtests/Makefile.am
@@ -14,9 +14,9 @@ EXTRA_DIST = run-xtests.sh tst-pam_dispatch1.pamd tst-pam_dispatch2.pamd \
 	tst-pam_dispatch3.pamd tst-pam_dispatch4.pamd \
 	tst-pam_dispatch5.pamd \
 	tst-pam_unix1.pamd tst-pam_unix2.pamd tst-pam_unix3.pamd \
-	tst-pam_unix4.pamd \
+	tst-pam_unix4.pamd tst-pam_unix5.pamd \
 	tst-pam_unix1.sh tst-pam_unix2.sh tst-pam_unix3.sh \
-	tst-pam_unix4.sh \
+	tst-pam_unix4.sh tst-pam_unix5.sh \
 	access.conf tst-pam_access1.pamd tst-pam_access1.sh \
 	tst-pam_access2.pamd tst-pam_access2.sh \
 	tst-pam_access3.pamd tst-pam_access3.sh \
@@ -40,7 +40,7 @@ EXTRA_DIST = run-xtests.sh tst-pam_dispatch1.pamd tst-pam_dispatch2.pamd \
 
 XTESTS = tst-pam_dispatch1 tst-pam_dispatch2 tst-pam_dispatch3 \
 	tst-pam_dispatch4 tst-pam_dispatch5 \
-	tst-pam_unix1 tst-pam_unix2 tst-pam_unix3 tst-pam_unix4 \
+	tst-pam_unix1 tst-pam_unix2 tst-pam_unix3 tst-pam_unix4 tst-pam_unix5 \
 	tst-pam_access1 tst-pam_access2 tst-pam_access3 \
 	tst-pam_access4 tst-pam_limits1 tst-pam_succeed_if1 \
 	tst-pam_group1 tst-pam_authfail tst-pam_authsucceed \
diff --git a/xtests/tst-pam_unix5.c b/xtests/tst-pam_unix5.c
new file mode 100644
index 0000000..6e6e378
--- /dev/null
+++ b/xtests/tst-pam_unix5.c
@@ -0,0 +1,151 @@
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * ALTERNATIVELY, this product may be distributed under the terms of
+ * the GNU Public License, in which case the provisions of the GPL are
+ * required INSTEAD OF the above restrictions.  (This clause is
+ * necessary due to a potential bad interaction between the GPL and
+ * the restrictions contained in a BSD-style copyright.)
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Check password authtok.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <security/pam_appl.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <unistd.h>
+
+/* A conversation function which uses an internally-stored value for
+   the responses. */
+static int
+fake_conv (int num_msg, const struct pam_message **msgm,
+	   struct pam_response **response, void *appdata_ptr)
+{
+  struct pam_response *reply;
+  int count;
+
+  /* Sanity test. */
+  if (num_msg <= 0)
+    return PAM_CONV_ERR;
+
+  /* Allocate memory for the responses. */
+  reply = calloc (num_msg, sizeof (struct pam_response));
+  if (reply == NULL)
+    return PAM_CONV_ERR;
+
+  /* Each prompt elicits the same response */
+  for (count = 0; count < num_msg; ++count)
+    {
+      if (msgm[count]->msg_style == PAM_PROMPT_ECHO_OFF)
+        {
+          reply[count].resp_retcode = 0;
+          reply[count].resp = strdup(appdata_ptr);
+        } else {
+          reply[count].resp_retcode = 0;
+          reply[count].resp = strdup("");
+        }
+    }
+
+  /* Set the pointers in the response structure and return. */
+  *response = reply;
+  return PAM_SUCCESS;
+}
+
+static struct pam_conv conv = {
+    fake_conv,
+    NULL
+};
+
+
+/* Check that errors of optional modules are ignored and that
+   required modules after a sufficient one are not executed.  */
+
+int
+main(int argc, char *argv[])
+{
+  pam_handle_t *pamh=NULL;
+  const char *user="tstpamunix";
+  int retval;
+  int debug = 0;
+  int fail;
+  struct passwd *pwd;
+
+  if (argc < 2 || (*argv[1] != 'f' &&
+      *argv[1] != 'p'))
+    {
+      fprintf (stderr, "Need fail or pass argument.\n");
+      return 2;
+    }
+
+  fail = *argv[1] == 'f';
+
+  if (argc > 2 && strcmp (argv[2], "-d") == 0)
+    debug = 1;
+
+  pwd = getpwnam (user);
+
+  if (pwd == NULL)
+    {
+       if (debug)
+         fprintf (stderr, "unix5: Missing tstpamunix user.\n");
+       return 2;
+    }
+
+  conv.appdata_ptr = "zhangsan@123";
+  retval = pam_start("tst-pam_unix5", user, &conv, &pamh);
+  if (retval != PAM_SUCCESS)
+    {
+      if (debug)
+	fprintf (stderr, "unix5: pam_start returned %d\n", retval);
+      return 1;
+    }
+
+  retval = pam_chauthtok (pamh, PAM_SILENT);
+  if ((!fail && retval != PAM_SUCCESS) || (fail && retval == PAM_SUCCESS))
+    {
+      if (debug)
+	fprintf (stderr, "unix5-1: pam_chauthtok returned %d\n", retval);
+      return 1;
+    }
+
+  retval = pam_end (pamh,retval);
+  if (retval != PAM_SUCCESS)
+    {
+      if (debug)
+	fprintf (stderr, "unix5: pam_end returned %d\n", retval);
+      return 1;
+    }
+  return 0;
+}
diff --git a/xtests/tst-pam_unix5.pamd b/xtests/tst-pam_unix5.pamd
new file mode 100644
index 0000000..4c77a6c
--- /dev/null
+++ b/xtests/tst-pam_unix5.pamd
@@ -0,0 +1,5 @@
+#%PAM-1.0
+auth     required       pam_unix.so
+account  required       pam_unix.so
+password required       pam_unix.so sm3
+session  required       pam_unix.so
diff --git a/xtests/tst-pam_unix5.sh b/xtests/tst-pam_unix5.sh
new file mode 100644
index 0000000..a6be19b
--- /dev/null
+++ b/xtests/tst-pam_unix5.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+# testcase1 modify password, desire password encrypt with sm3 
+/usr/sbin/useradd tstpamunix
+# this run must successfully change the password
+./tst-pam_unix5 pass
+RET=$?
+# verify tstpamunix hash algo
+if test -z "$(nl /etc/shadow | sed -n '/tstpamunix:$sm3/p')"; then
+  /usr/sbin/userdel -r tstpamunix 2> /dev/null
+  exit 1
+fi
+# testcase2 config valid rounds, desire password encrypt with sm3 and rounds
+sed -i 's/password.*/& rounds=6666/g' /etc/pam.d/tst-pam_unix5
+./tst-pam_unix5 pass
+RET=$?
+if test -z "$(nl /etc/shadow | sed -n '/tstpamunix:$sm3$rounds=6666/p')"; then
+  /usr/sbin/userdel -r tstpamunix 2> /dev/null
+  exit 1
+fi
+
+# testcase3 config rounds=999, desire password encrypt with sm3, but without rounds
+sed -i 's/rounds=6666/rounds=999/g' /etc/pam.d/tst-pam_unix5
+./tst-pam_unix5 pass
+RET=$?
+if test -z "$(nl /etc/shadow | sed -n '/tstpamunix:$sm3/p')"; then
+  /usr/sbin/userdel -r tstpamunix 2> /dev/null
+  exit 1
+fi
+
+# testcase4 config rounds=10000000, desire password encrypt with sm3, but with rounds=9999999
+sed -i 's/rounds=999/rounds=10000000/g' /etc/pam.d/tst-pam_unix5
+./tst-pam_unix5 pass
+RET=$?
+if test -z "$(nl /etc/shadow | sed -n '/tstpamunix:$sm3$rounds=9999999/p')"; then
+  /usr/sbin/userdel -r tstpamunix 2> /dev/null
+  exit 1
+fi
+
+/usr/sbin/userdel -r tstpamunix 2> /dev/null
+exit $RET
-- 
2.33.0