root/usr/src/lib/krb5/plugins/preauth/pkinit/pkinit_identity.c
/*
 * COPYRIGHT (C) 2007
 * THE REGENTS OF THE UNIVERSITY OF MICHIGAN
 * ALL RIGHTS RESERVED
 *
 * Permission is granted to use, copy, create derivative works
 * and redistribute this software and such derivative works
 * for any purpose, so long as the name of The University of
 * Michigan is not used in any advertising or publicity
 * pertaining to the use of distribution of this software
 * without specific, written prior authorization.  If the
 * above copyright notice or any other identification of the
 * University of Michigan is included in any copy of any
 * portion of this software, then the disclaimer below must
 * also be included.
 *
 * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION
 * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY
 * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF
 * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
 * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
 * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE
 * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR
 * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN
 * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGES.
 */

/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <unistd.h>
#include <dirent.h>

#include <libintl.h>

#include "pkinit.h"

static void
free_list(char **list)
{
    int i;

    if (list == NULL)
        return;

    for (i = 0; list[i] != NULL; i++)
        free(list[i]);
     free(list);
}

static krb5_error_code
copy_list(char ***dst, char **src)
{
    int i;
    char **newlist;

    if (dst == NULL)
        return EINVAL;
    *dst = NULL;

    if (src == NULL)
        return 0;

    for (i = 0; src[i] != NULL; i++);

    newlist = calloc(1, (i + 1) * sizeof(*newlist));
    if (newlist == NULL)
        return ENOMEM;

    for (i = 0; src[i] != NULL; i++) {
        newlist[i] = strdup(src[i]);
        if (newlist[i] == NULL)
            goto cleanup;
    }
    newlist[i] = NULL;
    *dst = newlist;
    return 0;
cleanup:
    free_list(newlist);
    return ENOMEM;
}

char *
idtype2string(int idtype)
{
/* Solaris Kerberos: Removed "break"s (lint) */
    switch(idtype) {
    case IDTYPE_FILE: return "FILE";
    case IDTYPE_DIR: return "DIR";
    case IDTYPE_PKCS11: return "PKCS11";
    case IDTYPE_PKCS12: return "PKCS12";
    case IDTYPE_ENVVAR: return "ENV";
    default: return "INVALID";
    }
}

char *
catype2string(int catype)
{
/* Solaris Kerberos: Removed "break"s (lint) */
    switch(catype) {
    case CATYPE_ANCHORS: return "ANCHORS";
    case CATYPE_INTERMEDIATES: return "INTERMEDIATES";
    case CATYPE_CRLS: return "CRLS";
    default: return "INVALID";
    }
}

krb5_error_code
pkinit_init_identity_opts(pkinit_identity_opts **idopts)
{
    pkinit_identity_opts *opts = NULL;

    *idopts = NULL;
    opts = (pkinit_identity_opts *) calloc(1, sizeof(pkinit_identity_opts));
    if (opts == NULL)
        return ENOMEM;

    opts->identity = NULL;
    opts->anchors = NULL;
    opts->intermediates = NULL;
    opts->crls = NULL;
    opts->ocsp = NULL;
    opts->dn_mapping_file = NULL;

    opts->cert_filename = NULL;
    opts->key_filename = NULL;
#ifndef WITHOUT_PKCS11
    opts->p11_module_name = NULL;
    opts->slotid = PK_NOSLOT;
    opts->token_label = NULL;
    opts->cert_id_string = NULL;
    opts->cert_label = NULL;
    opts->PIN = NULL;
#endif

    *idopts = opts;

    return 0;
}

krb5_error_code
pkinit_dup_identity_opts(pkinit_identity_opts *src_opts,
                         pkinit_identity_opts **dest_opts)
{
    pkinit_identity_opts *newopts;
    krb5_error_code retval;

    *dest_opts = NULL;
    retval = pkinit_init_identity_opts(&newopts);
    if (retval)
        return retval;

    retval = ENOMEM;

    if (src_opts->identity != NULL) {
        newopts->identity = strdup(src_opts->identity);
        if (newopts->identity == NULL)
            goto cleanup;
    }

    retval = copy_list(&newopts->anchors, src_opts->anchors);
    if (retval)
        goto cleanup;

    retval = copy_list(&newopts->intermediates,src_opts->intermediates);
    if (retval)
        goto cleanup;

    retval = copy_list(&newopts->crls, src_opts->crls);
    if (retval)
        goto cleanup;

    if (src_opts->ocsp != NULL) {
        newopts->ocsp = strdup(src_opts->ocsp);
        if (newopts->ocsp == NULL)
            goto cleanup;
    }

    if (src_opts->cert_filename != NULL) {
        newopts->cert_filename = strdup(src_opts->cert_filename);
        if (newopts->cert_filename == NULL)
            goto cleanup;
    }

    if (src_opts->key_filename != NULL) {
        newopts->key_filename = strdup(src_opts->key_filename);
        if (newopts->key_filename == NULL)
            goto cleanup;
    }

#ifndef WITHOUT_PKCS11
    if (src_opts->p11_module_name != NULL) {
        newopts->p11_module_name = strdup(src_opts->p11_module_name);
        if (newopts->p11_module_name == NULL)
            goto cleanup;
    }

    newopts->slotid = src_opts->slotid;

    if (src_opts->token_label != NULL) {
        newopts->token_label = strdup(src_opts->token_label);
        if (newopts->token_label == NULL)
            goto cleanup;
    }

    if (src_opts->cert_id_string != NULL) {
        newopts->cert_id_string = strdup(src_opts->cert_id_string);
        if (newopts->cert_id_string == NULL)
            goto cleanup;
    }

    if (src_opts->cert_label != NULL) {
        newopts->cert_label = strdup(src_opts->cert_label);
        if (newopts->cert_label == NULL)
            goto cleanup;
    }
    if (src_opts->PIN != NULL) {
        newopts->PIN = strdup(src_opts->PIN);
        if (newopts->PIN == NULL)
            goto cleanup;
    }
#endif


    *dest_opts = newopts;
    return 0;
cleanup:
    pkinit_fini_identity_opts(newopts);
    return retval;
}

void
pkinit_fini_identity_opts(pkinit_identity_opts *idopts)
{
    if (idopts == NULL)
        return;

    if (idopts->identity != NULL)
        free(idopts->identity);
    free_list(idopts->anchors);
    free_list(idopts->intermediates);
    free_list(idopts->crls);
    free_list(idopts->identity_alt);

    if (idopts->cert_filename != NULL)
        free(idopts->cert_filename);
    if (idopts->key_filename != NULL)
        free(idopts->key_filename);
#ifndef WITHOUT_PKCS11
    if (idopts->p11_module_name != NULL)
        free(idopts->p11_module_name);
    if (idopts->token_label != NULL)
        free(idopts->token_label);
    if (idopts->cert_id_string != NULL)
        free(idopts->cert_id_string);
    if (idopts->cert_label != NULL)
        free(idopts->cert_label);
    if (idopts->PIN != NULL) {
        (void) memset(idopts->PIN, 0, strlen(idopts->PIN));
        free(idopts->PIN);
    }
#endif
    free(idopts);
}

#ifndef WITHOUT_PKCS11
/* ARGSUSED */
static krb5_error_code
parse_pkcs11_options(krb5_context context,
                     pkinit_identity_opts *idopts,
                     const char *residual)
{
    char *s, *cp, *vp;
    krb5_error_code retval = ENOMEM;

    if (residual == NULL || residual[0] == '\0')
        return 0;

    /* Split string into attr=value substrings */
    s = strdup(residual);
    if (s == NULL)
        return retval;

    for ((cp = strtok(s, ":")); cp; (cp = strtok(NULL, ":"))) {
        vp = strchr(cp, '=');

        /* If there is no "=", this is a pkcs11 module name */
        if (vp == NULL) {
            if (idopts->p11_module_name != NULL)
                free(idopts->p11_module_name);
            idopts->p11_module_name = strdup(cp);
            if (idopts->p11_module_name == NULL)
                goto cleanup;
            continue;
        }
        *vp++ = '\0';
        if (!strcmp(cp, "module_name")) {
            if (idopts->p11_module_name != NULL)
                free(idopts->p11_module_name);
            idopts->p11_module_name = strdup(vp);
            if (idopts->p11_module_name == NULL)
                goto cleanup;
        } else if (!strcmp(cp, "slotid")) {
            long slotid = strtol(vp, NULL, 10);
            if ((slotid == LONG_MIN || slotid == LONG_MAX) && errno != 0) {
                retval = EINVAL;
                goto cleanup;
            }
            if ((long) (int) slotid != slotid) {
                retval = EINVAL;
                goto cleanup;
            }
            idopts->slotid = slotid;
        } else if (!strcmp(cp, "token")) {
            if (idopts->token_label != NULL)
                free(idopts->token_label);
            idopts->token_label = strdup(vp);
            if (idopts->token_label == NULL)
                goto cleanup;
        } else if (!strcmp(cp, "certid")) {
            if (idopts->cert_id_string != NULL)
                free(idopts->cert_id_string);
            idopts->cert_id_string = strdup(vp);
            if (idopts->cert_id_string == NULL)
                goto cleanup;
        } else if (!strcmp(cp, "certlabel")) {
            if (idopts->cert_label != NULL)
                free(idopts->cert_label);
            idopts->cert_label = strdup(vp);
            if (idopts->cert_label == NULL)
                goto cleanup;
        }
    }
    retval = 0;
cleanup:
    free(s);
    return retval;
}
#endif

/* ARGSUSED */
static krb5_error_code
parse_fs_options(krb5_context context,
                 pkinit_identity_opts *idopts,
                 const char *residual)
{
    char *certname, *keyname;
    krb5_error_code retval = ENOMEM;

    if (residual == NULL || residual[0] == '\0')
        return 0;

    certname = strdup(residual);
    if (certname == NULL)
        goto cleanup;

    certname = strtok(certname, ",");
    keyname = strtok(NULL, ",");

    idopts->cert_filename = strdup(certname);
    if (idopts->cert_filename == NULL)
        goto cleanup;

    idopts->key_filename = strdup(keyname ? keyname : certname);
    if (idopts->key_filename == NULL)
        goto cleanup;

    retval = 0;
cleanup:
    if (certname != NULL)
        free(certname);
    return retval;
}

/* ARGSUSED */
static krb5_error_code
parse_pkcs12_options(krb5_context context,
                     pkinit_identity_opts *idopts,
                     const char *residual)
{
    krb5_error_code retval = ENOMEM;

    if (residual == NULL || residual[0] == '\0')
        return 0;

    idopts->cert_filename = strdup(residual);
    if (idopts->cert_filename == NULL)
        goto cleanup;

    idopts->key_filename = strdup(residual);
    if (idopts->key_filename == NULL)
        goto cleanup;

    pkiDebug("%s: cert_filename '%s' key_filename '%s'\n",
             __FUNCTION__, idopts->cert_filename,
             idopts->key_filename);
    retval = 0;
cleanup:
    return retval;
}

static krb5_error_code
process_option_identity(krb5_context context,
                        pkinit_plg_crypto_context plg_cryptoctx,
                        pkinit_req_crypto_context req_cryptoctx,
                        pkinit_identity_opts *idopts,
                        pkinit_identity_crypto_context id_cryptoctx,
                        const char *value)
{
    const char *residual;
    int idtype;
    krb5_error_code retval = 0;

    pkiDebug("%s: processing value '%s'\n",
             __FUNCTION__, value ? value : "NULL");
    if (value == NULL)
        return EINVAL;

    residual = strchr(value, ':');
    if (residual != NULL) {
        unsigned int typelen;
        residual++; /* skip past colon */
        typelen = residual - value;
        if (strncmp(value, "FILE:", typelen) == 0) {
            idtype = IDTYPE_FILE;
#ifndef WITHOUT_PKCS11
        } else if (strncmp(value, "PKCS11:", typelen) == 0) {
            idtype = IDTYPE_PKCS11;
#endif
        } else if (strncmp(value, "PKCS12:", typelen) == 0) {
            idtype = IDTYPE_PKCS12;
        } else if (strncmp(value, "DIR:", typelen) == 0) {
            idtype = IDTYPE_DIR;
        } else if (strncmp(value, "ENV:", typelen) == 0) {
            idtype = IDTYPE_ENVVAR;
        } else {
            pkiDebug("%s: Unsupported type while processing '%s'\n",
                     __FUNCTION__, value);
            krb5_set_error_message(context, KRB5_PREAUTH_FAILED,
                                   "Unsupported type while processing '%s'\n",
                                   value);
            return KRB5_PREAUTH_FAILED;
        }
    } else {
        idtype = IDTYPE_FILE;
        residual = value;
    }

    idopts->idtype = idtype;
    pkiDebug("%s: idtype is %s\n", __FUNCTION__, idtype2string(idopts->idtype));
    switch (idtype) {
    case IDTYPE_ENVVAR: {
            /* Solaris Kerberos: Improved error messages */
            char *envvar = getenv(residual);
            if (envvar == NULL) {
                    krb5_set_error_message(context, EINVAL,
                        gettext("failed to find environmental variable \'%s\'"),
                        residual);
                    return EINVAL;
            }
            return process_option_identity(context, plg_cryptoctx,
                                       req_cryptoctx, idopts, id_cryptoctx,
                                       envvar);
            /* Solaris Kerberos: not reached */
        }
    case IDTYPE_FILE:
        retval = parse_fs_options(context, idopts, residual);
        break;
    case IDTYPE_PKCS12:
        retval = parse_pkcs12_options(context, idopts, residual);
        break;
#ifndef WITHOUT_PKCS11
    case IDTYPE_PKCS11:
        retval = parse_pkcs11_options(context, idopts, residual);
        break;
#endif
    case IDTYPE_DIR:
        idopts->cert_filename = strdup(residual);
        if (idopts->cert_filename == NULL)
            retval = ENOMEM;
        break;
    default:
        krb5_set_error_message(context, KRB5_PREAUTH_FAILED,
                               "Internal error parsing X509_user_identity\n");
        retval = EINVAL;
        break;
    }
    return retval;
}

static krb5_error_code
process_option_ca_crl(krb5_context context,
                      pkinit_plg_crypto_context plg_cryptoctx,
                      pkinit_req_crypto_context req_cryptoctx,
                      pkinit_identity_opts *idopts,
                      pkinit_identity_crypto_context id_cryptoctx,
                      const char *value,
                      int catype)
{
    char *residual;
    unsigned int typelen;
    int idtype;

    pkiDebug("%s: processing catype %s, value '%s'\n",
             __FUNCTION__, catype2string(catype), value);
    residual = strchr(value, ':');
    if (residual == NULL) {
        pkiDebug("No type given for '%s'\n", value);
        return EINVAL;
    }
    residual++; /* skip past colon */
    typelen = residual - value;
    if (strncmp(value, "FILE:", typelen) == 0) {
        idtype = IDTYPE_FILE;
    } else if (strncmp(value, "DIR:", typelen) == 0) {
        idtype = IDTYPE_DIR;
    } else {
        return ENOTSUP;
    }
    return crypto_load_cas_and_crls(context,
                                    plg_cryptoctx,
                                    req_cryptoctx,
                                    idopts, id_cryptoctx,
                                    idtype, catype, residual);
}

static krb5_error_code
pkinit_identity_process_option(krb5_context context,
                               pkinit_plg_crypto_context plg_cryptoctx,
                               pkinit_req_crypto_context req_cryptoctx,
                               pkinit_identity_opts *idopts,
                               pkinit_identity_crypto_context id_cryptoctx,
                               int attr,
                               const char *value)
{
    krb5_error_code retval = 0;

    switch (attr) {
        case PKINIT_ID_OPT_USER_IDENTITY:
            retval = process_option_identity(context, plg_cryptoctx,
                                             req_cryptoctx, idopts,
                                             id_cryptoctx, value);
            break;
        case PKINIT_ID_OPT_ANCHOR_CAS:
            retval = process_option_ca_crl(context, plg_cryptoctx,
                                           req_cryptoctx, idopts,
                                           id_cryptoctx, value,
                                           CATYPE_ANCHORS);
            break;
        case PKINIT_ID_OPT_INTERMEDIATE_CAS:
            retval = process_option_ca_crl(context, plg_cryptoctx,
                                           req_cryptoctx, idopts,
                                           id_cryptoctx,
                                           value, CATYPE_INTERMEDIATES);
            break;
        case PKINIT_ID_OPT_CRLS:
            retval = process_option_ca_crl(context, plg_cryptoctx,
                                           req_cryptoctx, idopts,
                                           id_cryptoctx,
                                           value, CATYPE_CRLS);
            break;
        case PKINIT_ID_OPT_OCSP:
            retval = ENOTSUP;
            break;
        default:
            retval = EINVAL;
            break;
    }
    return retval;
}

krb5_error_code
pkinit_identity_initialize(krb5_context context,
                           pkinit_plg_crypto_context plg_cryptoctx,
                           pkinit_req_crypto_context req_cryptoctx,
                           pkinit_identity_opts *idopts,
                           pkinit_identity_crypto_context id_cryptoctx,
                           int do_matching,
                           krb5_principal princ)
{
    krb5_error_code retval = EINVAL;
    int i;

    pkiDebug("%s: %p %p %p\n", __FUNCTION__, context, idopts, id_cryptoctx);
    if (idopts == NULL || id_cryptoctx == NULL)
        goto errout;

    /*
     * If identity was specified, use that.  (For the kdc, this
     * is specified as pkinit_identity in the kdc.conf.  For users,
     * this is specified on the command line via X509_user_identity.)
     * If a user did not specify identity on the command line,
     * then we will try alternatives which may have been specified
     * in the config file.
     */
    if (idopts->identity != NULL) {
        retval = pkinit_identity_process_option(context, plg_cryptoctx,
                                                req_cryptoctx, idopts,
                                                id_cryptoctx,
                                                PKINIT_ID_OPT_USER_IDENTITY,
                                                idopts->identity);
    } else if (idopts->identity_alt != NULL) {
        for (i = 0; retval != 0 && idopts->identity_alt[i] != NULL; i++)
                retval = pkinit_identity_process_option(context, plg_cryptoctx,
                                                    req_cryptoctx, idopts,
                                                    id_cryptoctx,
                                                    PKINIT_ID_OPT_USER_IDENTITY,
                                                    idopts->identity_alt[i]);
    } else {
        pkiDebug("%s: no user identity options specified\n", __FUNCTION__);
        goto errout;
    }
    if (retval)
        goto errout;

    retval = crypto_load_certs(context, plg_cryptoctx, req_cryptoctx,
                               idopts, id_cryptoctx, princ, do_matching);
    if (retval)
        goto errout;

    if (do_matching) {
        retval = pkinit_cert_matching(context, plg_cryptoctx, req_cryptoctx,
                                      id_cryptoctx, princ, TRUE);
        if (retval) {
            pkiDebug("%s: No matching certificate found\n", __FUNCTION__);
            (void) crypto_free_cert_info(context, plg_cryptoctx, req_cryptoctx,
                                  id_cryptoctx);
            goto errout;
        }
    } else {
        /* Tell crypto code to use the "default" */
        retval = crypto_cert_select_default(context, plg_cryptoctx,
                                            req_cryptoctx, id_cryptoctx);
        if (retval) {
            pkiDebug("%s: Failed while selecting default certificate\n",
                     __FUNCTION__);
            (void) crypto_free_cert_info(context, plg_cryptoctx, req_cryptoctx,
                                  id_cryptoctx);
            goto errout;
        }
    }

    retval = crypto_free_cert_info(context, plg_cryptoctx, req_cryptoctx,
                                   id_cryptoctx);
    if (retval)
            goto errout;

    for (i = 0; idopts->anchors != NULL && idopts->anchors[i] != NULL; i++) {
        retval = pkinit_identity_process_option(context, plg_cryptoctx,
                                                req_cryptoctx, idopts,
                                                id_cryptoctx,
                                                PKINIT_ID_OPT_ANCHOR_CAS,
                                                idopts->anchors[i]);
        if (retval)
            goto errout;
    }
    for (i = 0; idopts->intermediates != NULL
                && idopts->intermediates[i] != NULL; i++) {
        retval = pkinit_identity_process_option(context, plg_cryptoctx,
                                                req_cryptoctx, idopts,
                                                id_cryptoctx,
                                                PKINIT_ID_OPT_INTERMEDIATE_CAS,
                                                idopts->intermediates[i]);
        if (retval)
            goto errout;
    }
    for (i = 0; idopts->crls != NULL && idopts->crls[i] != NULL; i++) {
        retval = pkinit_identity_process_option(context, plg_cryptoctx,
                                                req_cryptoctx, idopts,
                                                id_cryptoctx,
                                                PKINIT_ID_OPT_CRLS,
                                                idopts->crls[i]);
        if (retval)
            goto errout;
    }
    if (idopts->ocsp != NULL) {
        retval = pkinit_identity_process_option(context, plg_cryptoctx,
                                                req_cryptoctx, idopts,
                                                id_cryptoctx,
                                                PKINIT_ID_OPT_OCSP,
                                                idopts->ocsp);
        if (retval)
            goto errout;
    }

errout:
    return retval;
}