Hi Geoff, and thanks for your reply!

On 2004-10-07 06:09, Geoff Thorpe wrote:
> Hi Peter,
> 
> On October 5, 2004 12:38 pm, Peter 'Luna' Runestig wrote:
>> I've managed to hack together a custom RSA_METHOD, based on Microsoft
>> CryptoAPI (on Windows XP in my test case), to use a smart card for
>> authentication. And it actually works, as far as I have managed to test
>> it anyway. But I'm a little puzzled: When I'm running it, the only
>> (crypto-related) RSA_METHOD callback that gets called, is
>> rsa_priv_enc(), once. Even with a negotiated crypto like AES256-SHA,
>> that, AFAICS, uses RSA for key exchange. Is this as expected, or is
>> there other test cases that might trigger other callbacks (that needs
>> to be implemented then)?
> 
> This makes sense if you're using SSL/TLS with "simple" cipher-suites and 
> configs (ie. no client authentication, no ephemeral key-exchange, etc.) 
> Essentially, the server needs to be able to sign (for RSA, this is 
> priv_enc) some data using the private key corresponding to the public key 
> that's already been sent to the client via the certificate.

I'm sorry, I was a bit unclear in my original post. I'm using this on a
SSL/TLS *client*, to connect to a (telnet) server. So I am using client
authentication. And I have tried numerous different cipher setups, but I
can only trigger rsa_priv_enc() to be called. Do you have any tip for
me, how I might trigger e.g. rsa_priv_dec() to be called?

> Glad you got this working with CryptoAPI BTW ;-)

Yes :-) I post the code here, in case someone is interested/have
feedback/finds errors. It's all wrapped in a function:

int SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop);

The 'cert_prop' string parameter specifies how to search for a desired
certificate in the current logged on user's certificate store. You can
search for a substring in the certificate's subject, or the certificate's
thumbprint, like this:

SSL_CTX_use_CryptoAPI_certificate(ssl_ctx, "SUBJ:Peter Runestig");
or
SSL_CTX_use_CryptoAPI_certificate(ssl_ctx, "THUMB:f6 49 24 41 01 b4 ...");

The thumbprint hex string can easily be copy-and-pasted from the Windows
certificate store GUI.

-----8<----------8<----------8<----------8<----------8<----------8<-----
/*
 * Copyright (c) 2004 Peter 'Luna' Runestig <[EMAIL PROTECTED]>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modifi-
 * cation, are permitted provided that the following conditions are met:
 *
 *   o  Redistributions of source code must retain the above copyright notice,
 *      this list of conditions and the following disclaimer.
 *
 *   o  Redistributions in binary form must reproduce the above copyright no-
 *      tice, this list of conditions and the following disclaimer in the do-
 *      cumentation and/or other materials provided with the distribution.
 *
 *   o  The names of the contributors may not be used to endorse or promote
 *      products derived from this software without specific prior written
 *      permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 REGENTS OR CONTRIBUTORS BE LI-
 * ABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUEN-
 * TIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEV-
 * ER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABI-
 * LITY, 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 <windows.h>
#include <wincrypt.h>
#include <stdio.h>
#include <openssl\ssl.h>
#include <openssl\err.h>

/* Size of an SSL signature: MD5+SHA1 */
#define SSL_SIG_LENGTH  36

/* try to funnel any Windows/CryptoAPI error messages to OpenSSL ERR_... */
#define ERR_LIB_CRYPTOAPI (ERR_LIB_USER + 69)   /* 69 is just a number... */
#define CRYPTOAPIerr(f)   err_put_ms_error(GetLastError(), (f), __FILE__, __LINE__)
#define CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE                  100
#define CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE          101
#define CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY   102
#define CRYPTOAPI_F_CRYPT_CREATE_HASH                       103
#define CRYPTOAPI_F_CRYPT_GET_HASH_PARAM                    104
#define CRYPTOAPI_F_CRYPT_SET_HASH_PARAM                    105
#define CRYPTOAPI_F_CRYPT_SIGN_HASH                         106

static ERR_STRING_DATA CRYPTOAPI_str_functs[] = {
    { ERR_PACK(ERR_LIB_CRYPTOAPI, 0, 0),                                    "microsoft 
cryptoapi"},
    { ERR_PACK(0, CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE, 0),                   
"CertOpenSystemStore" },
    { ERR_PACK(0, CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE, 0),           
"CertFindCertificateInStore" },
    { ERR_PACK(0, CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY, 0),    
"CryptAcquireCertificatePrivateKey" },
    { ERR_PACK(0, CRYPTOAPI_F_CRYPT_CREATE_HASH, 0),                        
"CryptCreateHash" },
    { ERR_PACK(0, CRYPTOAPI_F_CRYPT_GET_HASH_PARAM, 0),                     
"CryptGetHashParam" },
    { ERR_PACK(0, CRYPTOAPI_F_CRYPT_SET_HASH_PARAM, 0),                     
"CryptSetHashParam" },
    { ERR_PACK(0, CRYPTOAPI_F_CRYPT_SIGN_HASH, 0),                          
"CryptSignHash" },
    { 0, NULL }
};

typedef struct _CAPI_DATA {
    HCERTSTORE cert_store;
    const CERT_CONTEXT *cert_context;
    HCRYPTPROV crypt_prov;
    DWORD key_spec;
    BOOL free_crypt_prov;
    HCRYPTKEY priv_key;
} CAPI_DATA;

static char *ms_error_text(DWORD ms_err)
{
    LPVOID lpMsgBuf = NULL;
    char *rv = NULL;

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM |
        FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, ms_err,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
        (LPTSTR) &lpMsgBuf, 0, NULL);
    if (lpMsgBuf) {
        char *p;
        rv = strdup(lpMsgBuf);
        LocalFree(lpMsgBuf);
        /* trim to the left */
        if (rv)
            for (p = rv + strlen(rv) - 1; p >= rv; p--)
                if (isspace(*p))
                    *p = '\0';
                else
                    break;
    }
    return rv;
}

static void err_put_ms_error(DWORD ms_err, int func, const char *file, int line)
{
    static int init = 0;
#   define ERR_MAP_SZ 16
    static struct {
        int err;
        DWORD ms_err;       /* I don't think we get more than 16 *different* errors */
    } err_map[ERR_MAP_SZ];  /* in here, before we give up the whole thing...        */
    int i;

    if (ms_err == 0)
        /* 0 is not an error */
        return;
    if (!init) {
        ERR_load_strings(ERR_LIB_CRYPTOAPI, CRYPTOAPI_str_functs);
        memset(&err_map, 0, sizeof(err_map));
        init++;
    }
    /* since MS error codes are 32 bit, and the ones in the ERR_... system is
     * only 12, we must have a mapping table between them.  */
    for (i = 0; i < ERR_MAP_SZ; i++) {
        if (err_map[i].ms_err == ms_err) {
            ERR_PUT_error(ERR_LIB_CRYPTOAPI, func, err_map[i].err, file, line);
            break;
        } else if (err_map[i].ms_err == 0 ) {
            /* end of table, add new entry */
            ERR_STRING_DATA *esd = calloc(2, sizeof(*esd));
            if (esd == NULL)
                break;
            err_map[i].ms_err = ms_err;
            err_map[i].err = esd->error = i + 100;
            esd->string = ms_error_text(ms_err);
            ERR_load_strings(ERR_LIB_CRYPTOAPI, esd);
            ERR_PUT_error(ERR_LIB_CRYPTOAPI, func, err_map[i].err, file, line);
            break;
        }
    }
}

static int ANSI2Unicode(LPWSTR OutString, char *InString, int OutStringSize)
{
   return MultiByteToWideChar(CP_ACP, 0, InString, -1, OutString, OutStringSize);
}

/* encrypt */
static int rsa_pub_enc(int flen, const unsigned char *from, unsigned char *to, RSA 
*rsa, int padding)
{

    return 0;
}

/* verify arbitrary data */
static int rsa_pub_dec(int flen, const unsigned char *from, unsigned char *to, RSA 
*rsa, int padding)
{

    return 0;
}

/* sign arbitrary data */
static int rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA 
*rsa, int padding)
{
    CAPI_DATA *cd = (CAPI_DATA *) rsa->meth->app_data;
    HCRYPTHASH hash;
    DWORD hash_size, len, i;
    unsigned char *buf;

    if (cd == NULL) {
        RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, ERR_R_PASSED_NULL_PARAMETER);
        return 0;
    }
    if (padding != RSA_PKCS1_PADDING) {
        /* AFAICS, CryptSignHash() *always* uses PKCS1 padding. */
        RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
        return 0;
    }
    /* Unfortunately, there is no "CryptSign()" function in CryptoAPI, that would
     * be way too straightforward for M$, I guess... So we have to do it this
     * tricky way instead, by creating a "Hash", and load the already-made hash
     * from 'from' into it.  */
    /* For now, we only support NID_md5_sha1 */
    if (flen != SSL_SIG_LENGTH) {
        RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, RSA_R_INVALID_MESSAGE_LENGTH);
        return 0;
    }
    if (!CryptCreateHash(cd->crypt_prov, CALG_SSL3_SHAMD5, 0, 0, &hash)) {
        CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_CREATE_HASH);
        return 0;
    }
    len = sizeof(hash_size);
    if (!CryptGetHashParam(hash, HP_HASHSIZE, (BYTE *) &hash_size, &len, 0)) {
        CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_GET_HASH_PARAM);
        CryptDestroyHash(hash);
        return 0;
    }
    if ((int) hash_size != flen) {
        RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, RSA_R_INVALID_MESSAGE_LENGTH);
        CryptDestroyHash(hash);
        return 0;
    }
    if (!CryptSetHashParam(hash, HP_HASHVAL, from, 0)) {
        CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_SET_HASH_PARAM);
        CryptDestroyHash(hash);
        return 0;
    }

    len = RSA_size(rsa);
    buf = malloc(len);
    if (buf == NULL) {
        RSAerr(RSA_F_RSA_EAY_PRIVATE_ENCRYPT, ERR_R_MALLOC_FAILURE);
        CryptDestroyHash(hash);
        return 0;
    }
    if (!CryptSignHash(hash, cd->key_spec, NULL, 0, buf, &len)) {
        CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_SIGN_HASH);
        CryptDestroyHash(hash);
        free(buf);
        return 0;
    }
    /* and now, we have to reverse the byte-order in the result from 
CryptSignHash()... */
    for (i = 0; i < len; i++)
        to[i] = buf[len - i - 1];
    free(buf);

    CryptDestroyHash(hash);
    return len;
}

/* decrypt */
static int rsa_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA 
*rsa, int padding)
{

    return 0;
}

/* called at RSA_new */
static int init(RSA *rsa)
{

    return 0;
}

/* called at RSA_free */
static int finish(RSA *rsa)
{
    CAPI_DATA *cd = (CAPI_DATA *) rsa->meth->app_data;

    if (cd == NULL)
        return 0;
    if (cd->priv_key)
        CryptDestroyKey(cd->priv_key);
    if (cd->crypt_prov && cd->free_crypt_prov)
        CryptReleaseContext(cd->crypt_prov, 0);
    if (cd->cert_context)
        CertFreeCertificateContext(cd->cert_context);
    if (cd->cert_store)
        CertCloseStore(cd->cert_store, 0);
    free(rsa->meth->app_data);
    free((char *) rsa->meth);
    rsa->meth = NULL;
    return 1;
}

static const CERT_CONTEXT *find_certificate_in_store(const char *cert_prop, HCERTSTORE 
cert_store)
{
    /* Find, and use, the desired certificate from the store. The
     * 'cert_prop' certificate search string can look like this:
     * SUBJ:<certificate substring to match>
     * THUMB:<certificate thumbprint hex value>, e.g.
     *     THUMB:f6 49 24 41 01 b4 fb 44 0c ce f4 36 ae d0 c4 c9 df 7a b6 28
     */
    const CERT_CONTEXT *rv = NULL;

    if (!strncmp(cert_prop, "SUBJ:", 5)) {
        unsigned short wbuf[255];

        /* skip the tag */
        cert_prop += 5;
        ANSI2Unicode(wbuf, (char *) cert_prop, sizeof(wbuf));
        rv = CertFindCertificateInStore(cert_store, X509_ASN_ENCODING | 
PKCS_7_ASN_ENCODING,
                0, CERT_FIND_SUBJECT_STR, wbuf, NULL);

    } else if (!strncmp(cert_prop, "THUMB:", 6)) {
        unsigned char hash[255];
        char *p, *p_base;
        int i, x;
        CRYPT_HASH_BLOB blob;

        /* skip the tag */
        cert_prop += 6;
        /* make a copy of 'cert_prop', since we tamper with it here. */
        p_base = p = strdup(cert_prop);
        if (p == NULL) {
            /* TODO: Set some OpenSSL error here! */
            return NULL;
        }
        /* convert to upper case */
        for (; *p; p++)
            *p = toupper(*p);
        p = p_base;
        for (i = 0; *p && i < sizeof(hash); i++) {
            if (*p >= '0' && *p <= '9')
                x = (*p - '0') << 4;
            else if (*p >= 'A' && *p <= 'F')
                x = (*p - 'A' + 10) << 4;
            if (!*++p)  /* unexpected end of string */
                break;
            if (*p >= '0' && *p <= '9')
                x += *p - '0';
            else if (*p >= 'A' && *p <= 'F')
                x += *p - 'A' + 10;
            hash[i] = x;
            /* skip any space(s) between hex numbers */
            for (p++; *p && *p == ' '; p++);
        }
        free(p_base);
        blob.cbData = i;
        blob.pbData = (unsigned char *) &hash;
        rv = CertFindCertificateInStore(cert_store, X509_ASN_ENCODING | 
PKCS_7_ASN_ENCODING,
                0, CERT_FIND_HASH, &blob, NULL);

    }

    return rv;
}

int SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop)
{
    X509 *cert = NULL;
    RSA *rsa = NULL, *pub_rsa;
    CAPI_DATA *cd = calloc(1, sizeof(*cd));
    RSA_METHOD *my_rsa_method = calloc(1, sizeof(*my_rsa_method));

    if (cd == NULL || my_rsa_method == NULL) {
        SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_MALLOC_FAILURE);
        goto err;
    }
    cd->cert_store = CertOpenSystemStore(0L, "MY");
    if (cd->cert_store == NULL) {
        CRYPTOAPIerr(CRYPTOAPI_F_CERT_OPEN_SYSTEM_STORE);
        goto err;
    }
    cd->cert_context = find_certificate_in_store(cert_prop, cd->cert_store);
    if (cd->cert_context == NULL) {
        CRYPTOAPIerr(CRYPTOAPI_F_CERT_FIND_CERTIFICATE_IN_STORE);
        goto err;
    }

    /* cert_context->pbCertEncoded is the cert X509 DER encoded. */
    cert = d2i_X509(NULL, (unsigned char **) &cd->cert_context->pbCertEncoded,
                    cd->cert_context->cbCertEncoded);
    if (cert == NULL) {
        SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_ASN1_LIB);
        goto err;
    }

    /* set up stuff to use the private key */
    if (!CryptAcquireCertificatePrivateKey(cd->cert_context, 
CRYPT_ACQUIRE_COMPARE_KEY_FLAG,
            NULL, &cd->crypt_prov, &cd->key_spec, &cd->free_crypt_prov)) {
        /* if we don't have a smart card reader here, and we try to access a
         * smart card certificate, we get:
         * "Error 1223: The operation was canceled by the user." */
        CRYPTOAPIerr(CRYPTOAPI_F_CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY);
        goto err;
    }
    /* here we don't need to do CryptGetUserKey() or anything; all necessary key
     * info is in cd->cert_context, and then, in cd->crypt_prov.  */

    my_rsa_method->name = "CryptoAPI RSA Method";
    /* my_rsa_method->rsa_pub_enc = rsa_pub_enc; */
    /* my_rsa_method->rsa_pub_dec = rsa_pub_dec; */
    my_rsa_method->rsa_priv_enc = rsa_priv_enc;
    /* my_rsa_method->rsa_priv_dec = rsa_priv_dec; */
    /* my_rsa_method->init = init; */
    my_rsa_method->finish = finish;
    my_rsa_method->flags = RSA_METHOD_FLAG_NO_CHECK;
    my_rsa_method->app_data = (char *) cd;

    rsa = RSA_new();
    if (rsa == NULL) {
        SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_MALLOC_FAILURE);
        goto err;
    }

    /* cert->cert_info->key->pkey is NULL until we call SSL_CTX_use_certificate(),
     * so we do it here then...  */
    if (!SSL_CTX_use_certificate(ssl_ctx, cert))
        goto err;
    /* the public key */
    pub_rsa = cert->cert_info->key->pkey->pkey.rsa;
    /* SSL_CTX_use_certificate() increased the reference count in 'cert', so
     * we decrease it here with X509_free(), or it will never be cleaned up. */
    X509_free(cert);
    cert = NULL;

    /* I'm not sure about what we have to fill in in the RSA, trying out stuff... */
    /* rsa->n indicates the key size */
    rsa->n = BN_dup(pub_rsa->n);
    rsa->flags |= RSA_FLAG_EXT_PKEY;
    if (!RSA_set_method(rsa, my_rsa_method))
        goto err;

    if (!SSL_CTX_use_RSAPrivateKey(ssl_ctx, rsa))
        goto err;
    /* SSL_CTX_use_RSAPrivateKey() increased the reference count in 'rsa', so
     * we decrease it here with RSA_free(), or it will never be cleaned up. */
    RSA_free(rsa);
    return 1;

err:
    if (cert)
        X509_free(cert);
    if (rsa)
        RSA_free(rsa);
    else {
        if (my_rsa_method)
            free(my_rsa_method);
        if (cd) {
            if (cd->priv_key)
                CryptDestroyKey(cd->priv_key);
            if (cd->free_crypt_prov && cd->crypt_prov)
                CryptReleaseContext(cd->crypt_prov, 0);
            if (cd->cert_context)
                CertFreeCertificateContext(cd->cert_context);
            if (cd->cert_store)
                CertCloseStore(cd->cert_store, 0);
            free(cd);
        }
    }
    return 0;
}
-----8<----------8<----------8<----------8<----------8<----------8<-----

-- 
Peter 'Luna' Runestig (fd. Altberg), Sweden <[EMAIL PROTECTED]>
PGP Key ID: 0xD07BBE13
Fingerprint: 7B5C 1F48 2997 C061 DE4B  42EA CB99 A35C D07B BE13
AOL Instant Messenger Screen name: PRunestig
Yahoo! Messenger profile name: altberg

______________________________________________________________________
OpenSSL Project                                 http://www.openssl.org
User Support Mailing List                    [EMAIL PROTECTED]
Automated List Manager                           [EMAIL PROTECTED]

Reply via email to