Logo Search packages:      
Sourcecode: pam-pkcs11 version File versions  Download package

openssh_mapper.c

/*
 * PAM-PKCS11 OPENSSH mapper module
 * Copyright (C) 2005 Juan Antonio Martinez <jonsito@teleline.es>
 * pam-pkcs11 is copyright (C) 2003-2004 of Mario Strasser <mast@gmx.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * $Id: openssh_mapper.c 238 2007-04-11 07:04:53Z ludovic.rousseau $
 */

#define __OPENSSH_MAPPER_C_

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include "../common/cert_st.h"
#ifndef HAVE_NSS
#include <openssl/evp.h>
#include <openssl/bn.h>
#endif

#include "../scconf/scconf.h"
#include "../common/debug.h"
#include "../common/error.h"
#include "../common/base64.h"
#include "../common/strings.h"
#include "../common/cert_info.h"
#include "mapper.h"
#include "openssh_mapper.h"

/* TODO 
Not sure on usage of authorized keys map file...
So the first version, will use getpwent() to navigate across all users 
and parsing ${userhome}/.ssh/authorized_keys
*/
static const char *keyfile="/etc/pam_pkcs11/authorized_keys";
static int debug=0;

/**
* This mapper try to locate user by comparing authorized public keys
* from each $HOME/.ssh user entry, as done in openssh package
*/

#define OPENSSH_LINE_MAX 8192 /* from openssh SSH_MAX_PUBKEY_BYTES */

#ifndef HAVE_NSS
static EVP_PKEY *ssh1_line_to_key(char *line) {
      EVP_PKEY *key;
      RSA *rsa;
      char *b, *e, *m, *c;

      key = EVP_PKEY_new();
      if (!key) return NULL;
      rsa = RSA_new();
      if (!rsa) goto err;

      /* first digitstring: the bits */
      b = line;
      /* second digitstring: the exponent */
      /* skip all digits */
      for (e = b; *e >= '0' && *e <= '0'; e++) ;
      /* must be a whitespace */
      if (*e != ' ' && *e != '\t') return NULL;
      /* cut the string in two part */
      *e = 0;
      e++;
      /* skip more whitespace */
      while (*e == ' ' || *e == '\t') e++;
      /* third digitstring: the modulus */
      /* skip all digits */
      for (m = e; *m >= '0' && *m <= '0'; m++) ;
      /* must be a whitespace */
      if (*m != ' ' && *m != '\t') return NULL;

      /* cut the string in two part */
      *m = 0;
      m++;

      /* skip more whitespace */
      while (*m == ' ' || *m == '\t') m++;

      /* look for a comment after the modulus */
      for (c = m; *c >= '0' && *c <= '0'; c++) ;

      /* could be a whitespace or end of line */
      if (*c != ' ' && *c != '\t' && *c != '\n' && *c != '\r' && *c != 0) return NULL;

      if (*c == ' ' || *c == '\t') {
            *c = 0;
            c++;

            /* skip more whitespace */
            while (*c == ' ' || *c == '\t') c++;

            if (*c && *c != '\r' && *c != '\n') {
                  /* we have a comment */
            } else {
                  c = NULL;
            }

      } else {
            *c = 0;
            c = NULL;
      }

      /* ok, now we have b e m pointing to pure digit
       * null terminated strings and maybe c pointing to a comment */

      BN_dec2bn(&rsa->e, e);
      BN_dec2bn(&rsa->n, m);

      EVP_PKEY_assign_RSA(key, rsa);
      return key;

      err:
      free(key);
      return NULL;
}

static EVP_PKEY *ssh2_line_to_key(char *line) {
      EVP_PKEY *key;
      RSA *rsa;
      unsigned char decoded[OPENSSH_LINE_MAX];
      int len;

      char *b, *c;
      int i;

      /* find the mime-blob */
      b = line;
      if (!b) return NULL;

      /* find the first whitespace */
      while (*b && *b != ' ') b++;
      /* skip that whitespace */
      b++;
      /* find the end of the blob / comment */
      for (c = b; *c && *c != ' ' && 'c' != '\t' && *c != '\r' && *c != '\n'; c++) ;
      *c = 0;
      /* decode binary data */
      if (base64_decode(b, decoded, OPENSSH_LINE_MAX) < 0) return NULL;

      i = 0;
      /* get integer from blob */
      len =
          (decoded[i] << 24) + (decoded[i + 1] << 16) +
          (decoded[i + 2] << 8) + (decoded[i + 3]);
      i += 4;

      /* now: key_from_blob */
      if (strncmp((char *)&decoded[i], "ssh-rsa", 7) != 0) return NULL; 
      i += len;

      key = EVP_PKEY_new();
      rsa = RSA_new();

      /* get integer from blob */
      len =
          (decoded[i] << 24) + (decoded[i + 1] << 16) +
          (decoded[i + 2] << 8) + (decoded[i + 3]);
      i += 4;

      /* get bignum */
      rsa->e = BN_bin2bn(decoded + i, len, NULL);
      i += len;

      /* get integer from blob */
      len =
          (decoded[i] << 24) + (decoded[i + 1] << 16) +
          (decoded[i + 2] << 8) + (decoded[i + 3]);
      i += 4;

      /* get bignum */
      rsa->n = BN_bin2bn(decoded + i, len, NULL);

      EVP_PKEY_assign_RSA(key, rsa);
      return key;
}

static void add_key(EVP_PKEY * key, EVP_PKEY *** keys, int *nkeys) {
      EVP_PKEY **keys2;
      /* sanity checks */
      if (!key) return;
      if (!keys) return;
      if (!nkeys) return;
      /* no keys so far */
      if (!*keys) {
            *keys = malloc(sizeof(void *));
            if (!*keys) return;
            *keys[0] = key;
            *nkeys = 1;
            return;
      }
      /* enlarge */
      keys2 = malloc(sizeof(void *) * ((*nkeys) + 1));
      if (!keys2) return;
      memcpy(keys2, *keys, sizeof(void *) * (*nkeys));
      keys2[*nkeys] = key;
      free(*keys);
      *keys = keys2;
      (*nkeys)++;
}
#endif

/*
* Returns the public key of certificate as an array list
*/
static char ** openssh_mapper_find_entries(X509 *x509, void *context) {
        char **entries= cert_info(x509,CERT_SSHPUK,ALGORITHM_NULL);
        if (!entries) {
                DBG("get_public_key() failed");
                return NULL;
        }
        return entries;
}

static int openssh_mapper_match_keys(X509 *x509, const char *filename) {
#ifdef HAVE_NSS
      return -1;
#else
      FILE *fd;
      char line[OPENSSH_LINE_MAX];
      int i;
      int nkeys =0;
      EVP_PKEY **keys = NULL;
      EVP_PKEY *authkey = X509_get_pubkey(x509);

      if (!authkey) {
          DBG("Cannot locate Cert Public key");
          return 0;
        }
        /* parse list of authorized keys until match */
      fd=fopen(filename,"rt");
      if (!fd) {
          DBG2("fopen('%s') : '%s'",filename,strerror(errno));
          return 0; /* no authorized_keys file -> no match :-) */
      }
      /* read pkey files and compose chain */
      for (;;) {
                char *cp;
                if (!fgets(line, OPENSSH_LINE_MAX, fd)) break;
                /* Skip leading whitespace, empty and comment lines. */
                for (cp = line; *cp == ' ' || *cp == '\t'; cp++)

                        if (!*cp || *cp == '\n' || *cp == '#') continue;
                if (*cp >= '0' && *cp <= '9') {
                        /* ssh v1 key format */
                        EVP_PKEY *key = ssh1_line_to_key(cp);
                        if (key) add_key(key, &keys, &nkeys);
                }
                if (strncmp("ssh-rsa", cp, 7) == 0) {
                        /* ssh v2 rsa key format */
                        EVP_PKEY *key = ssh2_line_to_key(cp);
                        if (key) add_key(key, &keys, &nkeys);
          }
        }
      fclose(fd);
        for (i = 0; i < nkeys; i++) {
                RSA *authrsa, *rsa;
                authrsa = EVP_PKEY_get1_RSA(authkey);
                if (!authrsa) continue;       /* not RSA */
                rsa = EVP_PKEY_get1_RSA(keys[i]);
                if (!rsa) continue;       /* not RSA */
                if (BN_cmp(rsa->e, authrsa->e) != 0) continue;
                if (BN_cmp(rsa->n, authrsa->n) != 0) continue;
                return 1;       /* FOUND */
        }
        DBG("User authorized_keys file doesn't match cert public key(s)");
        return 0;
#endif
}

_DEFAULT_MAPPER_END

/*
* parses the certificate, extract public key and try to match
* with contents of ${login}/.ssh/authorized_keys file
* returns -1, 0 or 1 ( error, no match, or match)
*/
static int openssh_mapper_match_user(X509 *x509, const char *user, void *context) {
        struct passwd *pw;
      char filename[512];
        if (!x509) return -1;
        if (!user) return -1;
        pw = getpwnam(user);
        if (!pw || is_empty_str(pw->pw_dir) ) {
            DBG1("User '%s' has no home directory",user);
            return -1;
        }
      sprintf(filename,"%s/.ssh/authorized_keys",pw->pw_dir);
        return openssh_mapper_match_keys(x509,filename);
}

/*
parses the certificate and return the _first_ user that matches public key 
*/
static char * openssh_mapper_find_user(X509 *x509, void *context) {
        int n = 0;
        struct passwd *pw = NULL;
        char *res = NULL;
        /* parse list of users until match */
        setpwent();
        while((pw=getpwent()) != NULL) {
          char filename[512];
            DBG1("Trying to match certificate with user: '%s'",pw->pw_name);
            if ( is_empty_str(pw->pw_dir) ) {
                DBG1("User '%s' has no home directory",pw->pw_name);
                continue;
            }
          sprintf(filename,"%s/.ssh/authorized_keys",pw->pw_dir);
            n = openssh_mapper_match_keys (x509,filename);
            if (n<0) {
                DBG1("Error in matching process with user '%s'",pw->pw_name);
                endpwent();
                return NULL;
            }
            if (n==0) {
                DBG1("Certificate doesn't match user '%s'",pw->pw_name);
                continue;
          }
            /* arriving here means user found */
            DBG1("Certificate match found for user '%s'",pw->pw_name);
            res= clone_str(pw->pw_name);
            endpwent();
          return res;
        } /* next login */
        /* no user found that contains cert in their directory */
        endpwent();
        DBG("No entry at ${login}/.ssh/authorized_keys maps to any provided certificate");
        return NULL;
}

static mapper_module * init_mapper_st(scconf_block *blk, const char *name) {
      mapper_module *pt= malloc(sizeof(mapper_module));
      if (!pt) return NULL;
      pt->name = name;
      pt->block = blk;
      pt->context = NULL;
      pt->entries = openssh_mapper_find_entries;
      pt->finder = openssh_mapper_find_user;
      pt->matcher = openssh_mapper_match_user;
      pt->deinit = mapper_module_end;
      return pt;
}

/**
* Initialization routine
*/
#ifndef OPENSSH_MAPPER_STATIC
mapper_module * mapper_module_init(scconf_block *blk,const char *mapper_name) {
#else
mapper_module * openssh_mapper_module_init(scconf_block *blk,const char *mapper_name) {
#endif
      mapper_module *pt;
        if (blk) {
        debug      = scconf_get_bool(blk,"debug",0);
        keyfile    = scconf_get_str(blk,"keyfile",keyfile);
      } else {
            DBG1("No block declaration for mapper '%'",mapper_name);
      }
        set_debug_level(debug);
      pt = init_mapper_st(blk,mapper_name);
        if(pt) DBG2("OpenSSH mapper started. debug: %d, mapfile: %s",debug,keyfile);
      else DBG("OpenSSH mapper initialization failed");
        return pt;
}

Generated by  Doxygen 1.6.0   Back to index