root/usr/src/lib/libelfsign/common/elfsignlib.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

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

#define ELF_TARGET_ALL  /* get definitions of all section flags */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <strings.h>
#include <stddef.h>
#include <stdlib.h>
#include <libintl.h>
#include <dirent.h>
#include <errno.h>
#include <libelf.h>
#include <gelf.h>
#include <cryptoutil.h>
#include <sha1.h>
#include <sys/crypto/elfsign.h>
#include <libelfsign.h>

#ifndef SHA1_DIGEST_LENGTH
#define SHA1_DIGEST_LENGTH 20
#endif /* SHA1_DIGEST_LENGTH */

const char SUNW_ELF_SIGNATURE_ID[] =    ELF_SIGNATURE_SECTION;
const char OID_sha1WithRSAEncryption[] = "1.2.840.113549.1.1.5";

static ELFsign_status_t elfsign_adjustoffsets(ELFsign_t ess,
    Elf_Scn *scn, uint64_t new_size);
static uint32_t elfsign_switch_uint32(uint32_t i);
static ELFsign_status_t elfsign_switch(ELFsign_t ess,
    struct filesignatures *fssp, enum ES_ACTION action);

struct filesig_extraction {
        filesig_vers_t  fsx_version;
        char    *fsx_format;
        char    fsx_signer_DN[ELFCERT_MAX_DN_LEN];
        size_t  fsx_signer_DN_len;
        uchar_t fsx_signature[SIG_MAX_LENGTH];
        size_t  fsx_sig_len;
        char    fsx_sig_oid[100];
        size_t  fsx_sig_oid_len;
        time_t  fsx_time;
};

static char *
version_to_str(filesig_vers_t v)
{
        char    *ret;

        switch (v) {
        case FILESIG_VERSION1:
                ret = "VERSION1";
                break;
        case FILESIG_VERSION2:
                ret = "VERSION2";
                break;
        case FILESIG_VERSION3:
                ret = "VERSION3";
                break;
        case FILESIG_VERSION4:
                ret = "VERSION4";
                break;
        default:
                ret = "UNKNOWN";
                break;
        }
        return (ret);
}

/*
 * Update filesignatures to include the v1/v2 filesig,
 *      composed of signer DN, signature, and OID.
 */
static struct filesignatures *
filesig_insert_dso(struct filesignatures *fssp,
    filesig_vers_t      version,
    const char          *dn,
    int                 dn_len,
    const uchar_t       *sig,
    int                 sig_len,
    const char          *oid,
    int                 oid_len)
{
        struct filesig  *fsgp;
        char            *fsdatap;

        if (oid == NULL) {
                /*
                 * This OID is used for the rsa_md5_sha1 format signature also.
                 * This use is historical, and is hence continued,
                 * despite its lack of technical accuracy.
                 */
                oid = OID_sha1WithRSAEncryption;
                oid_len = strlen(oid);
        }

        /*
         * for now, always insert a single-signature signature block
         */
        if (fssp != NULL)
                free(fssp);
        fssp  = (struct filesignatures *)
            malloc(filesig_ALIGN(sizeof (struct filesignatures) +
            dn_len + sig_len + oid_len));
        if (fssp == NULL)
                return (fssp);

        fssp->filesig_cnt = 1;
        fssp->filesig_pad = 0;  /* reserve for future use */

        fsgp = &fssp->filesig_sig;
        fsgp->filesig_size = sizeof (struct filesig) +
            dn_len + sig_len + oid_len;
        fsgp->filesig_version = version;
        switch (version) {
        case FILESIG_VERSION1:
        case FILESIG_VERSION2:
                fsgp->filesig_size -= sizeof (struct filesig) -
                    offsetof(struct filesig, filesig_v1_data[0]);
                fsgp->filesig_v1_dnsize = dn_len;
                fsgp->filesig_v1_sigsize = sig_len;
                fsgp->filesig_v1_oidsize = oid_len;
                fsdatap = &fsgp->filesig_v1_data[0];
                break;
        case FILESIG_VERSION3:
        case FILESIG_VERSION4:
                fsgp->filesig_size -= sizeof (struct filesig) -
                    offsetof(struct filesig, filesig_v3_data[0]);
                fsgp->filesig_v3_time = time(NULL);
                fsgp->filesig_v3_dnsize = dn_len;
                fsgp->filesig_v3_sigsize = sig_len;
                fsgp->filesig_v3_oidsize = oid_len;
                fsdatap = &fsgp->filesig_v3_data[0];
                break;
        default:
                cryptodebug("filesig_insert_dso: unknown version: %d",
                    version);
                free(fssp);
                return (NULL);
        }
        (void) memcpy(fsdatap, dn, dn_len);
        fsdatap += dn_len;
        (void) memcpy(fsdatap, (char *)sig, sig_len);
        fsdatap += sig_len;
        (void) memcpy(fsdatap, oid, oid_len);
        fsdatap += oid_len;
        fsgp = filesig_next(fsgp);
        (void) memset(fsdatap, 0, (char *)(fsgp) - fsdatap);

        return (fssp);
}

/*
 * filesig_extract - extract filesig structure to internal form
 */
static filesig_vers_t
filesig_extract(struct filesig *fsgp, struct filesig_extraction *fsxp)
{
        char    *fsdp;

#define filesig_extract_common(cp, field, data_var, len_var, len_limit)  { \
        len_var = len_limit; \
        if (len_var > fsgp->field) \
                len_var = fsgp->field; \
        (void) memcpy(data_var, cp, len_var); \
        cp += fsgp->field; }
#define filesig_extract_str(cp, field, data_var, len_var) \
        filesig_extract_common(cp, field, data_var, len_var, \
            sizeof (data_var) - 1); \
        data_var[len_var] = '\0';
#define filesig_extract_opaque(cp, field, data_var, len_var) \
        filesig_extract_common(cp, field, data_var, len_var, sizeof (data_var))

        fsxp->fsx_version = fsgp->filesig_version;
        cryptodebug("filesig_extract: version=%s",
            version_to_str(fsxp->fsx_version));
        switch (fsxp->fsx_version) {
        case FILESIG_VERSION1:
        case FILESIG_VERSION2:
                /*
                 * extract VERSION1 DN, signature, and OID
                 */
                fsdp = fsgp->filesig_v1_data;
                fsxp->fsx_format = ES_FMT_RSA_MD5_SHA1;
                fsxp->fsx_time = 0;
                filesig_extract_str(fsdp, filesig_v1_dnsize,
                    fsxp->fsx_signer_DN, fsxp->fsx_signer_DN_len);
                filesig_extract_opaque(fsdp, filesig_v1_sigsize,
                    fsxp->fsx_signature, fsxp->fsx_sig_len);
                filesig_extract_str(fsdp, filesig_v1_oidsize,
                    fsxp->fsx_sig_oid, fsxp->fsx_sig_oid_len);
                break;
        case FILESIG_VERSION3:
        case FILESIG_VERSION4:
                fsdp = fsgp->filesig_v3_data;
                fsxp->fsx_format = ES_FMT_RSA_SHA1;
                fsxp->fsx_time = fsgp->filesig_v3_time;
                filesig_extract_str(fsdp, filesig_v3_dnsize,
                    fsxp->fsx_signer_DN, fsxp->fsx_signer_DN_len);
                filesig_extract_opaque(fsdp, filesig_v3_sigsize,
                    fsxp->fsx_signature, fsxp->fsx_sig_len);
                filesig_extract_str(fsdp, filesig_v3_oidsize,
                    fsxp->fsx_sig_oid, fsxp->fsx_sig_oid_len);
                break;
        default:
                break;
        }

        return (fsxp->fsx_version);
}

ELFsign_status_t
elfsign_begin(const char *filename, enum ES_ACTION action, ELFsign_t *essp)
{
        Elf_Cmd         elfcmd;
        int             oflags = 0;
        short           l_type;
        ELFsign_t       ess;
        struct stat     stb;
        union {
                char    c[2];
                short   s;
        }       uorder;
        GElf_Ehdr       elfehdr;
        char            *ident;

        switch (action) {
        case ES_GET:
        case ES_GET_CRYPTO:
        case ES_GET_FIPS140:
                cryptodebug("elfsign_begin for get");
                elfcmd = ELF_C_READ;
                oflags = O_RDONLY | O_NOCTTY | O_NDELAY;
                l_type = F_RDLCK;
                break;
        case ES_UPDATE_RSA_MD5_SHA1:
        case ES_UPDATE_RSA_SHA1:
                cryptodebug("elfsign_begin for update");
                elfcmd = ELF_C_RDWR;
                oflags = O_RDWR | O_NOCTTY | O_NDELAY;
                l_type = F_WRLCK;
                break;
        default:
                return (ELFSIGN_UNKNOWN);
        }

        if ((ess = malloc(sizeof (struct ELFsign_s))) == NULL) {
                return (ELFSIGN_UNKNOWN);
        }
        (void) memset((void *)ess, 0, sizeof (struct ELFsign_s));

        if (!elfcertlib_init(ess)) {
                cryptodebug("elfsign_begin: failed initialization");
                return (ELFSIGN_UNKNOWN);
        }

        ess->es_elf = NULL;
        ess->es_action = action;
        ess->es_version = FILESIG_UNKNOWN;
        ess->es_pathname = NULL;
        ess->es_certpath = NULL;

        if (filename == NULL) {
                *essp = ess;
                return (ELFSIGN_SUCCESS);
        }

        if ((ess->es_fd = open(filename, oflags)) == -1) {
                elfsign_end(ess);
                return (ELFSIGN_INVALID_ELFOBJ);
        }
        if ((fstat(ess->es_fd, &stb) == -1) || !S_ISREG(stb.st_mode)) {
                elfsign_end(ess);
                return (ELFSIGN_INVALID_ELFOBJ);
        }
        if ((ess->es_pathname = strdup(filename)) == NULL) {
                elfsign_end(ess);
                return (ELFSIGN_UNKNOWN);
        }
        /*
         * The following lock is released in elfsign_end() when we close(2)
         * the es_fd. This ensures that we aren't trying verify a file
         * we are currently updating.
         */
        ess->es_flock.l_type = l_type;
        ess->es_flock.l_whence = SEEK_CUR;
        ess->es_flock.l_start = 0;
        ess->es_flock.l_len = 0;
        if (fcntl(ess->es_fd, F_SETLK, &ess->es_flock) == -1) {
                cryptodebug("fcntl(F_SETLK) of %s failed with: %s",
                    ess->es_pathname, strerror(errno));
                elfsign_end(ess);
                return (ELFSIGN_UNKNOWN);
        }

        if (elf_version(EV_CURRENT) == EV_NONE) {
                elfsign_end(ess);
                return (ELFSIGN_UNKNOWN);
        }

        if ((ess->es_elf = elf_begin(ess->es_fd, elfcmd,
            (Elf *)NULL)) == NULL) {
                cryptodebug("elf_begin() failed: %s", elf_errmsg(-1));
                elfsign_end(ess);
                return (ELFSIGN_INVALID_ELFOBJ);
        }

        if (gelf_getehdr(ess->es_elf, &elfehdr) == NULL) {
                cryptodebug("elf_getehdr() failed: %s", elf_errmsg(-1));
                elfsign_end(ess);
                return (ELFSIGN_INVALID_ELFOBJ);
        }
        ess->es_has_phdr = (elfehdr.e_phnum != 0);

        uorder.s = ELFDATA2MSB << 8 | ELFDATA2LSB;
        ident = elf_getident(ess->es_elf, NULL);
        if (ident == NULL) {
                cryptodebug("elf_getident() failed: %s", elf_errmsg(-1));
                elfsign_end(ess);
                return (ELFSIGN_INVALID_ELFOBJ);
        }
        ess->es_same_endian = (ident[EI_DATA] == uorder.c[0]);
        ess->es_ei_class = ident[EI_CLASS];

        /*
         * Call elf_getshstrndx to be sure we have a real ELF object
         * this is required because elf_begin doesn't check that.
         */
        if (elf_getshstrndx(ess->es_elf, &ess->es_shstrndx) == 0) {
                elfsign_end(ess);
                cryptodebug("elfsign_begin: elf_getshstrndx failed");
                return (ELFSIGN_INVALID_ELFOBJ);
        }

        /*
         * Make sure libelf doesn't rearrange section ordering / offsets.
         */
        (void) elf_flagelf(ess->es_elf, ELF_C_SET, ELF_F_LAYOUT);

        *essp = ess;

        return (ELFSIGN_SUCCESS);
}

/*
 * elfsign_end - cleanup the ELFsign_t
 *
 * IN/OUT:      ess
 */
void
elfsign_end(ELFsign_t ess)
{
        if (ess == NULL)
                return;

        if (ess->es_elf != NULL && ES_ACTISUPDATE(ess->es_action)) {
                if (elf_update(ess->es_elf, ELF_C_WRITE) == -1) {
                        cryptodebug("elf_update() failed: %s",
                            elf_errmsg(-1));
                        return;
                }
        }

        if (ess->es_fd != -1) {
                (void) close(ess->es_fd);
                ess->es_fd = -1;
        }

        if (ess->es_pathname != NULL) {
                free(ess->es_pathname);
                ess->es_pathname = NULL;
        }
        if (ess->es_certpath != NULL) {
                free(ess->es_certpath);
                ess->es_certpath = NULL;
        }

        if (ess->es_elf != NULL) {
                (void) elf_end(ess->es_elf);
                ess->es_elf = NULL;
        }

        elfcertlib_fini(ess);

        free(ess);
}

/*
 * set the certificate path
 */
ELFsign_status_t
elfsign_setcertpath(ELFsign_t ess, const char *certpath)
{
        /*
         * Normally use of access(2) is insecure, here we are only
         * doing it to help provide early failure and better error
         * checking, so there is no race condition.
         */
        if (access(certpath, R_OK) != 0)
                return (ELFSIGN_INVALID_CERTPATH);

        if ((ess->es_certpath = strdup(certpath)) == NULL)
                return (ELFSIGN_FAILED);

        if (ES_ACTISUPDATE(ess->es_action)) {
                ELFCert_t       cert = NULL;
                char            *subject;

                /* set the version based on the certificate */
                if (elfcertlib_getcert(ess, ess->es_certpath, NULL,
                    &cert, ess->es_action)) {
                        if ((subject = elfcertlib_getdn(cert)) != NULL) {
                                if (strstr(subject, ELFSIGN_CRYPTO))
                                        ess->es_version = (ess->es_action ==
                                            ES_UPDATE_RSA_MD5_SHA1) ?
                                            FILESIG_VERSION1 : FILESIG_VERSION3;
                                else
                                        ess->es_version = (ess->es_action ==
                                            ES_UPDATE_RSA_MD5_SHA1) ?
                                            FILESIG_VERSION2 : FILESIG_VERSION4;
                        }
                        elfcertlib_releasecert(ess, cert);
                }
                if (ess->es_version == FILESIG_UNKNOWN)
                        return (ELFSIGN_FAILED);
        }
        return (ELFSIGN_SUCCESS);
}

/*
 * set the callback context
 */
void
elfsign_setcallbackctx(ELFsign_t ess, void *ctx)
{
        ess->es_callbackctx = ctx;
}

/*
 * set the signature extraction callback
 */
void
elfsign_setsigvercallback(ELFsign_t ess,
    void (*cb)(void *, void *, size_t, ELFCert_t))
{
        ess->es_sigvercallback = cb;
}

/*
 * elfsign_signatures
 *
 * IN:  ess, fsspp, action
 * OUT: fsspp
 */
ELFsign_status_t
elfsign_signatures(ELFsign_t ess,
    struct filesignatures **fsspp,
    size_t *fslen,
    enum ES_ACTION action)
{
        Elf_Scn         *scn = NULL, *sig_scn = NULL;
        GElf_Shdr       shdr;
        Elf_Data        *data = NULL;
        const char      *elf_section = SUNW_ELF_SIGNATURE_ID;
        int             fscnt, fssize;
        struct filesig  *fsgp, *fsgpnext;
        uint64_t        sig_offset = 0;

        cryptodebug("elfsign_signature");
        if ((ess == NULL) || (fsspp == NULL)) {
                cryptodebug("invalid arguments");
                return (ELFSIGN_UNKNOWN);
        }

        cryptodebug("elfsign_signature %s for %s",
            ES_ACTISUPDATE(action) ? "ES_UPDATE" : "ES_GET", elf_section);

        (void) elf_errno();
        while ((scn = elf_nextscn(ess->es_elf, scn)) != NULL) {
                const char      *sh_name;
                /*
                 * Do a string compare to examine each section header
                 * to see if this is the section that needs to be updated.
                 */
                if (gelf_getshdr(scn, &shdr) == NULL) {
                        cryptodebug("gelf_getshdr() failed: %s",
                            elf_errmsg(-1));
                        return (ELFSIGN_FAILED);
                }
                sh_name = elf_strptr(ess->es_elf, ess->es_shstrndx,
                    (size_t)shdr.sh_name);
                if (strcmp(sh_name, elf_section) == 0) {
                        cryptodebug("elfsign_signature: found %s", elf_section);
                        sig_scn = scn;
                        break;
                }
                if (shdr.sh_type != SHT_NOBITS &&
                    sig_offset < shdr.sh_offset + shdr.sh_size) {
                        sig_offset = shdr.sh_offset + shdr.sh_size;
                }
        }
        if (elf_errmsg(0) != NULL) {
                cryptodebug("unexpected error: %s", elf_section,
                    elf_errmsg(-1));
                return (ELFSIGN_FAILED);
        }

        if (ES_ACTISUPDATE(action) && (sig_scn == NULL))  {
                size_t  old_size, new_size;
                char    *new_d_buf;

                cryptodebug("elfsign_signature: %s not found - creating",
                    elf_section);

                /*
                 * insert section name in .shstrtab
                 */
                if ((scn = elf_getscn(ess->es_elf, ess->es_shstrndx)) == 0) {
                        cryptodebug("elf_getscn() failed: %s",
                            elf_errmsg(-1));
                        return (ELFSIGN_FAILED);
                }
                if (gelf_getshdr(scn, &shdr) == NULL) {
                        cryptodebug("gelf_getshdr() failed: %s",
                            elf_errmsg(-1));
                        return (ELFSIGN_FAILED);
                }
                if ((data = elf_getdata(scn, data)) == NULL) {
                        cryptodebug("elf_getdata() failed: %s",
                            elf_errmsg(-1));
                        return (ELFSIGN_FAILED);
                }
                old_size = data->d_size;
                if (old_size != shdr.sh_size) {
                        cryptodebug("mismatch between data size %d "
                            "and section size %lld", old_size, shdr.sh_size);
                        return (ELFSIGN_FAILED);
                }
                new_size = old_size + strlen(elf_section) + 1;
                if ((new_d_buf = malloc(new_size)) == NULL)
                        return (ELFSIGN_FAILED);

                (void) memcpy(new_d_buf, data->d_buf, old_size);
                (void) strlcpy(new_d_buf + old_size, elf_section,
                    new_size - old_size);
                data->d_buf = new_d_buf;
                data->d_size = new_size;
                data->d_align = 1;
                /*
                 * Add the section name passed in to the end of the file.
                 * Initialize the fields in the Section Header that
                 * libelf will not fill in.
                 */
                if ((sig_scn = elf_newscn(ess->es_elf)) == 0) {
                        cryptodebug("elf_newscn() failed: %s",
                            elf_errmsg(-1));
                        return (ELFSIGN_FAILED);
                }
                if (gelf_getshdr(sig_scn, &shdr) == 0) {
                        cryptodebug("gelf_getshdr() failed: %s",
                            elf_errmsg(-1));
                        return (ELFSIGN_FAILED);
                }
                shdr.sh_name = old_size;
                shdr.sh_type = SHT_SUNW_SIGNATURE;
                shdr.sh_flags = SHF_EXCLUDE;
                shdr.sh_addr = 0;
                shdr.sh_link = 0;
                shdr.sh_info = 0;
                shdr.sh_size = 0;
                shdr.sh_offset = sig_offset;
                shdr.sh_addralign = 1;

                /*
                 * Flush the changes to the underlying elf32 or elf64
                 * section header.
                 */
                if (gelf_update_shdr(sig_scn, &shdr) == 0) {
                        cryptodebug("gelf_update_shdr failed");
                        return (ELFSIGN_FAILED);
                }

                if ((data = elf_newdata(sig_scn)) == NULL) {
                        cryptodebug("can't add elf data area for %s: %s",
                            elf_section, elf_errmsg(-1));
                        return (ELFSIGN_FAILED);
                }
                if (elfsign_adjustoffsets(ess, scn,
                    old_size + strlen(elf_section) + 1) != ELFSIGN_SUCCESS) {
                        cryptodebug("can't adjust for new section name %s",
                            elf_section);
                        return (ELFSIGN_FAILED);
                }
        } else {
                if (sig_scn == NULL) {
                        cryptodebug("can't find signature section");
                        *fsspp = NULL;
                        return (ELFSIGN_NOTSIGNED);
                }
                if ((data = elf_getdata(sig_scn, NULL)) == 0) {
                        cryptodebug("can't get section data for %s",
                            elf_section);
                        return (ELFSIGN_FAILED);
                }
        }

        if (ES_ACTISUPDATE(action))  {
                fssize = offsetof(struct filesignatures, _u1);
                if (*fsspp != NULL) {
                        fsgp = &(*fsspp)->filesig_sig;
                        for (fscnt = 0; fscnt < (*fsspp)->filesig_cnt;
                            fscnt++) {
                                fsgpnext = filesig_next(fsgp);
                                fssize += (char *)(fsgpnext) - (char *)(fsgp);
                                fsgp = fsgpnext;
                        }
                }
                if (shdr.sh_addr != 0) {
                        cryptodebug("section %s is part of a loadable segment, "
                            "it cannot be changed.\n", elf_section);
                        return (ELFSIGN_FAILED);
                }
                if ((data->d_buf = malloc(fssize)) == NULL)
                        return (ELFSIGN_FAILED);
                if (*fsspp != NULL) {
                        (void) memcpy(data->d_buf, *fsspp, fssize);
                        (void) elfsign_switch(ess,
                            (struct filesignatures *)data->d_buf, action);
                }
                data->d_size = fssize;
                data->d_align = 1;
                data->d_type = ELF_T_BYTE;
                cryptodebug("elfsign_signature: data->d_size = %d",
                    data->d_size);
                if (elfsign_adjustoffsets(ess, sig_scn, fssize) !=
                    ELFSIGN_SUCCESS) {
                        cryptodebug("can't adjust for revised signature "
                            "section contents");
                        return (ELFSIGN_FAILED);
                }
        } else {
                *fsspp = malloc(data->d_size);
                if (*fsspp == NULL)
                        return (ELFSIGN_FAILED);
                (void) memcpy(*fsspp, data->d_buf, data->d_size);
                if (elfsign_switch(ess, *fsspp, ES_GET) != ELFSIGN_SUCCESS) {
                        free(*fsspp);
                        *fsspp = NULL;
                        return (ELFSIGN_FAILED);
                }
                *fslen = data->d_size;
        }

        return (ELFSIGN_SUCCESS);
}

static ELFsign_status_t
elfsign_adjustoffsets(ELFsign_t ess, Elf_Scn *scn, uint64_t new_size)
{
        GElf_Ehdr       elfehdr;
        GElf_Shdr       shdr;
        uint64_t        prev_end, scn_offset;
        char            *name;
        Elf_Scn         *scnp;
        Elf_Data        *data;
        ELFsign_status_t        retval = ELFSIGN_FAILED;
        struct scninfo {
                struct scninfo  *scni_next;
                Elf_Scn         *scni_scn;
                uint64_t        scni_offset;
        }               *scnip = NULL, *tmpscnip, **scnipp;

        /* get the size of the current section */
        if (gelf_getshdr(scn, &shdr) == NULL)
                return (ELFSIGN_FAILED);
        if (shdr.sh_size == new_size)
                return (ELFSIGN_SUCCESS);
        scn_offset = shdr.sh_offset;
        name = elf_strptr(ess->es_elf, ess->es_shstrndx,
            (size_t)shdr.sh_name);
        if (shdr.sh_flags & SHF_ALLOC && ess->es_has_phdr) {
                cryptodebug("elfsign_adjustoffsets: "
                    "can't move allocated section %s", name ? name : "NULL");
                return (ELFSIGN_FAILED);
        }

        /* resize the desired section */
        cryptodebug("elfsign_adjustoffsets: "
            "resizing %s at 0x%llx from 0x%llx to 0x%llx",
            name ? name : "NULL", shdr.sh_offset, shdr.sh_size, new_size);
        shdr.sh_size = new_size;
        if (gelf_update_shdr(scn, &shdr) == 0) {
                cryptodebug("gelf_update_shdr failed");
                goto bad;
        }
        prev_end = shdr.sh_offset + shdr.sh_size;

        /*
         * find sections whose data follows the changed section
         *      must scan all sections since section data may not
         *      be in same order as section headers
         */
        scnp = elf_getscn(ess->es_elf, 0);      /* "seek" to start */
        while ((scnp = elf_nextscn(ess->es_elf, scnp)) != NULL) {
                if (gelf_getshdr(scnp, &shdr) == NULL)
                        goto bad;
                if (shdr.sh_offset <= scn_offset)
                        continue;
                name = elf_strptr(ess->es_elf, ess->es_shstrndx,
                    (size_t)shdr.sh_name);
                if (shdr.sh_flags & SHF_ALLOC && ess->es_has_phdr) {
                        if (shdr.sh_type == SHT_NOBITS) {
                                /* .bss can occasionally overlap .shrtab */
                                continue;
                        }
                        cryptodebug("elfsign_adjustoffsets: "
                            "can't move allocated section %s",
                            name ? name : "NULL");
                        goto bad;
                }
                /*
                 * force reading of data to memory image
                 */
                data = NULL;
                while ((data = elf_rawdata(scnp, data)) != NULL)
                        ;
                /*
                 * capture section information
                 * insert into list in order of sh_offset
                 */
                cryptodebug("elfsign_adjustoffsets: "
                    "may have to adjust section %s, offset 0x%llx",
                    name ? name : "NULL", shdr.sh_offset);
                tmpscnip = (struct scninfo *)malloc(sizeof (struct scninfo));
                if (tmpscnip == NULL) {
                        cryptodebug("elfsign_adjustoffsets: "
                            "memory allocation failure");
                        goto bad;
                }
                tmpscnip->scni_scn = scnp;
                tmpscnip->scni_offset = shdr.sh_offset;
                for (scnipp = &scnip; *scnipp != NULL;
                    scnipp = &(*scnipp)->scni_next) {
                        if ((*scnipp)->scni_offset > tmpscnip->scni_offset)
                                break;
                }
                tmpscnip->scni_next = *scnipp;
                *scnipp = tmpscnip;
        }

        /* move following sections as necessary */
        for (tmpscnip = scnip; tmpscnip != NULL;
            tmpscnip = tmpscnip->scni_next) {
                scnp = tmpscnip->scni_scn;
                if (gelf_getshdr(scnp, &shdr) == NULL) {
                        cryptodebug("elfsign_adjustoffsets: "
                            "elf_getshdr for section %d failed",
                            elf_ndxscn(scnp));
                        goto bad;
                }
                if (shdr.sh_offset >= prev_end)
                        break;
                prev_end = (prev_end + shdr.sh_addralign - 1) &
                    (-shdr.sh_addralign);
                name = elf_strptr(ess->es_elf, ess->es_shstrndx,
                    (size_t)shdr.sh_name);
                cryptodebug("elfsign_adjustoffsets: "
                    "moving %s size 0x%llx from 0x%llx to 0x%llx",
                    name ? name : "NULL", shdr.sh_size,
                    shdr.sh_offset, prev_end);
                shdr.sh_offset = prev_end;
                if (gelf_update_shdr(scnp, &shdr) == 0) {
                        cryptodebug("gelf_update_shdr failed");
                        goto bad;
                }
                prev_end = shdr.sh_offset + shdr.sh_size;
        }

        /*
         * adjust section header offset in elf header
         */
        if (gelf_getehdr(ess->es_elf, &elfehdr) == NULL) {
                cryptodebug("elf_getehdr() failed: %s", elf_errmsg(-1));
                goto bad;
        }
        if (elfehdr.e_shoff < prev_end) {
                if (ess->es_ei_class == ELFCLASS32)
                        prev_end = (prev_end + ELF32_FSZ_OFF - 1) &
                            (-ELF32_FSZ_OFF);
                else if (ess->es_ei_class == ELFCLASS64)
                        prev_end = (prev_end + ELF64_FSZ_OFF - 1) &
                            (-ELF64_FSZ_OFF);
                cryptodebug("elfsign_adjustoffsets: "
                    "move sh_off from 0x%llx to 0x%llx",
                    elfehdr.e_shoff, prev_end);
                elfehdr.e_shoff = prev_end;
                if (gelf_update_ehdr(ess->es_elf, &elfehdr) == 0) {
                        cryptodebug("elf_update_ehdr() failed: %s",
                            elf_errmsg(-1));
                        goto bad;
                }
        }

        retval = ELFSIGN_SUCCESS;

bad:
        while (scnip != NULL) {
                tmpscnip = scnip->scni_next;
                free(scnip);
                scnip = tmpscnip;
        }
        return (retval);
}

struct filesignatures *
elfsign_insert_dso(ELFsign_t ess,
    struct filesignatures *fssp,
    const char          *dn,
    int                 dn_len,
    const uchar_t       *sig,
    int                 sig_len,
    const char          *oid,
    int                 oid_len)
{
        return (filesig_insert_dso(fssp, ess->es_version, dn, dn_len,
            sig, sig_len, oid, oid_len));
}

/*ARGSUSED*/
filesig_vers_t
elfsign_extract_sig(ELFsign_t ess,
    struct filesignatures *fssp,
    uchar_t             *sig,
    size_t              *sig_len)
{
        struct filesig_extraction       fsx;
        filesig_vers_t  version;

        if (fssp == NULL)
                return (FILESIG_UNKNOWN);
        if (fssp->filesig_cnt != 1)
                return (FILESIG_UNKNOWN);
        version = filesig_extract(&fssp->filesig_sig, &fsx);
        switch (version) {
        case FILESIG_VERSION1:
        case FILESIG_VERSION2:
        case FILESIG_VERSION3:
        case FILESIG_VERSION4:
                if (*sig_len >= fsx.fsx_sig_len) {
                        (void) memcpy((char *)sig, (char *)fsx.fsx_signature,
                            *sig_len);
                        *sig_len = fsx.fsx_sig_len;
                } else
                        version = FILESIG_UNKNOWN;
                break;
        default:
                version = FILESIG_UNKNOWN;
                break;
        }

        if (ess->es_version == FILESIG_UNKNOWN) {
                ess->es_version = version;
        }

        return (version);
}

static ELFsign_status_t
elfsign_hash_common(ELFsign_t ess, uchar_t *hash, size_t *hash_len,
    boolean_t hash_mem_resident)
{
        Elf_Scn         *scn = NULL;
        ELFsign_status_t elfstat;
        GElf_Shdr       shdr;
        SHA1_CTX        ctx;

        /* The buffer must be large enough to hold the hash */
        if (*hash_len < SHA1_DIGEST_LENGTH)
                return (ELFSIGN_FAILED);

        bzero(hash, *hash_len);

        /* Initialize the digest session */
        SHA1Init(&ctx);

        scn = elf_getscn(ess->es_elf, 0);       /* "seek" to start */
        (void) elf_errno();
        while ((scn = elf_nextscn(ess->es_elf, scn)) != 0) {
                char *name = NULL;
                Elf_Data *data = NULL;

                if (gelf_getshdr(scn, &shdr) == NULL) {
                        elfstat = ELFSIGN_FAILED;
                        goto done;
                }

                name = elf_strptr(ess->es_elf, ess->es_shstrndx,
                    (size_t)shdr.sh_name);
                if (name == NULL)
                        name = "NULL";

                if (!hash_mem_resident &&
                    (ess->es_version == FILESIG_VERSION1 ||
                    ess->es_version == FILESIG_VERSION3)) {
                        /*
                         * skip the signature section only
                         */
                        if (shdr.sh_type == SHT_SUNW_SIGNATURE) {
                                cryptodebug("elfsign_hash: skipping %s", name);
                                continue;
                        }
                } else if (!(shdr.sh_flags & SHF_ALLOC)) {
                        /*
                         * select only memory resident sections
                         */
                        cryptodebug("elfsign_hash: skipping %s", name);
                        continue;
                }

                /*
                 * throw this section into the hash
                 *   use elf_rawdata for endian-independence
                 *   use elf_getdata to get update of .shstrtab
                 */
                while ((data = (shdr.sh_type == SHT_STRTAB ?
                    elf_getdata(scn, data) : elf_rawdata(scn, data))) != NULL) {
                        if (data->d_buf == NULL) {
                                cryptodebug("elfsign_hash: %s has NULL data",
                                    name);
                                continue;
                        }
                        cryptodebug("elfsign_hash: updating hash "
                            "with %s data size=%d", name, data->d_size);
                        SHA1Update(&ctx, data->d_buf, data->d_size);
                }
        }
        if (elf_errmsg(0) != NULL) {
                cryptodebug("elfsign_hash: %s", elf_errmsg(-1));
                elfstat = ELFSIGN_FAILED;
                goto done;
        }

        SHA1Final(hash, &ctx);
        *hash_len = SHA1_DIGEST_LENGTH;
        { /* DEBUG START */
                const int hashstr_len = (*hash_len) * 2 + 1;
                char *hashstr = malloc(hashstr_len);

                if (hashstr != NULL) {
                        tohexstr(hash, *hash_len, hashstr, hashstr_len);
                        cryptodebug("hash value is: %s", hashstr);
                        free(hashstr);
                }
        } /* DEBUG END */
        elfstat = ELFSIGN_SUCCESS;
done:
        return (elfstat);
}

/*
 * elfsign_hash - return the hash of the ELF sections affecting execution.
 *
 * IN:          ess, hash_len
 * OUT:         hash, hash_len
 */
ELFsign_status_t
elfsign_hash(ELFsign_t ess, uchar_t *hash, size_t *hash_len)
{
        return (elfsign_hash_common(ess, hash, hash_len, B_FALSE));
}

/*
 * elfsign_hash_mem_resident - return the hash of the ELF sections
 * with only memory resident sections.
 *
 * IN:          ess, hash_len
 * OUT:         hash, hash_len
 */
ELFsign_status_t
elfsign_hash_mem_resident(ELFsign_t ess, uchar_t *hash, size_t *hash_len)
{
        return (elfsign_hash_common(ess, hash, hash_len, B_TRUE));
}


/*
 * elfsign_verify_signature - Verify the signature of the ELF object.
 *
 * IN:          ess
 * OUT:         esipp
 * RETURNS:
 *      ELFsign_status_t
 */
ELFsign_status_t
elfsign_verify_signature(ELFsign_t ess, struct ELFsign_sig_info **esipp)
{
        ELFsign_status_t        ret = ELFSIGN_FAILED;
        struct  filesignatures *fssp;
        struct  filesig *fsgp;
        size_t  fslen;
        struct filesig_extraction       fsx;
        uchar_t hash[SIG_MAX_LENGTH];
        size_t  hash_len;
        ELFCert_t       cert = NULL;
        int     sigcnt;
        int     nocert = 0;
        struct ELFsign_sig_info *esip = NULL;

        if (esipp != NULL) {
                esip = (struct ELFsign_sig_info *)
                    calloc(1, sizeof (struct ELFsign_sig_info));
                *esipp = esip;
        }

        /*
         * Find out which cert we need, based on who signed the ELF object
         */
        if (elfsign_signatures(ess, &fssp, &fslen, ES_GET) != ELFSIGN_SUCCESS) {
                return (ELFSIGN_NOTSIGNED);
        }

        if (fssp->filesig_cnt < 1) {
                ret = ELFSIGN_FAILED;
                goto cleanup;
        }

        fsgp = &fssp->filesig_sig;

        /*
         * Scan the signature block, looking for a verifiable signature
         */
        for (sigcnt = 0; sigcnt < fssp->filesig_cnt;
            sigcnt++, fsgp = filesig_next(fsgp)) {
                ess->es_version = filesig_extract(fsgp, &fsx);
                cryptodebug("elfsign_verify_signature: version=%s",
                    version_to_str(ess->es_version));
                switch (ess->es_version) {
                case FILESIG_VERSION1:
                case FILESIG_VERSION2:
                case FILESIG_VERSION3:
                case FILESIG_VERSION4:
                        break;
                default:
                        ret = ELFSIGN_FAILED;
                        goto cleanup;
                }

                cryptodebug("elfsign_verify_signature: signer_DN=\"%s\"",
                    fsx.fsx_signer_DN);
                cryptodebug("elfsign_verify_signature: algorithmOID=\"%s\"",
                    fsx.fsx_sig_oid);
                /* return signer DN if requested */
                if (esipp != NULL) {
                        esip->esi_format = fsx.fsx_format;
                        if (esip->esi_signer != NULL)
                                free(esip->esi_signer);
                        esip->esi_signer = strdup(fsx.fsx_signer_DN);
                        esip->esi_time = fsx.fsx_time;
                }

                /*
                 * look for certificate
                 */
                if (cert != NULL)
                        elfcertlib_releasecert(ess, cert);

                /*
                 * skip unfound certificates
                 */
                if (!elfcertlib_getcert(ess, ess->es_certpath,
                    fsx.fsx_signer_DN, &cert, ess->es_action)) {
                        cryptodebug("unable to find certificate "
                            "with DN=\"%s\" for %s",
                            fsx.fsx_signer_DN, ess->es_pathname);
                        nocert++;
                        continue;
                }

                /*
                 * skip unverified certificates
                 *      force verification of crypto certs
                 */
                if ((ess->es_action == ES_GET_CRYPTO ||
                    ess->es_action == ES_GET_FIPS140 ||
                    strstr(fsx.fsx_signer_DN, ELFSIGN_CRYPTO)) &&
                    !elfcertlib_verifycert(ess, cert)) {
                        cryptodebug("elfsign_verify_signature: invalid cert");
                        nocert++;
                        continue;
                }

                /*
                 * At this time the only sha1WithRSAEncryption is supported,
                 * so check that is what we have and skip with anything else.
                 */
                if (strcmp(fsx.fsx_sig_oid, OID_sha1WithRSAEncryption) != 0) {
                        continue;
                }

                nocert = 0;
                /*
                 * compute file hash
                 */
                hash_len = sizeof (hash);
                if (elfsign_hash(ess, hash, &hash_len) != ELFSIGN_SUCCESS) {
                        cryptodebug("elfsign_verify_signature:"
                            " elfsign_hash failed");
                        ret = ELFSIGN_FAILED;
                        break;
                }

                { /* DEBUG START */
                        const int sigstr_len = fsx.fsx_sig_len * 2 + 1;
                        char *sigstr = malloc(sigstr_len);

                        if (sigstr != NULL) {
                                tohexstr(fsx.fsx_signature, fsx.fsx_sig_len,
                                    sigstr, sigstr_len);
                                cryptodebug("signature value is: %s", sigstr);
                                free(sigstr);
                        }
                } /* DEBUG END */

                if (elfcertlib_verifysig(ess, cert,
                    fsx.fsx_signature, fsx.fsx_sig_len, hash, hash_len)) {
                        if (ess->es_sigvercallback)
                                (ess->es_sigvercallback)
                                    (ess->es_callbackctx, fssp, fslen, cert);
                        /*
                         * The signature is verified!
                         */
                        ret = ELFSIGN_SUCCESS;
                }

                cryptodebug("elfsign_verify_signature: invalid signature");
        }

cleanup:
        if (cert != NULL)
                elfcertlib_releasecert(ess, cert);

        free(fssp);
        if (ret == ELFSIGN_FAILED && nocert)
                ret = ELFSIGN_INVALID_CERTPATH;
        return (ret);
}


static uint32_t
elfsign_switch_uint32(uint32_t i)
{
        return (((i & 0xff) << 24) | ((i & 0xff00) << 8) |
            ((i >> 8) & 0xff00) | ((i >> 24) & 0xff));
}

static uint64_t
elfsign_switch_uint64(uint64_t i)
{
        return (((uint64_t)elfsign_switch_uint32(i) << 32) |
            (elfsign_switch_uint32(i >> 32)));
}

/*
 * If appropriate, switch the endianness of the filesignatures structure
 *      Examine the structure only when it is in native endianness
 */
static ELFsign_status_t
elfsign_switch(ELFsign_t ess, struct filesignatures *fssp,
    enum ES_ACTION action)
{
        int             fscnt;
        filesig_vers_t  version;
        struct filesig  *fsgp, *fsgpnext;

        if (ess->es_same_endian)
                return (ELFSIGN_SUCCESS);

        if (ES_ACTISUPDATE(action))
                fscnt = fssp->filesig_cnt;
        fssp->filesig_cnt = elfsign_switch_uint32(fssp->filesig_cnt);
        if (!ES_ACTISUPDATE(action))
                fscnt = fssp->filesig_cnt;

        fsgp = &(fssp)->filesig_sig;
        for (; fscnt > 0; fscnt--, fsgp = fsgpnext) {
                if (ES_ACTISUPDATE(action)) {
                        version = fsgp->filesig_version;
                        fsgpnext = filesig_next(fsgp);
                }
                fsgp->filesig_size =
                    elfsign_switch_uint32(fsgp->filesig_size);
                fsgp->filesig_version =
                    elfsign_switch_uint32(fsgp->filesig_version);
                if (!ES_ACTISUPDATE(action)) {
                        version = fsgp->filesig_version;
                        fsgpnext = filesig_next(fsgp);
                }
                switch (version) {
                case FILESIG_VERSION1:
                case FILESIG_VERSION2:
                        fsgp->filesig_v1_dnsize =
                            elfsign_switch_uint32(fsgp->filesig_v1_dnsize);
                        fsgp->filesig_v1_sigsize =
                            elfsign_switch_uint32(fsgp->filesig_v1_sigsize);
                        fsgp->filesig_v1_oidsize =
                            elfsign_switch_uint32(fsgp->filesig_v1_oidsize);
                        break;
                case FILESIG_VERSION3:
                case FILESIG_VERSION4:
                        fsgp->filesig_v3_time =
                            elfsign_switch_uint64(fsgp->filesig_v3_time);
                        fsgp->filesig_v3_dnsize =
                            elfsign_switch_uint32(fsgp->filesig_v3_dnsize);
                        fsgp->filesig_v3_sigsize =
                            elfsign_switch_uint32(fsgp->filesig_v3_sigsize);
                        fsgp->filesig_v3_oidsize =
                            elfsign_switch_uint32(fsgp->filesig_v3_oidsize);
                        break;
                default:
                        cryptodebug("elfsign_switch: failed");
                        return (ELFSIGN_FAILED);
                }
        }
        return (ELFSIGN_SUCCESS);
}

/*
 * get/put an integer value from/to a buffer, possibly of opposite endianness
 */
void
elfsign_buffer_len(ELFsign_t ess, size_t *ip, uchar_t *cp,
    enum ES_ACTION action)
{
        uint32_t tmp;

        if (!ES_ACTISUPDATE(action)) {
                /* fetch integer from buffer */
                (void) memcpy(&tmp, cp, sizeof (tmp));
                if (!ess->es_same_endian) {
                        tmp = elfsign_switch_uint32(tmp);
                }
                *ip = tmp;
        } else {
                /* put integer into buffer */
                tmp = *ip;
                if (!ess->es_same_endian) {
                        tmp = elfsign_switch_uint32(tmp);
                }
                (void) memcpy(cp, &tmp, sizeof (tmp));
        }
}

char const *
elfsign_strerror(ELFsign_status_t elferror)
{
        char const *msg = NULL;

        switch (elferror) {
                case ELFSIGN_SUCCESS:
                        msg = gettext("sign or verify of ELF object succeeded");
                        break;
                case ELFSIGN_FAILED:
                        msg = gettext("sign or verify of ELF object failed");
                        break;
                case ELFSIGN_NOTSIGNED:
                        msg = gettext("ELF object not signed");
                        break;
                case ELFSIGN_INVALID_CERTPATH:
                        msg = gettext("cannot access certificate");
                        break;
                case ELFSIGN_INVALID_ELFOBJ:
                        msg = gettext("unable to open as an ELF object");
                        break;
                case ELFSIGN_UNKNOWN:
                default:
                        msg = gettext("Unknown error");
                        break;
        }

        return (msg);
}

boolean_t
elfsign_sig_info(struct filesignatures *fssp, struct ELFsign_sig_info **esipp)
{
        struct filesig_extraction       fsx;
        struct ELFsign_sig_info *esip;

        esip = (struct ELFsign_sig_info *)
            calloc(1, sizeof (struct ELFsign_sig_info));
        *esipp = esip;
        if (esip == NULL)
                return (B_FALSE);

        switch (filesig_extract(&fssp->filesig_sig, &fsx)) {
        case FILESIG_VERSION1:
        case FILESIG_VERSION2:
        case FILESIG_VERSION3:
        case FILESIG_VERSION4:
                esip->esi_format = fsx.fsx_format;
                esip->esi_signer = strdup(fsx.fsx_signer_DN);
                esip->esi_time = fsx.fsx_time;
                break;
        default:
                free(esip);
                *esipp = NULL;
        }

        return (*esipp != NULL);
}

void
elfsign_sig_info_free(struct ELFsign_sig_info *esip)
{
        if (esip != NULL) {
                free(esip->esi_signer);
                free(esip);
        }
}