root/crypto/heimdal/lib/hx509/ks_keychain.c
/*
 * Copyright (c) 2007 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "hx_locl.h"

#ifdef HAVE_FRAMEWORK_SECURITY

#include <Security/Security.h>

/* Missing function decls in pre Leopard */
#ifdef NEED_SECKEYGETCSPHANDLE_PROTO
OSStatus SecKeyGetCSPHandle(SecKeyRef, CSSM_CSP_HANDLE *);
OSStatus SecKeyGetCredentials(SecKeyRef, CSSM_ACL_AUTHORIZATION_TAG,
                              int, const CSSM_ACCESS_CREDENTIALS **);
#define kSecCredentialTypeDefault 0
#define CSSM_SIZE uint32_t
#endif


static int
getAttribute(SecKeychainItemRef itemRef, SecItemAttr item,
             SecKeychainAttributeList **attrs)
{
    SecKeychainAttributeInfo attrInfo;
    UInt32 attrFormat = 0;
    OSStatus ret;

    *attrs = NULL;

    attrInfo.count = 1;
    attrInfo.tag = &item;
    attrInfo.format = &attrFormat;

    ret = SecKeychainItemCopyAttributesAndData(itemRef, &attrInfo, NULL,
                                               attrs, NULL, NULL);
    if (ret)
        return EINVAL;
    return 0;
}


/*
 *
 */

struct kc_rsa {
    SecKeychainItemRef item;
    size_t keysize;
};


static int
kc_rsa_public_encrypt(int flen,
                      const unsigned char *from,
                      unsigned char *to,
                      RSA *rsa,
                      int padding)
{
    return -1;
}

static int
kc_rsa_public_decrypt(int flen,
                      const unsigned char *from,
                      unsigned char *to,
                      RSA *rsa,
                      int padding)
{
    return -1;
}


static int
kc_rsa_private_encrypt(int flen,
                       const unsigned char *from,
                       unsigned char *to,
                       RSA *rsa,
                       int padding)
{
    struct kc_rsa *kc = RSA_get_app_data(rsa);

    CSSM_RETURN cret;
    OSStatus ret;
    const CSSM_ACCESS_CREDENTIALS *creds;
    SecKeyRef privKeyRef = (SecKeyRef)kc->item;
    CSSM_CSP_HANDLE cspHandle;
    const CSSM_KEY *cssmKey;
    CSSM_CC_HANDLE sigHandle = 0;
    CSSM_DATA sig, in;
    int fret = 0;

    if (padding != RSA_PKCS1_PADDING)
        return -1;

    cret = SecKeyGetCSSMKey(privKeyRef, &cssmKey);
    if(cret) abort();

    cret = SecKeyGetCSPHandle(privKeyRef, &cspHandle);
    if(cret) abort();

    ret = SecKeyGetCredentials(privKeyRef, CSSM_ACL_AUTHORIZATION_SIGN,
                               kSecCredentialTypeDefault, &creds);
    if(ret) abort();

    ret = CSSM_CSP_CreateSignatureContext(cspHandle, CSSM_ALGID_RSA,
                                          creds, cssmKey, &sigHandle);
    if(ret) abort();

    in.Data = (uint8 *)from;
    in.Length = flen;

    sig.Data = (uint8 *)to;
    sig.Length = kc->keysize;

    cret = CSSM_SignData(sigHandle, &in, 1, CSSM_ALGID_NONE, &sig);
    if(cret) {
        /* cssmErrorString(cret); */
        fret = -1;
    } else
        fret = sig.Length;

    if(sigHandle)
        CSSM_DeleteContext(sigHandle);

    return fret;
}

static int
kc_rsa_private_decrypt(int flen, const unsigned char *from, unsigned char *to,
                       RSA * rsa, int padding)
{
    struct kc_rsa *kc = RSA_get_app_data(rsa);

    CSSM_RETURN cret;
    OSStatus ret;
    const CSSM_ACCESS_CREDENTIALS *creds;
    SecKeyRef privKeyRef = (SecKeyRef)kc->item;
    CSSM_CSP_HANDLE cspHandle;
    const CSSM_KEY *cssmKey;
    CSSM_CC_HANDLE handle = 0;
    CSSM_DATA out, in, rem;
    int fret = 0;
    CSSM_SIZE outlen = 0;
    char remdata[1024];

    if (padding != RSA_PKCS1_PADDING)
        return -1;

    cret = SecKeyGetCSSMKey(privKeyRef, &cssmKey);
    if(cret) abort();

    cret = SecKeyGetCSPHandle(privKeyRef, &cspHandle);
    if(cret) abort();

    ret = SecKeyGetCredentials(privKeyRef, CSSM_ACL_AUTHORIZATION_DECRYPT,
                               kSecCredentialTypeDefault, &creds);
    if(ret) abort();


    ret = CSSM_CSP_CreateAsymmetricContext (cspHandle,
                                            CSSM_ALGID_RSA,
                                            creds,
                                            cssmKey,
                                            CSSM_PADDING_PKCS1,
                                            &handle);
    if(ret) abort();

    in.Data = (uint8 *)from;
    in.Length = flen;

    out.Data = (uint8 *)to;
    out.Length = kc->keysize;

    rem.Data = (uint8 *)remdata;
    rem.Length = sizeof(remdata);

    cret = CSSM_DecryptData(handle, &in, 1, &out, 1, &outlen, &rem);
    if(cret) {
        /* cssmErrorString(cret); */
        fret = -1;
    } else
        fret = out.Length;

    if(handle)
        CSSM_DeleteContext(handle);

    return fret;
}

static int
kc_rsa_init(RSA *rsa)
{
    return 1;
}

static int
kc_rsa_finish(RSA *rsa)
{
    struct kc_rsa *kc_rsa = RSA_get_app_data(rsa);
    CFRelease(kc_rsa->item);
    memset(kc_rsa, 0, sizeof(*kc_rsa));
    free(kc_rsa);
    return 1;
}

static const RSA_METHOD kc_rsa_pkcs1_method = {
    "hx509 Keychain PKCS#1 RSA",
    kc_rsa_public_encrypt,
    kc_rsa_public_decrypt,
    kc_rsa_private_encrypt,
    kc_rsa_private_decrypt,
    NULL,
    NULL,
    kc_rsa_init,
    kc_rsa_finish,
    0,
    NULL,
    NULL,
    NULL
};

static int
set_private_key(hx509_context context,
                SecKeychainItemRef itemRef,
                hx509_cert cert)
{
    struct kc_rsa *kc;
    hx509_private_key key;
    RSA *rsa;
    int ret;

    ret = hx509_private_key_init(&key, NULL, NULL);
    if (ret)
        return ret;

    kc = calloc(1, sizeof(*kc));
    if (kc == NULL)
        _hx509_abort("out of memory");

    kc->item = itemRef;

    rsa = RSA_new();
    if (rsa == NULL)
        _hx509_abort("out of memory");

    /* Argh, fake modulus since OpenSSL API is on crack */
    {
        SecKeychainAttributeList *attrs = NULL;
        uint32_t size;
        void *data;

        rsa->n = BN_new();
        if (rsa->n == NULL) abort();

        ret = getAttribute(itemRef, kSecKeyKeySizeInBits, &attrs);
        if (ret) abort();

        size = *(uint32_t *)attrs->attr[0].data;
        SecKeychainItemFreeAttributesAndData(attrs, NULL);

        kc->keysize = (size + 7) / 8;

        data = malloc(kc->keysize);
        memset(data, 0xe0, kc->keysize);
        BN_bin2bn(data, kc->keysize, rsa->n);
        free(data);
    }
    rsa->e = NULL;

    RSA_set_method(rsa, &kc_rsa_pkcs1_method);
    ret = RSA_set_app_data(rsa, kc);
    if (ret != 1)
        _hx509_abort("RSA_set_app_data");

    hx509_private_key_assign_rsa(key, rsa);
    _hx509_cert_assign_key(cert, key);

    return 0;
}

/*
 *
 */

struct ks_keychain {
    int anchors;
    SecKeychainRef keychain;
};

static int
keychain_init(hx509_context context,
              hx509_certs certs, void **data, int flags,
              const char *residue, hx509_lock lock)
{
    struct ks_keychain *ctx;

    ctx = calloc(1, sizeof(*ctx));
    if (ctx == NULL) {
        hx509_clear_error_string(context);
        return ENOMEM;
    }

    if (residue) {
        if (strcasecmp(residue, "system-anchors") == 0) {
            ctx->anchors = 1;
        } else if (strncasecmp(residue, "FILE:", 5) == 0) {
            OSStatus ret;

            ret = SecKeychainOpen(residue + 5, &ctx->keychain);
            if (ret != noErr) {
                hx509_set_error_string(context, 0, ENOENT,
                                       "Failed to open %s", residue);
                return ENOENT;
            }
        } else {
            hx509_set_error_string(context, 0, ENOENT,
                                   "Unknown subtype %s", residue);
            return ENOENT;
        }
    }

    *data = ctx;
    return 0;
}

/*
 *
 */

static int
keychain_free(hx509_certs certs, void *data)
{
    struct ks_keychain *ctx = data;
    if (ctx->keychain)
        CFRelease(ctx->keychain);
    memset(ctx, 0, sizeof(*ctx));
    free(ctx);
    return 0;
}

/*
 *
 */

struct iter {
    hx509_certs certs;
    void *cursor;
    SecKeychainSearchRef searchRef;
};

static int
keychain_iter_start(hx509_context context,
                    hx509_certs certs, void *data, void **cursor)
{
    struct ks_keychain *ctx = data;
    struct iter *iter;

    iter = calloc(1, sizeof(*iter));
    if (iter == NULL) {
        hx509_set_error_string(context, 0, ENOMEM, "out of memory");
        return ENOMEM;
    }

    if (ctx->anchors) {
        CFArrayRef anchors;
        int ret;
        int i;

        ret = hx509_certs_init(context, "MEMORY:ks-file-create",
                               0, NULL, &iter->certs);
        if (ret) {
            free(iter);
            return ret;
        }

        ret = SecTrustCopyAnchorCertificates(&anchors);
        if (ret != 0) {
            hx509_certs_free(&iter->certs);
            free(iter);
            hx509_set_error_string(context, 0, ENOMEM,
                                   "Can't get trust anchors from Keychain");
            return ENOMEM;
        }
        for (i = 0; i < CFArrayGetCount(anchors); i++) {
            SecCertificateRef cr;
            hx509_cert cert;
            CSSM_DATA cssm;

            cr = (SecCertificateRef)CFArrayGetValueAtIndex(anchors, i);

            SecCertificateGetData(cr, &cssm);

            ret = hx509_cert_init_data(context, cssm.Data, cssm.Length, &cert);
            if (ret)
                continue;

            ret = hx509_certs_add(context, iter->certs, cert);
            hx509_cert_free(cert);
        }
        CFRelease(anchors);
    }

    if (iter->certs) {
        int ret;
        ret = hx509_certs_start_seq(context, iter->certs, &iter->cursor);
        if (ret) {
            hx509_certs_free(&iter->certs);
            free(iter);
            return ret;
        }
    } else {
        OSStatus ret;

        ret = SecKeychainSearchCreateFromAttributes(ctx->keychain,
                                                    kSecCertificateItemClass,
                                                    NULL,
                                                    &iter->searchRef);
        if (ret) {
            free(iter);
            hx509_set_error_string(context, 0, ret,
                                   "Failed to start search for attributes");
            return ENOMEM;
        }
    }

    *cursor = iter;
    return 0;
}

/*
 *
 */

static int
keychain_iter(hx509_context context,
              hx509_certs certs, void *data, void *cursor, hx509_cert *cert)
{
    SecKeychainAttributeList *attrs = NULL;
    SecKeychainAttributeInfo attrInfo;
    UInt32 attrFormat[1] = { 0 };
    SecKeychainItemRef itemRef;
    SecItemAttr item[1];
    struct iter *iter = cursor;
    OSStatus ret;
    UInt32 len;
    void *ptr = NULL;

    if (iter->certs)
        return hx509_certs_next_cert(context, iter->certs, iter->cursor, cert);

    *cert = NULL;

    ret = SecKeychainSearchCopyNext(iter->searchRef, &itemRef);
    if (ret == errSecItemNotFound)
        return 0;
    else if (ret != 0)
        return EINVAL;

    /*
     * Pick out certificate and matching "keyid"
     */

    item[0] = kSecPublicKeyHashItemAttr;

    attrInfo.count = 1;
    attrInfo.tag = item;
    attrInfo.format = attrFormat;

    ret = SecKeychainItemCopyAttributesAndData(itemRef, &attrInfo, NULL,
                                               &attrs, &len, &ptr);
    if (ret)
        return EINVAL;

    ret = hx509_cert_init_data(context, ptr, len, cert);
    if (ret)
        goto out;

    /*
     * Find related private key if there is one by looking at
     * kSecPublicKeyHashItemAttr == kSecKeyLabel
     */
    {
        SecKeychainSearchRef search;
        SecKeychainAttribute attrKeyid;
        SecKeychainAttributeList attrList;

        attrKeyid.tag = kSecKeyLabel;
        attrKeyid.length = attrs->attr[0].length;
        attrKeyid.data = attrs->attr[0].data;

        attrList.count = 1;
        attrList.attr = &attrKeyid;

        ret = SecKeychainSearchCreateFromAttributes(NULL,
                                                    CSSM_DL_DB_RECORD_PRIVATE_KEY,
                                                    &attrList,
                                                    &search);
        if (ret) {
            ret = 0;
            goto out;
        }

        ret = SecKeychainSearchCopyNext(search, &itemRef);
        CFRelease(search);
        if (ret == errSecItemNotFound) {
            ret = 0;
            goto out;
        } else if (ret) {
            ret = EINVAL;
            goto out;
        }
        set_private_key(context, itemRef, *cert);
    }

out:
    SecKeychainItemFreeAttributesAndData(attrs, ptr);

    return ret;
}

/*
 *
 */

static int
keychain_iter_end(hx509_context context,
                  hx509_certs certs,
                  void *data,
                  void *cursor)
{
    struct iter *iter = cursor;

    if (iter->certs) {
        hx509_certs_end_seq(context, iter->certs, iter->cursor);
        hx509_certs_free(&iter->certs);
    } else {
        CFRelease(iter->searchRef);
    }

    memset(iter, 0, sizeof(*iter));
    free(iter);
    return 0;
}

/*
 *
 */

struct hx509_keyset_ops keyset_keychain = {
    "KEYCHAIN",
    0,
    keychain_init,
    NULL,
    keychain_free,
    NULL,
    NULL,
    keychain_iter_start,
    keychain_iter,
    keychain_iter_end
};

#endif /* HAVE_FRAMEWORK_SECURITY */

/*
 *
 */

void
_hx509_ks_keychain_register(hx509_context context)
{
#ifdef HAVE_FRAMEWORK_SECURITY
    _hx509_ks_register(context, &keyset_keychain);
#endif
}