summaryrefslogtreecommitdiff
path: root/ckcapi-rsa.c
diff options
context:
space:
mode:
Diffstat (limited to 'ckcapi-rsa.c')
-rw-r--r--ckcapi-rsa.c405
1 files changed, 405 insertions, 0 deletions
diff --git a/ckcapi-rsa.c b/ckcapi-rsa.c
new file mode 100644
index 0000000..0d4e75d
--- /dev/null
+++ b/ckcapi-rsa.c
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2008 Stef Walter
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "ckcapi.h"
+
+#include "ckcapi-der.h"
+#include "ckcapi-key.h"
+#include "ckcapi-object.h"
+
+/*
+ * Portions derived from NSS source files:
+ * lib/ckfw/capi/crsa.c
+ */
+
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Netscape security libraries.
+ *
+ * The Initial Developer of the Original Code is
+ * Red Hat, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Bob Relyea (rrelyea@redhat.com)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#define SSL3_SHAMD5_HASH_SIZE 36 /* LEN_MD5 (16) + LEN_SHA1 (20) */
+
+/*
+ * PKCS #11 sign for RSA expects to take a fully DER-encoded hash value,
+ * which includes the hash OID. CAPI expects to take a Hash Context. While
+ * CAPI does have the capability of setting a raw hash value, it does not
+ * have the ability to sign an arbitrary value. This function tries to
+ * reduce the passed in data into something that CAPI could actually sign.
+ */
+static CK_BYTE_PTR
+parse_rsa_pkcs_der_hash(CK_BYTE_PTR input, CK_ULONG n_input,
+ ALG_ID* algorithm, CK_ULONG_PTR n_hash)
+{
+ BYTE* algid;
+ BYTE* oid;
+ BYTE* hash_data;
+ BYTE* oid_str;
+ DWORD n_oid;
+ DWORD n_algid;
+
+ /*
+ * there are 2 types of hashes NSS typically tries to sign, regular
+ * RSA signature format (with encoded DER_OIDS), and SSL3 Signed hashes.
+ * CAPI knows not to add any oids to SSL3_Signed hashes, so if we have any
+ * random hash that is exactly the same size as an SSL3 hash, then we can
+ * just pass the data through. CAPI has know way of knowing if the value
+ * is really a combined hash or some other arbitrary data, so it's safe to
+ * handle this case first.
+ */
+ if(SSL3_SHAMD5_HASH_SIZE == n_input)
+ {
+ *n_hash = n_input;
+ *algorithm = CALG_SSL3_SHAMD5;
+ return input;
+ }
+
+ /* make sure we have a sequence tag */
+ if((CKCAPI_DER_SEQUENCE | CKCAPI_DER_CONSTRUCTED) != *input)
+ return NULL;
+
+ /*
+ * parse the input block to get 1) the hash oid, and 2) the raw hash value.
+ * unfortunatly CAPI doesn't have a builtin function to do this work, so
+ * we go ahead and do it by hand here.
+ *
+ * format is:
+ * SEQUENCE {
+ * SECQUENCE { // algid
+ * OID {} // oid
+ * ANY {} // optional params
+ * }
+ * OCTECT {} // hash
+ */
+
+ /* unwrap */
+ algid = ckcapi_der_unwrap(input, n_input, &n_algid, NULL);
+ if(!algid)
+ return NULL;
+
+ /* make sure there is not extra data at the end */
+ if(algid + n_algid != input + n_input)
+ return NULL;
+
+ /* wasn't an algid */
+ if((CKCAPI_DER_SEQUENCE | CKCAPI_DER_CONSTRUCTED) != *algid)
+ return NULL;
+
+ oid = ckcapi_der_unwrap(algid, n_algid, &n_oid, &hash_data);
+ if(!oid || !hash_data)
+ return NULL;
+
+ if(algorithm)
+ {
+ /*
+ * get the real oid as a string. Again, Microsoft does not
+ * export anything that does this for us
+ */
+ oid_str = ckcapi_der_read_oid(oid, n_oid);
+ if(!oid_str)
+ return NULL;
+
+ /* look up the hash alg from the oid (fortunately CAPI does to this) */
+ *algorithm = CertOIDToAlgId(oid_str);
+ free(oid_str);
+ }
+
+ /* wasn't a hash? */
+ if(CKCAPI_DER_OCTET_STRING != *hash_data)
+ return NULL;
+
+ /* get the real raw hash */
+ return ckcapi_der_unwrap(hash_data, n_algid - (hash_data - algid),
+ n_hash, NULL);
+}
+
+CK_RV
+ckcapi_rsa_pkcs_sign_init(CkCapiObjectData *keydata, void** operation)
+{
+ CRYPT_KEY_PROV_INFO* prov_info;
+
+ ASSERT(keydata);
+ ASSERT(operation);
+ ASSERT(!*operation);
+
+ prov_info = ckcapi_key_object_data_get_prov_info(keydata);
+ if(prov_info->dwProvType == PROV_RSA_FULL)
+ return CKR_KEY_TYPE_INCONSISTENT;
+
+ *operation = keydata;
+ return CKR_OK;
+}
+
+CK_RV
+ckcapi_rsa_pkcs_sign_perform (CK_BYTE_PTR data, CK_ULONG n_data,
+ CK_BYTE_PTR signature, CK_ULONG_PTR n_signature,
+ void** operation)
+{
+ CRYPT_KEY_PROV_INFO* prov_info;
+ CkCapiObjectData* keydata;
+ ALG_ID algorithm;
+ BYTE* hash_data;
+ DWORD n_hash_data;
+ BOOL capifail;
+ DWORD len, check;
+ DWORD bits;
+ CK_RV ret;
+
+ HCRYPTPROV prov = 0;
+ HCRYPTHASH hash = 0;
+
+
+ ASSERT(operation);
+ ASSERT(*operation);
+
+ if(!data || !n_data)
+ return CKR_ARGUMENTS_BAD;
+
+ keydata = (CkCapiObjectData*)*operation;
+
+ prov_info = ckcapi_key_object_data_get_prov_info(keydata);
+ ASSERT(prov_info);
+
+ /* Calculate the number of bits */
+ bits = ckcapi_key_object_data_get_bits (keydata);
+ if(!bits)
+ return CKR_GENERAL_ERROR;
+
+ /* Want to know the length */
+ if(!signature)
+ {
+ *n_signature = bits / 8;
+ return CKR_OK;
+ }
+
+ /* TODO: Support arbitrary input on Vista */
+
+ /*
+ * PKCS #11 sign for RSA expects to take a fully DER-encoded hash value,
+ * which includes the hash OID. CAPI expects to take a Hash Context. While
+ * CAPI does have the capability of setting a raw hash value, it does not
+ * have the ability to sign an arbitrary value. This function tries to
+ * reduce the passed in data into something that CAPI could actually sign.
+ */
+ hash_data = parse_rsa_pkcs_der_hash(data, n_data, &algorithm, &n_hash_data);
+ if(!hash_data)
+ return CKR_DATA_INVALID;
+
+ capifail = TRUE;
+ if(CryptAcquireContextW(&prov, prov_info->pwszContainerName, prov_info->pwszProvName,
+ prov_info->dwProvType, 0))
+ {
+ if(CryptCreateHash(prov, algorithm, 0, 0, &hash))
+ {
+ /* make sure the hash lens match before we set it */
+ len = sizeof(DWORD);
+ if(CryptGetHashParam(hash, HP_HASHSIZE, (BYTE*)&check, &len, 0))
+ {
+ if(check != n_hash_data)
+ {
+ capifail = FALSE;
+ ret = CKR_DATA_INVALID;
+ }
+
+ /*
+ * we have an explicit hash, set it, note that the length is
+ * implicit by the hashAlg used in create
+ */
+ if(CryptSetHashParam(hash, HP_HASHVAL, hash_data, 0))
+ {
+ /* OK, we have the data in a hash structure, sign it! */
+ if(CryptSignHash(hash, prov_info->dwKeySpec,
+ NULL, 0, signature, n_signature))
+ {
+ /*
+ * OK, Microsoft likes to do things completely
+ * differently than anyone else. We need to reverse
+ * the data we recieved here
+ */
+ if(signature)
+ ckcapi_reverse_memory(signature, *n_signature);
+
+ capifail = FALSE;
+ ret = CKR_OK;
+ }
+ }
+ }
+ }
+ }
+
+ if(capifail)
+ ret = ckcapi_winerr_to_ckr(GetLastError());
+
+ if(hash)
+ CryptDestroyHash(hash);
+ if(prov)
+ CryptReleaseContext(prov, 0);
+
+ return ret;
+}
+
+void
+ckcapi_rsa_pkcs_sign_cleanup (void* operation)
+{
+ /* Nothing to do */
+}
+
+
+CK_RV
+ckcapi_rsa_pkcs_decrypt_init(CkCapiObjectData* keydata, void** operation)
+{
+ CRYPT_KEY_PROV_INFO* prov_info;
+
+ ASSERT(keydata);
+ ASSERT(operation);
+ ASSERT(!*operation);
+
+ prov_info = ckcapi_key_object_data_get_prov_info(keydata);
+ if(prov_info->dwProvType == PROV_RSA_FULL)
+ return CKR_KEY_TYPE_INCONSISTENT;
+
+ *operation = keydata;
+ return CKR_OK;
+}
+
+CK_RV
+ckcapi_rsa_pkcs_decrypt_perform(CK_BYTE_PTR encdata, CK_ULONG n_encdata,
+ CK_BYTE_PTR result, CK_ULONG_PTR n_result,
+ void** operation)
+{
+ CRYPT_KEY_PROV_INFO* prov_info;
+ CkCapiObjectData* keydata;
+ BOOL capifail;
+ DWORD bits, error;
+ CK_RV ret;
+
+ HCRYPTPROV prov = 0;
+ HCRYPTKEY key = 0;
+ void* buffer = NULL;
+
+ ASSERT(operation);
+ ASSERT(*operation);
+ ASSERT(encdata);
+ ASSERT(n_encdata);
+
+ keydata = (CkCapiObjectData*)*operation;
+
+ prov_info = ckcapi_key_object_data_get_prov_info(keydata);
+ ASSERT(prov_info);
+
+ /* Calculate the number of bits */
+ bits = ckcapi_key_object_data_get_bits (keydata);
+ if(!bits)
+ return CKR_GENERAL_ERROR;
+
+ /* Want to know the length */
+ if(!result)
+ {
+ *n_result = bits / 8;
+ return CKR_OK;
+ }
+
+ /*
+ * Copy the input, since CAPI operates in place, and
+ * we must also reverse it properly.
+ */
+ buffer = malloc(n_encdata);
+ if(!buffer)
+ return CKR_HOST_MEMORY;
+
+ memcpy(buffer, encdata, n_encdata);
+ ckcapi_reverse_memory(buffer, n_encdata);
+
+ capifail = TRUE;
+ if(CryptAcquireContextW(&prov, prov_info->pwszContainerName, prov_info->pwszProvName,
+ prov_info->dwProvType, 0))
+ {
+ if(CryptGetUserKey(prov, prov_info->dwKeySpec, &key))
+ {
+ *n_result = n_encdata;
+ if(CryptDecrypt(key, 0, TRUE, 0, buffer, n_result))
+ {
+ capifail = FALSE;
+ ret = CKR_OK;
+ }
+ }
+ }
+
+ if(capifail)
+ {
+ error = GetLastError();
+ switch(error)
+ {
+ case NTE_BAD_DATA:
+ ret = CKR_ENCRYPTED_DATA_INVALID;
+ default:
+ ret = ckcapi_winerr_to_ckr(error);
+ };
+ }
+
+ /* Copy the memory out to the result buffer */
+ if(ret == CKR_OK)
+ ret = ckcapi_return_data_raw(result, n_result, buffer, *n_result);
+
+ if(key)
+ CryptDestroyKey(key);
+ if(prov)
+ CryptReleaseContext(prov, 0);
+ if(buffer)
+ free(buffer);
+
+ return ret;
+}
+
+void
+ckcapi_rsa_pkcs_decrypt_cleanup(void* operation)
+{
+ /* Nothing to do */
+}