Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 9 Oct 2000 15:54:37 -0500 (EST)
From:      ajk@iu.edu
To:        FreeBSD-gnats-submit@freebsd.org
Cc:        kris@freebsd.org
Subject:   bin/21877: [PATCH] DSA support for pam_ssh
Message-ID:  <200010092054.e99KsbC08757@verbal.uits.iupui.edu>

next in thread | raw e-mail | index | archive | help

>Number:         21877
>Category:       bin
>Synopsis:       [PATCH] DSA support for pam_ssh
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Mon Oct 09 14:00:01 PDT 2000
>Closed-Date:
>Last-Modified:
>Originator:     Andrew J. Korty
>Release:        FreeBSD 5.0-CURRENT i386
>Organization:
Indiana University
>Environment:

FreeBSD 5.0-CURRENT #1: Sun Oct  8 00:30:52 EST 2000

>Description:

When DSA support was added to ssh-agent, pam_ssh stopped working.
To fix this problem, I have added DSA support to pam_ssh.  The user
is authenticated if the supplied passphrase decrypts either the
DSA or RSA key.  Any and all decrypted keys are passed to the agent.

This patch also fixes a possible memory leak and ensures the user's
environment file (~/.ssh/agent-*) has sensible permissions.

>How-To-Repeat:

Apply the patch below.

>Fix:

Index: pam_ssh.c
===================================================================
RCS file: /var/cvs/src/crypto/openssh/pam_ssh/pam_ssh.c,v
retrieving revision 1.8
diff -u -r1.8 pam_ssh.c
--- pam_ssh.c	2000/07/16 05:52:28	1.8
+++ pam_ssh.c	2000/10/09 20:39:35
@@ -28,11 +28,9 @@
  */
 
 
-#include <sys/param.h>
 #include <sys/queue.h>
+#include <sys/stat.h>
 
-#include <fcntl.h>
-#include <paths.h>
 #include <pwd.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -45,6 +43,7 @@
 #include <security/pam_mod_misc.h>
 
 #include <openssl/dsa.h>
+#include <openssl/evp.h>
 
 #include "includes.h"
 #include "rsa.h"
@@ -54,18 +53,26 @@
 #include "authfile.h"
 
 #define	MODULE_NAME	"pam_ssh"
-#define	NEED_PASSPHRASE	"Need passphrase for %s (%s).\nEnter passphrase: "
+#define	NEED_PASSPHRASE	"SSH passphrase: "
 #define	PATH_SSH_AGENT	"/usr/bin/ssh-agent"
 
 
+/*
+ * Generic cleanup function for SSH "Key" type.
+ */
+
 void
-rsa_cleanup(pam_handle_t *pamh, void *data, int error_status)
+key_cleanup(pam_handle_t *pamh, void *data, int error_status)
 {
 	if (data)
-		RSA_free(data);
+		key_free(data);
 }
 
 
+/*
+ * Generic PAM cleanup function for this module.
+ */
+
 void
 ssh_cleanup(pam_handle_t *pamh, void *data, int error_status)
 {
@@ -107,6 +114,10 @@
 extern char **environ;
 
 
+/*
+ * Create a new environment list.
+ */
+
 static ENV *
 env_new(void)
 {
@@ -123,8 +134,12 @@
 }
 
 
+/*
+ * Insert a new entry into the list.
+ */
+
 static int
-env_put(ENV *self, char *s)
+env_put(ENV *self, const char *s)
 {
 	struct env_entry	*env;
 
@@ -139,13 +154,21 @@
 }
 
 
+/*
+ * Switch between the original environ string and our crafted one.
+ */
+
 static void
-env_swap(ENV *self, int which)
+env_swap(const ENV *self, int which)
 {
 	environ = which ? self->e_environ_new : self->e_environ_orig;
 }
 
 
+/*
+ * Craft an environ string out of the list we've built.
+ */
+
 static int
 env_commit(ENV *self)
 {
@@ -171,15 +194,20 @@
 }
 
 
+/*
+ * Destroy our list and environ string.
+ */
+
 static void
 env_destroy(ENV *self)
 {
 	struct env_entry	 *p;
 
 	env_swap(self, 0);
-	SLIST_FOREACH(p, &self->e_head, ee_entries) {
+	while ((p = SLIST_FIRST(&self->e_head))) {
 		free(p->ee_env);
 		free(p);
+		SLIST_REMOVE_HEAD(&self->e_head, ee_entries);
 	}
 	if (self->e_committed)
 		free(self->e_environ_new);
@@ -187,6 +215,10 @@
 }
 
 
+/*
+ * Cleanup function for PAM data storage.
+ */
+
 void
 env_cleanup(pam_handle_t *pamh, void *data, int error_status)
 {
@@ -195,6 +227,80 @@
 }
 
 
+/*
+ * Authenticate a user's key by trying to decrypt it with the password
+ * provided.  The key and its comment are then stored for later
+ * retrieval by the session phase.  An increasing index is embedded in
+ * the PAM variable names so this function may be called multiple times
+ * for multiple keys.
+ */
+
+int
+auth_via_key(
+	pam_handle_t		*pamh,
+	int			 type,
+	const char		*file,
+	const struct passwd	*user,
+	const char		*pass
+)
+{
+	char		*comment;		/* private key comment */
+	char		*data_name;		/* PAM state */
+	static int	 index = 0;		/* for saved keys */
+	Key		*key;			/* user's key */
+	char		*path;			/* to key files */
+	int		 retval;		/* from calls */
+	uid_t		 saved_uid;		/* caller's uid */
+
+	/* locate the user's private key file */
+	if (!asprintf(&path, "%s/%s", user->pw_dir, file)) {
+		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
+		return PAM_SERVICE_ERR;
+	}
+	saved_uid = getuid();
+	/*
+	 * Try to decrypt the private key with the passphrase provided.
+	 * If success, the user is authenticated.
+	 */
+	key = key_new(type);
+	(void)setreuid(user->pw_uid, saved_uid);
+	retval = load_private_key(path, pass, key, &comment);
+	free(path);
+	(void)setuid(saved_uid);
+	if (!retval)
+		return retval;
+	/*
+	 * Save the key and comment to pass to ssh-agent in the session
+	 * phase.
+	 */
+	if (!asprintf(&data_name, "ssh_private_key_%d", index)) {
+		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
+		free(comment);
+		return PAM_SERVICE_ERR;
+	}
+	retval = pam_set_data(pamh, data_name, key, key_cleanup);
+	free(data_name);
+	if (retval != PAM_SUCCESS) {
+		key_free(key);
+		free(comment);
+		return retval;
+	}
+	if (!asprintf(&data_name, "ssh_key_comment_%d", index)) {
+		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
+		free(comment);
+		return PAM_SERVICE_ERR;
+	}
+	retval = pam_set_data(pamh, data_name, comment, ssh_cleanup);
+	free(data_name);
+	if (retval != PAM_SUCCESS) {
+		free(comment);
+		return retval;
+	}
+	++index;
+	return PAM_SUCCESS;
+}
+
+
 typedef struct passwd PASSWD;
 
 PAM_EXTERN int
@@ -204,18 +310,11 @@
 	int		  argc,
 	const char	**argv)
 {
-	char		*comment_priv;		/* on private key */
-	char		*comment_pub;		/* on public key */
-	char		*identity;		/* user's identity file */
-	Key		 key;			/* user's private key */
 	int		 options;		/* module options */
 	const char	*pass;			/* passphrase */
-	char		*prompt;		/* passphrase prompt */
-	Key		 public_key;		/* user's public key */
 	const PASSWD	*pwent;			/* user's passwd entry */
 	PASSWD		*pwent_keep;		/* our own copy */
 	int		 retval;		/* from calls */
-	uid_t		 saved_uid;		/* caller's uid */
 	const char	*user;			/* username */
 
 	options = 0;
@@ -227,70 +326,25 @@
 		/* delay? */
 		return PAM_AUTH_ERR;
 	}
-	/* locate the user's private key file */
-	if (!asprintf(&identity, "%s/%s", pwent->pw_dir,
-	    SSH_CLIENT_IDENTITY)) {
-		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
-		return PAM_SERVICE_ERR;
-	}
 	/*
-	 * Fail unless we can load the public key.  Change to the
-	 * owner's UID to appease load_public_key().
+	 * Pass prompt message to application and receive
+	 * passphrase.
 	 */
-	key.type = KEY_RSA;
-	key.rsa = RSA_new();
-	public_key.type = KEY_RSA;
-	public_key.rsa = RSA_new();
-	saved_uid = getuid();
-	(void)setreuid(pwent->pw_uid, saved_uid);
-	retval = load_public_key(identity, &public_key, &comment_pub);
-	(void)setuid(saved_uid);
-	if (!retval) {
-		free(identity);
-		return PAM_AUTH_ERR;
-	}
-	RSA_free(public_key.rsa);
-	/* build the passphrase prompt */
-	retval = asprintf(&prompt, NEED_PASSPHRASE, identity, comment_pub);
-	free(comment_pub);
-	if (!retval) {
-		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
-		free(identity);
-		return PAM_SERVICE_ERR;
-	}
-	/* pass prompt message to application and receive passphrase */
-	retval = pam_get_pass(pamh, &pass, prompt, options);
-	free(prompt);
-	if (retval != PAM_SUCCESS) {
-		free(identity);
+	if ((retval = pam_get_pass(pamh, &pass, NEED_PASSPHRASE, options))
+	    != PAM_SUCCESS)
 		return retval;
-	}
+	OpenSSL_add_all_algorithms();	/* required for DSA */
 	/*
-	 * Try to decrypt the private key with the passphrase provided.
-	 * If success, the user is authenticated.
+	 * Either the DSA or the RSA key will authenticate us, but if
+	 * we can decrypt both, we'll do so here so we can cache them in
+	 * the session phase.
 	 */
-	(void)setreuid(pwent->pw_uid, saved_uid);
-	retval = load_private_key(identity, pass, &key, &comment_priv);
-	free(identity);
-	(void)setuid(saved_uid);
-	if (!retval)
+	retval = auth_via_key(pamh, KEY_DSA, SSH_CLIENT_ID_DSA, pwent,
+	    pass);
+	if (auth_via_key(pamh, KEY_RSA, SSH_CLIENT_IDENTITY, pwent, pass)
+	    != PAM_SUCCESS && retval != PAM_SUCCESS)
 		return PAM_AUTH_ERR;
 	/*
-	 * Save the key and comment to pass to ssh-agent in the session
-	 * phase.
-	 */
-	if ((retval = pam_set_data(pamh, "ssh_private_key", key.rsa,
-	    rsa_cleanup)) != PAM_SUCCESS) {
-		RSA_free(key.rsa);
-		free(comment_priv);
-		return retval;
-	}
-	if ((retval = pam_set_data(pamh, "ssh_key_comment", comment_priv,
-	    ssh_cleanup)) != PAM_SUCCESS) {
-		free(comment_priv);
-		return retval;
-	}
-	/*
 	 * Copy the passwd entry (in case successive calls are made)
 	 * and save it for the session phase.
 	 */
@@ -333,7 +387,10 @@
 	char		*env_end;		/* end of env */
 	char		*env_file;		/* to store env */
 	FILE		*env_fp;		/* env_file handle */
-	Key		 key;			/* user's private key */
+	char		*data_name;		/* PAM state */
+	int		 final;			/* final return value */
+	int		 index;			/* for saved keys */
+	Key		*key;			/* user's private key */
 	FILE		*pipe;			/* ssh-agent handle */
 	const PASSWD	*pwent;			/* user's passwd entry */
 	int		 retval;		/* from calls */
@@ -351,9 +408,10 @@
 	if ((retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty))
 	    != PAM_SUCCESS)
 		return retval;
-	if (*tty == ':' && gethostname(hname, sizeof hname) == 0) {
-		if (asprintf(&env_file, "%s/.ssh/agent-%s%s",
-		    pwent->pw_dir, hname, tty) == -1) {
+	if (gethostname(hname, sizeof hname) == 0) {
+		if (asprintf(&env_file, "%s/.ssh/agent-%s%s%s",
+		    pwent->pw_dir, hname, *tty == ':' ? "" : ":", tty)
+		    == -1) {
 			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
 			return PAM_SERVICE_ERR;
 		}
@@ -371,7 +429,8 @@
 	/* start the agent as the user */
 	saved_uid = geteuid();
 	(void)seteuid(pwent->pw_uid);
-	env_fp = fopen(env_file, "w");
+	if ((env_fp = fopen(env_file, "w")))
+		(void)chmod(env_file, S_IRUSR);
 	pipe = popen(PATH_SSH_AGENT, "r");
 	(void)seteuid(saved_uid);
 	if (!pipe) {
@@ -380,6 +439,11 @@
 			(void)fclose(env_fp);
 		return PAM_SESSION_ERR;
 	}
+	/*
+	 * Save environment for application with pam_putenv()
+	 * but also with env_* functions for our own call to
+	 * ssh_get_authentication_connection().
+	 */
 	if (!(ssh_env = env_new()))
 		return PAM_SESSION_ERR;
 	if ((retval = pam_set_data(pamh, "ssh_env_handle", ssh_env,
@@ -388,11 +452,6 @@
 	while (fgets(parse, sizeof parse, pipe)) {
 		if (env_fp)
 			(void)fputs(parse, env_fp);
-		/*
-		 * Save environment for application with pam_putenv()
-		 * but also with env_* functions for our own call to
-		 * ssh_get_authentication_connection().
-		 */
 		if (strchr(parse, '=') && (env_end = strchr(parse, ';'))) {
 			*env_end = '\0';
 			/* pass to the application ... */
@@ -427,13 +486,8 @@
 		env_destroy(ssh_env);
 		return PAM_SESSION_ERR;
 	}
-	key.type = KEY_RSA;
-	/* connect to the agent and hand off the private key */
-	if ((retval = pam_get_data(pamh, "ssh_private_key",
-	    (const void **)&key.rsa)) != PAM_SUCCESS ||
-	    (retval = pam_get_data(pamh, "ssh_key_comment",
-	    (const void **)&comment)) != PAM_SUCCESS ||
-	    (retval = env_commit(ssh_env)) != PAM_SUCCESS) {
+	/* connect to the agent */
+	if ((retval = env_commit(ssh_env)) != PAM_SUCCESS) {
 		env_destroy(ssh_env);
 		return retval;
 	}
@@ -442,11 +496,38 @@
 		    MODULE_NAME);
 		env_destroy(ssh_env);
 		return PAM_SESSION_ERR;
+	}
+	/* hand off each private key to the agent */
+	final = 0;
+	for (index = 0; ; index++) {
+		if (!asprintf(&data_name, "ssh_private_key_%d", index)) {
+			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
+			ssh_close_authentication_connection(ac);
+			env_destroy(ssh_env);
+			return PAM_SERVICE_ERR;
+		}
+		retval = pam_get_data(pamh, data_name, (const void **)&key);
+		free(data_name);
+		if (retval != PAM_SUCCESS)
+			break;
+		if (!asprintf(&data_name, "ssh_key_comment_%d", index)) {
+			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
+			ssh_close_authentication_connection(ac);
+			env_destroy(ssh_env);
+			return PAM_SERVICE_ERR;
+		}
+		retval = pam_get_data(pamh, data_name,
+		    (const void **)&comment);
+		free(data_name);
+		if (retval != PAM_SUCCESS)
+			break;
+		retval = ssh_add_identity(ac, key, comment);
+		if (!final)
+			final = retval;
 	}
-	retval = ssh_add_identity(ac, key.rsa, comment);
 	ssh_close_authentication_connection(ac);
-	env_swap(ssh_env, 0);
-	return retval ? PAM_SUCCESS : PAM_SESSION_ERR;
+	env_swap(ssh_env, 0);		/* restore original environment */
+	return final ? PAM_SUCCESS : PAM_SESSION_ERR;
 }
 
 
@@ -461,6 +542,7 @@
 	int	 	 retval;	/* from calls */
 	ENV		*ssh_env;	/* env handle */
 
+	/* invoke the cached environment to re-access the agent */
 	if ((retval = pam_get_data(pamh, "ssh_env_handle",
 	    (const void **)&ssh_env)) != PAM_SUCCESS)
 		return retval;

>Release-Note:
>Audit-Trail:
>Unformatted:


To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-bugs" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200010092054.e99KsbC08757>