diff options
Diffstat (limited to 'ckcapi-rsa.c')
-rw-r--r-- | ckcapi-rsa.c | 405 |
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 */ +} |