[Pcsclite-cvs-commit] CVS MusclePAM
CVS User rousseau
ludovic.rousseau@free.fr
Wed, 13 Oct 2004 02:13:06 -0600
Update of /cvsroot/muscleapps/MusclePAM
In directory haydn:/tmp/cvs-serv32636
Modified Files:
README pam-muscle.conf pam_smartcard.c preferences.c
preferences.h
Log Message:
patch from Bruce Barnett to add flexibility:
- you can use a password authentication if the card authentication
failed (also depend on how you configured you PAM configuration file
of course)
- use getpwnam(3) to find the user home directory rather than just
UserPath from pam-muscle.conf
- spelling errors corrected
--- /cvsroot/muscleapps/MusclePAM/README 2004/09/15 07:00:58 1.5
+++ /cvsroot/muscleapps/MusclePAM/README 2004/10/13 08:13:05 1.6
@@ -42,7 +42,11 @@
have two values:
1. UserCert - the module will look in ~/.muscle/user.cert for the
- certificate.
+ certificate. If the file contains a value for UserPath, then
+ this is used instead of the user's home directory. This allows
+ the admin to create a directory containing certificates that
+ are not modify able by the user. Of it can be used for testing.
+
2. RootCert - the module will retreive the certificate from the smartcard
and validate the signature by looking at the RootCA's
certificate in /etc/root.cert. It will also check that the
@@ -59,12 +63,16 @@
MIIEzzCCA7egAwIBAgIBADANBgkqhkiG9w0BAQQFADBvMQswCQYDVQQGEwJkZTEg
[snip]
-----END CERTIFICATE-----
+
3) PEM - PUBKEY
+
+ This is also in base64 format, and has the form
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDhs17bA92YPg8XrR9s34qoK2Fc
[snip]
-----END PUBLIC KEY-----
+
The third form can be obtained by using musacletool to create a
keypair, export it, and then using b2fs to convert it into a
pubkey format
@@ -101,24 +109,130 @@
% b2fs cert.blob >~/.muscle/user.cert
-Note that the user can do this without any outside authority. Make sure
-the home directory and certificate is not writable. (pam_musclecard.so
-should check to make sure this is not possible. However it doesn't, so
-this is a potential security problem).
-
-Once you have the certificate in place, I have been able to get the su
-and login modules to work with PAM.
-
-Modify
- /etc/pam.d/su
-to contain
- auth required /lib/security/pam_musclecard.so service=system-auth
-
- and /etc/pam.d/login to contain
- auth required /lib/security/pam_musclecard.so service=system-auth
-
-and when you try to log into the console, or to do an su, it first
-checks if a smartcard is plugged in. If so, it asks for a PIN value.
-Then it procees to verify it with the user's ~user/.muscle/user.cert
-file.
+ Also not that you have to change the certificate number from 0
+ (which is the private key) to 1, which would in this case be the
+ public key.
+
+Note that the user can do this without any outside authority.
+
+The pam module makes sure that only the user and root has write access
+to the certificate. The certificate, and all of the directories
+above, cannot be group or world writable, and have to be owned by the
+user or root. Otherwise, the user is not allowed to log in, and a log
+message is written to the log file.
+
+Once you have the certificate in place, you can use PAM to add
+smartcard authentication to the following modules [I have verified
+that these work on Fedora Core 1. Other applications might work as
+well, but I have not tested them. -Bruce]
+
+ login
+ su
+ xscreensaver
+ gdm
+ gdm-autologin
+
+
+The modifications to the /etc/pam.d/* files only change the auth
+values (lines whose first column is "auth")
+
+Gdm can be configured in several ways.
+
+Variation #1: If the reader is present, and the token inserted
+ only ask for the PIN, and not the password
+ If the reader or token is not present, ask for the password
+------------
+auth sufficient pam_musclecard.so service=system-auth debug
+auth required pam_stack.so service=system-auth
+-------------
+
+Variation #2: Only use the smartcard, never the password
+------------
+auth required pam_musclecard.so service=system-auth debug
+#auth required pam_stack.so service=system-auth
+-------------
+
+
+Variation #3: Require a password (first) and a pin (second)
+------------
+auth required pam_stack.so service=system-auth
+auth required pam_musclecard.so service=system-auth debug
+-------------
+
+Note that you can optionally allow root to log in without a smartcard by adding
+------------
+auth required pam_nologin.so
+------------
+
+xscreeensaver only requires a PIN value, but prompts for a password.
+This seems to be a bug in xscreensaver.
+
+
+::::::::::::::
+gdm
+::::::::::::::
+#%PAM-1.0
+auth required pam_env.so
+# If musclecard is "sufficient", then if this fails, fall back on the password
+auth sufficient pam_musclecard.so service=system-auth debug
+auth required pam_stack.so service=system-auth
+# If you are sure, you may want to remove the next line
+# It allows root to log in without a smartcard
+auth required pam_nologin.so
+account required pam_stack.so service=system-auth
+password required pam_stack.so service=system-auth
+session required pam_stack.so service=system-auth
+session optional pam_console.so
+::::::::::::::
+gdm-autologin
+::::::::::::::
+#%PAM-1.0
+auth required pam_env.so
+auth required pam_nologin.so
+auth required pam_permit.so
+auth required pam_musclecard.so service=system-auth
+account required pam_stack.so service=system-auth
+password required pam_stack.so service=system-auth
+session required pam_stack.so service=system-auth
+session optional pam_console.so
+::::::::::::::
+login
+::::::::::::::
+#%PAM-1.0
+#auth required pam_securetty.so
+auth required /lib/security/pam_musclecard.so service=system-auth
+#auth required pam_stack.so service=system-auth
+#auth required pam_nologin.so
+account required pam_stack.so service=system-auth
+password required pam_stack.so service=system-auth
+session required pam_stack.so service=system-auth
+session optional pam_console.so
+::::::::::::::
+su
+::::::::::::::
+#%PAM-1.0
+# If you remove the next line, then root will require a smartcard to do a 'su
+# This might break things (cronjobs, admin scripts, etc.)
+auth sufficient /lib/security/$ISA/pam_rootok.so
+# Uncomment the following line to implicitly trust users in the "wheel" group.
+#auth sufficient /lib/security/$ISA/pam_wheel.so trust use_uid
+# Uncomment the following line to require a user to be in the "wheel" group.
+#auth required /lib/security/$ISA/pam_wheel.so use_uid
+#was
+# Normally we use the pam_stack, but in this case we use musclecard
+#auth required /lib/security/$ISA/pam_stack.so service=system-auth
+auth required /lib/security/pam_musclecard.so service=system-auth
+account required /lib/security/$ISA/pam_stack.so service=system-auth
+password required /lib/security/$ISA/pam_stack.so service=system-auth
+session required /lib/security/$ISA/pam_stack.so service=system-auth
+session optional /lib/security/$ISA/pam_xauth.so
+::::::::::::::
+xscreensaver
+::::::::::::::
+#%PAM-1.0
+
+# Red Hat says this is right for them, as of 7.3:
+auth required pam_stack.so service=system-auth
+auth required /lib/security/pam_musclecard.so service=system-auth debug
+#auth required pam_pwdb.so shadow nullok
--- /cvsroot/muscleapps/MusclePAM/pam-muscle.conf 2003/10/03 19:30:31 1.3
+++ /cvsroot/muscleapps/MusclePAM/pam-muscle.conf 2004/10/13 08:13:05 1.4
@@ -5,10 +5,14 @@
#
Debug = OFF # Debug ON or OFF
-CertNumber = 0 # Certificate number to use
+# Usually, cert #0 is the private key, and cert# 1 is the public key
+CertNumber = 1 # Certificate number to use
PinNumber = 1 # Pin number to verify
-UserPath = /home/ # Path to user home directory
CertName = user.cert # User Certificate in DER format
+# If the following value is defined, then it is used to determine the
+# location of the user certificate - i.e. $UserPath/$USER/.muscle/$CertName
+# Otherwise, the user's home directory is used - i.e. ~user/.muscle/$CertName
+#UserPath = /home/ # Path to user home directory
RootCACert = /etc/musclepam/root.cert # Root CA certificate
LDAPHost = unsupported # Web-server with LDAP
LDAPPath = unsupported # Search path in LDAP
--- /cvsroot/muscleapps/MusclePAM/pam_smartcard.c 2004/10/07 18:49:02 1.17
+++ /cvsroot/muscleapps/MusclePAM/pam_smartcard.c 2004/10/13 08:13:05 1.18
@@ -42,7 +42,7 @@
#define DEBUG 1
/**
- * This struct contains secure data and shoul be overwritten
+ * This struct contains secure data and should be overwritten
* before releasing!
*/
struct secure_data {
@@ -253,14 +253,33 @@
int readUserPubKey(EVP_PKEY **userCert, MSCTokenConnection pConnection,
struct secure_data *sd) {
- int rv;
+ int rv = 0;
EVP_PKEY *tmpCert;
X509 *tmpCert1;
unsigned char homeFile[200];
/* tries to get a public key information from the user's public key certificate */
- snprintf(homeFile, 200, "%s%s/.muscle/%s", pr.userpath, sd->user, pr.certname);
+ /* Is userpath defined? If so, use it as a way to 'find' someone's
+ home directory (ignoring the value in the password file).
+ I'm using this because the existence of the
+ specified file overrides the end user's default certificate in their
+ home directory - so userpath is defined for debugging only */
+ if (strlen(pr.userpath)>0) {
+ snprintf(homeFile, sizeof(homeFile), "%s%s/.muscle/%s", pr.userpath, sd->user, pr.certname);
+ } else {
+ /* It's not defined. We have to determine the user's home directory. */
+ struct passwd *pw;
+ if ((pw=getpwnam(sd->user)) == NULL) {
+ syslog(LOG_ERR, "su attempt to non-existing user: %s", sd->user);
+ return -1;
+ } else
+ snprintf(homeFile, sizeof(homeFile), "%s/.muscle/%s", pw->pw_dir, pr.certname);
+ }
+ if (util_CheckFile(homeFile,(char *)(sd->user))) {
+ syslog(LOG_ERR, "Unsafe permissions on user certificate, file: %s: user: %s", homeFile, sd->user);
+ return -1;
+ }
rv = getFileCert(homeFile, &tmpCert1);
if (rv == -1) {
@@ -271,24 +290,21 @@
without an outside authority to sign it. */
rv = getPubKeyFromFile(homeFile, &tmpCert);
+ if (pr.debug)
+ syslog(LOG_INFO, "user certificate successfully read from %s", homeFile);
} else {
- syslog(LOG_ERR, "user certificate successfully read from %s", homeFile);
rv = checkCert(tmpCert1);
if (rv == -1) {
syslog(LOG_ERR, "user certificate expired or revoked");
- pcsc_release(&pConnection);
- pam_release_data(sd);
- return PAM_AUTHINFO_UNAVAIL;
+ return -1;
}
rv = getPublicKey(tmpCert1, &tmpCert);
}
if (rv == -1) {
syslog(LOG_ERR, "cannot read certificate from %s", homeFile);
- pcsc_release(&pConnection);
- pam_release_data(sd);
- return PAM_AUTHINFO_UNAVAIL;
+ return -1;
}
@@ -311,7 +327,7 @@
int try_first_pass, use_first_pass;
unsigned char error[150];
//unsigned char nameBuf[100];
- X509 *userCert;
+ X509 *userCert = 0;
EVP_PKEY *pubkey;
reader = 0;
@@ -336,7 +352,16 @@
if (rv != MSC_SUCCESS) {
syslog(LOG_ERR, "musclecard error during pcsc_init: %s", msc_error(rv));
pam_release_data(sd);
+ /* if we return PAM_AUTHINFO_UNAVAIL, then we cannot use pam "auth
+ sufficient" That is, if the token is unavailable, will you
+ allow a password to be used. If we return PAM_AUTHINFO_UNAVAIL,
+ we cannot fall back on a password. Therefore, return PAM_ERR,
+ allowing password to be sufficient.
+
return PAM_AUTHINFO_UNAVAIL;
+ */
+ result = PAM_AUTH_ERR;
+
}
if (pr.debug) printf("Welcome to pam_musclecard.so verification Module\n");
@@ -439,8 +464,11 @@
if (pr.authmode == ROOTCERT) {
rv = readRootCert(&userCert, pConnection, sd);
- if (rv != 0)
+ if (rv != 0) {
+ pcsc_release(&pConnection);
+ pam_release_data(sd);
return PAM_AUTHINFO_UNAVAIL;
+ }
rv = getPublicKey(userCert, &pubkey);
} else {
rv = readUserPubKey(&pubkey, pConnection, sd);
@@ -453,11 +481,10 @@
return PAM_AUTHINFO_UNAVAIL;
}
-
- /* This sectionj can be used to verify the user's certificate */
+ /* This section can be used to verify the user's certificate */
#ifdef DEBUG_SPECIAL1
if (pr.debug) {
- syslog(LOG_ERR, "Public key file read from user certificate");
+ syslog(LOG_INFO, "Public key file read from user certificate");
printf("public exponent : %s\n", BN_bn2hex(pubkey->pkey.rsa->e));
printf("public modulus : %s\n", BN_bn2hex(pubkey->pkey.rsa->n));
}
@@ -514,6 +541,7 @@
sd2 = (struct secure_data*)malloc(sizeof(struct secure_data));
if (sd2 == NULL) {
syslog(LOG_CRIT, "not enough free memory to initialize sd2");
+ pam_release_data(sd);
return PAM_AUTHINFO_UNAVAIL;
}
memcpy(sd2->rand,sd->cipher,RAND_SIZE);
@@ -527,7 +555,7 @@
printf("Undecoded value using key #%d = ", cryptInit.keyNum);
for (i=0; i < RAND_SIZE; i++) {
rv = (unsigned char)sd2->cipher[i];
- printf("%02x", rv);
+ printf("%02X", rv);
}
printf("\n\n");
}
@@ -567,9 +595,9 @@
result = PAM_AUTH_ERR;
}
-
/* Release certificates */
- X509_free(userCert);
+ if (userCert)
+ X509_free(userCert);
/* release pcsc */
pcsc_release(&pConnection);
--- /cvsroot/muscleapps/MusclePAM/preferences.c 2003/10/03 19:30:31 1.4
+++ /cvsroot/muscleapps/MusclePAM/preferences.c 2004/10/13 08:13:06 1.5
@@ -7,6 +7,7 @@
* Authors: *
* Chris Osgod <oznet@mac.com> *
* Eirik A. Herskedal <ehersked@cs.purdue.edu> *
+ * Bruce Barnett <muscle040302@grymoire.com>
* *
********************************************************************
* *
@@ -19,14 +20,21 @@
#include <stdio.h>
#include <syslog.h>
#include <string.h>
-
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <sys/types.h>
#include "preferences.h"
+/* The third parameter was /home, but this overrides the default value
+ of the home directory specified by the passwd file */
+
struct preferences pr = {
DEBUGOFF,
0,
1,
- "/home/",
+ "", /* was "/home/" */
"user.cert",
"/etc/musclepam/root.cert",
"",
@@ -40,7 +48,7 @@
** Parse a preference item from a line of text. The text should be null
** terminated and buf_size keeps overruns from happening.
**
-** If a preference item is successfully parsed then it is stored in the
+** If a preference item is successfully parsed then it is stored in the
** st.prefs settings.
**
** This whole function is fairly verbose and could be broken into smaller
@@ -51,7 +59,7 @@
**
** Parameters:
** buf - Null terminated text buffer
-** buf_size - Size of buffer
+** buf_size - Size of buffer
**
** Returns:
** none
@@ -60,15 +68,15 @@
{
char sep[] = "=\t\r\n ";
char *token;
-
+
/* We will be using many unsafe string functions so force a NULL at the */
/* end of the buffer to protect ourselves */
buf[buf_size - 1] = 0;
token = (char *) strchr(buf, '#');
-
- if (token)
+
+ if (token)
*token = 0;
-
+
token = (char *) strtok(buf, sep);
if (token)
{
@@ -175,7 +183,7 @@
** Function: util_ReadPreferences
**
** Gets preferences, if available. On UNIX, looks for .pkcs11rc
-** in the $HOME directory, or root directory if $HOME is not
+** in the $HOME directory, or root directory if $HOME is not
** defined. Having a preferences file is optional and it is assumed
** that most of the time users will not have one unless debug/logging
** or other special settings are required.
@@ -193,16 +201,143 @@
FILE *fp;
char rcfilename[] = "/etc/musclepam/pam-muscle.conf";
char buf[1024];
-
+
+ if (pr.debug)
+ syslog(LOG_INFO, "Reading the preferences from file '%s'", rcfilename);
+
+ /* unsafe preference file? */
+ if (util_CheckFile(rcfilename, "root") != 0)
+ return -1;
fp = fopen(rcfilename, "rb");
if (fp)
{
while (fgets(buf, sizeof(buf), fp))
util_ParsePreference(buf, sizeof(buf));
-
fclose(fp);
}
return rv;
}
+/******************************************************************************
+** Function: util_CheckFile
+**
+** Checks to make sure the file is not modifyable by someone other
+** than the owner This utility is given the complete name for the file
+** and recursively walks up the direwctory tree until it reaches the
+** root directory.
+**
+**
+** Parameters:
+** file (string)
+** user (string)
+**
+** Returns:
+** zero if no problem
+** -1 if there is a security risk
+** -2 if the file does not exist
+**
+******************************************************************************/
+
+int util_CheckFile (char *file, char *user)
+{
+ char path[1024];
+ int i;
+ char *l;
+
+ if (strstr(file, "..")) {
+ /* This would probably never happen, and the owner of the
+ /etc/musclepam/pam-muscle.conf file has to put it
+ there. . But it's better to be paranoid and safe than sorry */
+ syslog(LOG_ERR, "File '%s' contains the string '..' - unsafe place to put configuration file", file);
+ return -1;
+ }
+
+ if (file[0] != '/') {
+ syslog(LOG_ERR, "File '%s' is relative. Must use absolute path - unsafe place to put configuration file", file);
+ return -1;
+ }
+
+ /* Now - check the entire file */
+ if ((i = util_CheckFileComponent(file,user)) < 0)
+ return i;
+
+ /* looks okay - now check all of the paths */
+ strncpy(path, file, sizeof(path));
+
+ while ((l = strrchr(path,'/')) != NULL) {
+ /* chop off all characters after the '/' */
+ *l = '\0';
+ if (strlen(path) > 0)
+ if ((i = util_CheckFileComponent(path,user)) < 0)
+ return i;
+ }
+
+ if (pr.debug)
+ syslog(LOG_INFO, "File '%s' and user %s look okay", file, user);
+
+ return 0;
+}
+
+/******************************************************************************
+** Function: util_CheckFileComponent
+**
+** Checks to make sure the file component (either a single file or
+** directory) is not modifyable by someone other than the owner
+**
+** Parameters:
+** file (string)
+** user (string)
+**
+** Returns:
+** zero if no problem
+** -1 if there is a security risk
+** -2 if the file or directory does not exist
+**
+******************************************************************************/
+
+int util_CheckFileComponent (char *file, char *user)
+{
+ struct stat buf;
+ struct passwd *pw;
+ uid_t uid;
+
+ /* Check if the file is a symbolic link */
+ if (lstat(file, &buf)) {
+ /* if (pr.debug) printf("File does not exist %s - done\n", file); */
+ return -2; /* doesn't exist */
+ }
+
+ if (S_ISLNK(buf.st_mode)) {
+ syslog(LOG_ERR, "File '%s' is a symbolic link - unsafe place to put configuration file", file);
+ return -1;
+ }
+
+ /* Check the real file */
+ if (stat(file,&buf)) {
+ /* if (pr.debug) printf("File does not exist (2) %s - done\n", file); */
+ return -2; /* I can't check it, so it's okay */
+ }
+
+ /* Find the user's UID number */
+ if ((pw = getpwnam(user)) == NULL) {
+ syslog(LOG_ERR, "User '%s' does not exist", user);
+ return -1;
+ }
+
+ uid = pw->pw_uid;
+
+ if ((buf.st_uid != uid) && (buf.st_uid != 0)) {
+ syslog(LOG_ERR, "File '%s' is owned by UID %d, and should be owned by %d (%s) - unsafe operation", file, buf.st_uid, uid, user);
+ return -1;
+ }
+
+ if ((buf.st_mode & 0022) != 0) {
+ /* if (pr.debug) printf("File mode is BAD, %o vs. %o\n", buf.st_mode, (buf.st_mode&022)); */
+ syslog(LOG_ERR, "File '%s' is group or world writable - may be unsafe operation", file);
+ return -1;
+ }
+
+ return 0;
+}
+
--- /cvsroot/muscleapps/MusclePAM/preferences.h 2002/07/11 21:10:37 1.2
+++ /cvsroot/muscleapps/MusclePAM/preferences.h 2004/10/13 08:13:06 1.3
@@ -40,4 +40,9 @@
/* Parse the configuration file */
int util_ReadPreferences();
+/* Check the permissions of the file */
+int util_CheckFile (char *file, char *user);
+
+/* Check the permissions of the file Component */
+int util_CheckFileComponent (char *file, char *user);