From ef65c988c784c493dbf71aa4e717dcf84c435295 Mon Sep 17 00:00:00 2001 From: ilammy Date: Wed, 20 Feb 2019 12:25:01 +0200 Subject: [PATCH 01/21] Introduce new Secure Message API The new API has more obvious naming and should be harder to misuse, with encrypt/decrypt and sign/verify API clearly named and separated. A common mistake with the old API was for users to accidentally using sign/verify API instead of encryption by not providing a private key. New API features more strict checks and prevents this kind of mistakes. Currently the new API is implemented using the old functions (as they are intended to be used). We'll switch the implementations in the next commit and then deprecate the old API. --- src/themis/secure_message.c | 68 +++++ src/themis/secure_message.h | 92 +++++++ tests/themis/themis_seccure_message.c | 348 +++++++++++++++++++++++++- 3 files changed, 501 insertions(+), 7 deletions(-) diff --git a/src/themis/secure_message.c b/src/themis/secure_message.c index d478e4cf2..bd2530dd7 100644 --- a/src/themis/secure_message.c +++ b/src/themis/secure_message.c @@ -77,6 +77,74 @@ themis_status_t themis_gen_ec_key_pair(uint8_t* private_key, return themis_gen_key_pair(SOTER_SIGN_ecdsa_none_pkcs8, private_key, private_key_length, public_key, public_key_length); } +themis_status_t themis_secure_message_encrypt(const uint8_t* private_key, + const size_t private_key_length, + const uint8_t* public_key, + const size_t public_key_length, + const uint8_t* message, + const size_t message_length, + uint8_t* encrypted_message, + size_t* encrypted_message_length) +{ + THEMIS_CHECK_PARAM(private_key!=NULL); + THEMIS_CHECK_PARAM(private_key_length!=0); + THEMIS_CHECK_PARAM(public_key!=NULL); + THEMIS_CHECK_PARAM(public_key_length!=0); + THEMIS_CHECK_PARAM(message!=NULL); + THEMIS_CHECK_PARAM(message_length!=0); + THEMIS_CHECK_PARAM(encrypted_message_length!=NULL); + return themis_secure_message_wrap(private_key, private_key_length, public_key, public_key_length, message, message_length, encrypted_message, encrypted_message_length); +} + +themis_status_t themis_secure_message_decrypt(const uint8_t* private_key, + const size_t private_key_length, + const uint8_t* public_key, + const size_t public_key_length, + const uint8_t* encrypted_message, + const size_t encrypted_message_length, + uint8_t* message, + size_t* message_length) +{ + THEMIS_CHECK_PARAM(private_key!=NULL); + THEMIS_CHECK_PARAM(private_key_length!=0); + THEMIS_CHECK_PARAM(public_key!=NULL); + THEMIS_CHECK_PARAM(public_key_length!=0); + THEMIS_CHECK_PARAM(encrypted_message!=NULL); + THEMIS_CHECK_PARAM(encrypted_message_length!=0); + THEMIS_CHECK_PARAM(message_length!=NULL); + return themis_secure_message_unwrap(private_key, private_key_length, public_key, public_key_length, encrypted_message, encrypted_message_length, message, message_length); +} + +themis_status_t themis_secure_message_sign(const uint8_t* private_key, + const size_t private_key_length, + const uint8_t* message, + const size_t message_length, + uint8_t* signed_message, + size_t* signed_message_length) +{ + THEMIS_CHECK_PARAM(private_key!=NULL); + THEMIS_CHECK_PARAM(private_key_length!=0); + THEMIS_CHECK_PARAM(message!=NULL); + THEMIS_CHECK_PARAM(message_length!=0); + THEMIS_CHECK_PARAM(signed_message_length!=NULL); + return themis_secure_message_wrap(private_key, private_key_length, NULL, 0, message, message_length, signed_message, signed_message_length); +} + +themis_status_t themis_secure_message_verify(const uint8_t* public_key, + const size_t public_key_length, + const uint8_t* signed_message, + const size_t signed_message_length, + uint8_t* message, + size_t* message_length) +{ + THEMIS_CHECK_PARAM(public_key!=NULL); + THEMIS_CHECK_PARAM(public_key_length!=0); + THEMIS_CHECK_PARAM(signed_message!=NULL); + THEMIS_CHECK_PARAM(signed_message_length!=0); + THEMIS_CHECK_PARAM(message_length!=NULL); + return themis_secure_message_unwrap(NULL, 0, public_key, public_key_length, signed_message, signed_message_length, message, message_length); +} + themis_status_t themis_secure_message_wrap(const uint8_t* private_key, const size_t private_key_length, const uint8_t* public_key, diff --git a/src/themis/secure_message.h b/src/themis/secure_message.h index 4cb8328e1..66a13007c 100644 --- a/src/themis/secure_message.h +++ b/src/themis/secure_message.h @@ -64,6 +64,98 @@ themis_status_t themis_gen_ec_key_pair(uint8_t* private_key, uint8_t* public_key, size_t* public_key_length); +/** + * @brief encrypt message to secure message + * @param [in] private_key private key + * @param [in] private_key_length length of private_key + * @param [in] public_key peer public key + * @param [in] public_key_length length of public_key + * @param [in] message message to encrypt + * @param [in] message_length length of message + * @param [out] encrypted_message buffer for encrypted message. + * May be set to NULL to determine expected length of encrypted message + * @param [in, out] encrypted_message_length length of encrypted_message + * @return THEMIS_SUCCESS on success or an error code on failure + * @note If encrypted_message is NULL or encrypted_message_length is not enough to store the encrypted message + * then THEMIS_BUFFER_TOO_SMALL will be returned and encrypted_message_length will contain + * the length of the buffer needed to store the encrypted message. + */ +themis_status_t themis_secure_message_encrypt(const uint8_t* private_key, + const size_t private_key_length, + const uint8_t* public_key, + const size_t public_key_length, + const uint8_t* message, + const size_t message_length, + uint8_t* encrypted_message, + size_t* encrypted_message_length); + +/** + * @brief decrypt secure message to plaintext message + * @param [in] private_key private key + * @param [in] private_key_length length of private_key + * @param [in] public_key peer public key + * @param [in] public_key_length length of public_key + * @param [in] encrypted_message encrypted message to decrypt + * @param [in] encrypted_message_length length of encrypted_message + * @param [out] message buffer for plaintext message. + * May be set to NULL to determine expected length of plaintext message + * @param [in, out] message_length length of message + * @return THEMIS_SUCCESS on success or an error code on failure + * @note If message is NULL or message_length is not enough to store the plaintext message + * then THEMIS_BUFFER_TOO_SMALL will be returned and message_length will contain + * the length of the buffer needed to store the encrypted message. + */ +themis_status_t themis_secure_message_decrypt(const uint8_t* private_key, + const size_t private_key_length, + const uint8_t* public_key, + const size_t public_key_length, + const uint8_t* encrypted_message, + const size_t encrypted_message_length, + uint8_t* message, + size_t* message_length); + +/** + * @brief securely sign a message + * @param [in] private_key private key + * @param [in] private_key_length length of private_key + * @param [in] message message to sign + * @param [in] message_length length of message + * @param [out] signed_message buffer for signed message. + * May be set to NULL to determine expected length of signed message + * @param [in, out] signed_message_length length of signed_message + * @return THEMIS_SUCCESS on success or an error code on failure + * @note If signed_message is NULL or signed_message_length is not enough to store the signed message + * then THEMIS_BUFFER_TOO_SMALL will be returned and signed_message_length will contain + * the length of the buffer needed to store the signed message. + */ +themis_status_t themis_secure_message_sign(const uint8_t* private_key, + const size_t private_key_length, + const uint8_t* message, + const size_t message_length, + uint8_t* signed_message, + size_t* signed_message_length); + +/** + * @brief verify signature on a signed message + * @param [in] public_key peer public key + * @param [in] public_key_length length of public_key + * @param [in] signed_message signed message to verify + * @param [in] signed_message_length length of signed_message + * @param [out] message buffer for original message (without signature). + * May be set to NULL to determine expected length of original message + * @param [in, out] message_length length of message + * @return THEMIS_SUCCESS on success or an error code on failure + * @note If message is NULL or message_length is not enough to store the original message + * then THEMIS_BUFFER_TOO_SMALL will be returned and message_length will contain + * the length of the buffer needed to store the original message. + */ +themis_status_t themis_secure_message_verify(const uint8_t* public_key, + const size_t public_key_length, + const uint8_t* signed_message, + const size_t signed_message_length, + uint8_t* message, + size_t* message_length); + /** * @brief wrap message to secure message * @param [in] private_key private key diff --git a/tests/themis/themis_seccure_message.c b/tests/themis/themis_seccure_message.c index 5b70270d6..4711b2b95 100644 --- a/tests/themis/themis_seccure_message.c +++ b/tests/themis/themis_seccure_message.c @@ -23,6 +23,7 @@ #define MAX_MESSAGE_SIZE 2048 #define MESSAGES_TO_SEND 3 +#define MAX_KEY_SIZE 4096 #define RSA_ALG 1 #define EC_ALG 2 #define test_check(function_call, success_res, msg) { \ @@ -52,6 +53,154 @@ static themis_status_t themis_gen_key_pair(int alg, uint8_t* private_key, size_t return res; } +static int generic_themis_secure_message_encrypt_decrypt_test(int alg, const uint8_t* message, const size_t message_length){ + int res=-1; + themis_status_t status=THEMIS_FAIL; + + uint8_t private_key[MAX_KEY_SIZE]={0}; + uint8_t public_key[MAX_KEY_SIZE]={0}; + size_t private_key_length=sizeof(private_key); + size_t public_key_length=sizeof(public_key); + + uint8_t* encrypted_message=NULL; + uint8_t* decrypted_message=NULL; + size_t encrypted_message_length=0; + size_t decrypted_message_length=0; + + status=themis_gen_key_pair(alg, private_key, &private_key_length, public_key, &public_key_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "themis_gen_key_pair failed"); + goto out; + } + + status=themis_secure_message_encrypt(private_key, private_key_length, public_key, public_key_length, message, message_length, NULL, &encrypted_message_length); + if(status!=THEMIS_BUFFER_TOO_SMALL){ + testsuite_fail_if(true, "themis_secure_message_encrypt failed to determine encrypted message length"); + goto out; + } + + encrypted_message=malloc(encrypted_message_length); + if(!encrypted_message){ + testsuite_fail_if(true, "failed to allocate memory for encrypted message"); + goto out; + } + + status=themis_secure_message_encrypt(private_key, private_key_length, public_key, public_key_length, message, message_length, encrypted_message, &encrypted_message_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "themis_secure_message_encrypt failed to encrypt message"); + goto out; + } + + status=themis_secure_message_decrypt(private_key, private_key_length, public_key, public_key_length, encrypted_message, encrypted_message_length, NULL, &decrypted_message_length); + if(status!=THEMIS_BUFFER_TOO_SMALL){ + testsuite_fail_if(true, "themis_secure_message_decrypt failed to determine decrypted message length"); + goto out; + } + + decrypted_message=malloc(decrypted_message_length); + if(!decrypted_message){ + testsuite_fail_if(true, "failed to allocate memory for decrypted message"); + goto out; + } + + status=themis_secure_message_decrypt(private_key, private_key_length, public_key, public_key_length, encrypted_message, encrypted_message_length, decrypted_message, &decrypted_message_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "themis_secure_message_decrypt failed to decrypt message"); + goto out; + } + + if(decrypted_message_length!=message_length){ + testsuite_fail_if(true, "themis_secure_message_encrypt/decrypt does not preserve message length"); + goto out; + } + + if(memcmp(decrypted_message, message, message_length)!=0){ + testsuite_fail_if(true, "themis_secure_message_encrypt/decrypt does not preserve message content"); + goto out; + } + + res=0; + +out: + free(decrypted_message); + free(encrypted_message); + return res; +} + +static int generic_themis_secure_message_sign_verify_test(int alg, const uint8_t* message, const size_t message_length){ + int res=-1; + themis_status_t status=THEMIS_FAIL; + + uint8_t private_key[MAX_KEY_SIZE]={0}; + uint8_t public_key[MAX_KEY_SIZE]={0}; + size_t private_key_length=sizeof(private_key); + size_t public_key_length=sizeof(public_key); + + uint8_t* signed_message=NULL; + uint8_t* verified_message=NULL; + size_t signed_message_length=0; + size_t verified_message_length=0; + + status=themis_gen_key_pair(alg, private_key, &private_key_length, public_key, &public_key_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "themis_gen_key_pair failed"); + goto out; + } + + status=themis_secure_message_sign(private_key, private_key_length, message, message_length, NULL, &signed_message_length); + if(status!=THEMIS_BUFFER_TOO_SMALL){ + testsuite_fail_if(true, "themis_secure_message_sign failed to determine signed message length"); + goto out; + } + + signed_message=malloc(signed_message_length); + if(!signed_message){ + testsuite_fail_if(true, "failed to allocate memory for signed message"); + goto out; + } + + status=themis_secure_message_sign(private_key, private_key_length, message, message_length, signed_message, &signed_message_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "themis_secure_message_sign failed to sign message"); + goto out; + } + + status=themis_secure_message_verify(public_key, public_key_length, signed_message, signed_message_length, NULL, &verified_message_length); + if(status!=THEMIS_BUFFER_TOO_SMALL){ + testsuite_fail_if(true, "themis_secure_message_verify failed to determine verified message length"); + goto out; + } + + verified_message=malloc(verified_message_length); + if(!verified_message){ + testsuite_fail_if(true, "failed to allocate memory for verified message"); + goto out; + } + + status=themis_secure_message_verify(public_key, public_key_length, signed_message, signed_message_length, verified_message, &verified_message_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "themis_secure_message_verify failed to verify message"); + goto out; + } + + if(verified_message_length!=message_length){ + testsuite_fail_if(true, "themis_secure_message_sign/verify does not preserve message length"); + goto out; + } + + if(memcmp(verified_message, message, message_length)!=0){ + testsuite_fail_if(true, "themis_secure_message_sign/verify does not preserve message content"); + goto out; + } + + res=0; + + out: + free(verified_message); + free(signed_message); + return res; + } + static int themis_secure_signed_message_generic_test(int alg, const char* message, const size_t message_length){ uint8_t private_key[10240]; size_t private_key_length=10240; @@ -215,14 +364,197 @@ static void themis_secure_message_test(){ "Hit http://ftp.us.debian.org[11] wheezy/contrib Translation-en"; size_t message_length=strlen(message); - - testsuite_fail_if(themis_secure_signed_message_generic_test(RSA_ALG, message, message_length), "themis secure signed message (RSA)"); - testsuite_fail_if(themis_secure_signed_message_generic_test(EC_ALG, message,message_length), "themis secure signed message (EC)"); - testsuite_fail_if(themis_secure_encrypted_message_generic_test(RSA_ALG, message, message_length), "themis secure encrypted message (RSA)"); - testsuite_fail_if(themis_secure_encrypted_message_generic_test(EC_ALG, message,message_length), "themis secure encrypted message (EC)"); + + testsuite_fail_if(generic_themis_secure_message_encrypt_decrypt_test(RSA_ALG, message, message_length), "themis secure encrypt/decrypt message (RSA)"); + testsuite_fail_if(generic_themis_secure_message_encrypt_decrypt_test(EC_ALG, message, message_length), "themis secure encrypt/decrypt message (EC)"); + testsuite_fail_if(generic_themis_secure_message_sign_verify_test(RSA_ALG, message, message_length), "themis secure sign/verify message (RSA)"); + testsuite_fail_if(generic_themis_secure_message_sign_verify_test(EC_ALG, message, message_length), "themis secure sign/verify message (EC)"); + + testsuite_fail_if(themis_secure_signed_message_generic_test(RSA_ALG, message, message_length), "(deprecated) themis secure signed message (RSA)"); + testsuite_fail_if(themis_secure_signed_message_generic_test(EC_ALG, message,message_length), "(deprecated) themis secure signed message (EC)"); + testsuite_fail_if(themis_secure_encrypted_message_generic_test(RSA_ALG, message, message_length), "(deprecated) themis secure encrypted message (RSA)"); + testsuite_fail_if(themis_secure_encrypted_message_generic_test(EC_ALG, message,message_length), "(deprecated) themis secure encrypted message (EC)"); +} + +static void themis_secure_message_encrypt_decrypt_api_test(void){ + themis_status_t status=THEMIS_FAIL; + + uint8_t private_key[MAX_KEY_SIZE]={0}; + uint8_t public_key[MAX_KEY_SIZE]={0}; + size_t private_key_length = sizeof(private_key); + size_t public_key_length = sizeof(public_key); + + uint8_t plaintext[MAX_MESSAGE_SIZE]={0}; + uint8_t encrypted[MAX_MESSAGE_SIZE+1024]={0}; + uint8_t decrypted[MAX_MESSAGE_SIZE]={0}; + size_t plaintext_length = sizeof(plaintext); + size_t encrypted_length = sizeof(encrypted); + size_t decrypted_length = sizeof(decrypted); + + status=themis_gen_ec_key_pair(private_key, &private_key_length, public_key, &public_key_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "themis_gen_ec_key_pair failed"); + return; + } + + status=soter_rand(plaintext, plaintext_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "soter_rand failed"); + return; + } + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_encrypt(NULL, private_key_length, public_key, public_key_length, plaintext, plaintext_length, encrypted, &encrypted_length), + "themis_secure_message_encrypt: private key is NULL"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_encrypt(private_key, 0, public_key, public_key_length, plaintext, plaintext_length, encrypted, &encrypted_length), + "themis_secure_message_encrypt: private key is empty"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_encrypt(private_key, private_key_length-1, public_key, public_key_length, plaintext, plaintext_length, encrypted, &encrypted_length), + "themis_secure_message_encrypt: private key is invalid"); + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_encrypt(private_key, private_key_length, NULL, public_key_length, plaintext, plaintext_length, encrypted, &encrypted_length), + "themis_secure_message_encrypt: public key is NULL"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_encrypt(private_key, private_key_length, public_key, 0, plaintext, plaintext_length, encrypted, &encrypted_length), + "themis_secure_message_encrypt: public key is empty"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_encrypt(private_key, private_key_length, public_key, public_key_length-1, plaintext, plaintext_length, encrypted, &encrypted_length), + "themis_secure_message_encrypt: public key is invalid"); + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_encrypt(private_key, private_key_length, public_key, public_key_length, NULL, plaintext_length, encrypted, &encrypted_length), + "themis_secure_message_encrypt: plaintext is NULL"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_encrypt(private_key, private_key_length, public_key, public_key_length, plaintext, 0, encrypted, &encrypted_length), + "themis_secure_message_encrypt: plaintext is empty"); + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_encrypt(private_key, private_key_length, public_key, public_key_length, plaintext, plaintext_length, NULL, NULL), + "themis_secure_message_encrypt: encrypted_length is NULL"); + + testsuite_fail_unless(THEMIS_BUFFER_TOO_SMALL == themis_secure_message_encrypt(private_key, private_key_length, public_key, public_key_length, plaintext, plaintext_length, NULL, &encrypted_length), + "themis_secure_message_encrypt: encrypted buffer length"); + encrypted_length -= 1; + testsuite_fail_unless(THEMIS_BUFFER_TOO_SMALL == themis_secure_message_encrypt(private_key, private_key_length, public_key, public_key_length, plaintext, plaintext_length, encrypted, &encrypted_length), + "themis_secure_message_encrypt: encrypted buffer too small"); + + status=themis_secure_message_encrypt(private_key, private_key_length, public_key, public_key_length, plaintext, plaintext_length, encrypted, &encrypted_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "themis_secure_message_encrypt failed to encrypt"); + return; + } + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_decrypt(NULL, private_key_length, public_key, public_key_length, encrypted, encrypted_length, decrypted, &decrypted_length), + "themis_secure_message_decrypt: private key is NULL"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_decrypt(private_key, 0, public_key, public_key_length, encrypted, encrypted_length, decrypted, &decrypted_length), + "themis_secure_message_decrypt: private key is empty"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_decrypt(private_key, private_key_length-1, public_key, public_key_length, encrypted, encrypted_length, decrypted, &decrypted_length), + "themis_secure_message_decrypt: private key is invalid"); + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_decrypt(private_key, private_key_length, NULL, public_key_length, encrypted, encrypted_length, decrypted, &decrypted_length), + "themis_secure_message_decrypt: public key is NULL"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_decrypt(private_key, private_key_length, public_key, 0, encrypted, encrypted_length, decrypted, &decrypted_length), + "themis_secure_message_decrypt: public key is empty"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_decrypt(private_key, private_key_length, public_key, public_key_length-1, encrypted, encrypted_length, decrypted, &decrypted_length), + "themis_secure_message_decrypt: public key is invalid"); + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_decrypt(private_key, private_key_length, public_key, public_key_length, NULL, encrypted_length, decrypted, &decrypted_length), + "themis_secure_message_decrypt: encrypted is NULL"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_decrypt(private_key, private_key_length, public_key, public_key_length, encrypted, 0, decrypted, &decrypted_length), + "themis_secure_message_decrypt: encrypted is empty"); + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_decrypt(private_key, private_key_length, public_key, public_key_length, encrypted, encrypted_length, NULL, NULL), + "themis_secure_message_decrypt: decrypted_length is NULL"); + + testsuite_fail_unless(THEMIS_BUFFER_TOO_SMALL == themis_secure_message_decrypt(private_key, private_key_length, public_key, public_key_length, encrypted, encrypted_length, NULL, &decrypted_length), + "themis_secure_message_decrypt: decrypted buffer length"); + decrypted_length -= 1; + testsuite_fail_unless(THEMIS_BUFFER_TOO_SMALL == themis_secure_message_decrypt(private_key, private_key_length, public_key, public_key_length, encrypted, encrypted_length, decrypted, &decrypted_length), + "themis_secure_message_decrypt: decrypted buffer too small"); + + status=themis_secure_message_decrypt(private_key, private_key_length, public_key, public_key_length, encrypted, encrypted_length, decrypted, &decrypted_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "themis_secure_message_decrypt failed to decrypt"); + return; + } +} + +static void themis_secure_message_sign_verify_api_test(void){ + themis_status_t status=THEMIS_FAIL; + + uint8_t private_key[MAX_KEY_SIZE]={0}; + uint8_t public_key[MAX_KEY_SIZE]={0}; + size_t private_key_length = sizeof(private_key); + size_t public_key_length = sizeof(public_key); + + uint8_t plaintext[MAX_MESSAGE_SIZE]={0}; + uint8_t signed_msg[MAX_MESSAGE_SIZE+1024]={0}; + uint8_t verified[MAX_MESSAGE_SIZE]={0}; + size_t plaintext_length = sizeof(plaintext); + size_t signed_msg_length = sizeof(signed_msg); + size_t verified_length = sizeof(verified); + + status=themis_gen_ec_key_pair(private_key, &private_key_length, public_key, &public_key_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "themis_gen_ec_key_pair failed"); + return; + } + + status=soter_rand(plaintext, plaintext_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "soter_rand failed"); + return; + } + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_sign(NULL, private_key_length, plaintext, plaintext_length, signed_msg, &signed_msg_length), + "themis_secure_message_sign: private key is NULL"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_sign(private_key, 0, plaintext, plaintext_length, signed_msg, &signed_msg_length), + "themis_secure_message_sign: private key is empty"); + testsuite_fail_unless(THEMIS_FAIL == themis_secure_message_sign(private_key, private_key_length-1, plaintext, plaintext_length, signed_msg, &signed_msg_length), + "themis_secure_message_sign: private key is invalid"); + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_sign(private_key, private_key_length, NULL, plaintext_length, signed_msg, &signed_msg_length), + "themis_secure_message_sign: plaintext is NULL"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_sign(private_key, private_key_length, plaintext, 0, signed_msg, &signed_msg_length), + "themis_secure_message_sign: plaintext is empty"); + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_sign(private_key, private_key_length, plaintext, plaintext_length, NULL, NULL), + "themis_secure_message_sign: signed_msg_length is NULL"); + + testsuite_fail_unless(THEMIS_BUFFER_TOO_SMALL == themis_secure_message_sign(private_key, private_key_length, plaintext, plaintext_length, NULL, &signed_msg_length), + "themis_secure_message_sign: signed buffer length"); + signed_msg_length -= 1; + testsuite_fail_unless(THEMIS_BUFFER_TOO_SMALL == themis_secure_message_sign(private_key, private_key_length, plaintext, plaintext_length, signed_msg, &signed_msg_length), + "themis_secure_message_sign: signed buffer too small"); + + status=themis_secure_message_sign(private_key, private_key_length, plaintext, plaintext_length, signed_msg, &signed_msg_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "themis_secure_message_sign failed to sign"); + return; + } + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_verify(NULL, public_key_length, signed_msg, signed_msg_length, verified, &verified_length), + "themis_secure_message_verify: public key is NULL"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_verify(public_key, 0, signed_msg, signed_msg_length, verified, &verified_length), + "themis_secure_message_verify: public key is empty"); + testsuite_fail_unless(THEMIS_FAIL == themis_secure_message_verify(public_key, public_key_length-1, signed_msg, signed_msg_length, verified, &verified_length), + "themis_secure_message_verify: public key is invalid"); + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_verify(public_key, public_key_length, NULL, signed_msg_length, verified, &verified_length), + "themis_secure_message_verify: signed_msg is NULL"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_verify(public_key, public_key_length, signed_msg, 0, verified, &verified_length), + "themis_secure_message_verify: signed_msg is empty"); + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_verify(public_key, public_key_length, signed_msg, signed_msg_length, NULL, NULL), + "themis_secure_message_verify: verified_length is NULL"); + + testsuite_fail_unless(THEMIS_BUFFER_TOO_SMALL == themis_secure_message_verify(public_key, public_key_length, signed_msg, signed_msg_length, NULL, &verified_length), + "themis_secure_message_verify: verified buffer length"); + verified_length -= 1; + testsuite_fail_unless(THEMIS_BUFFER_TOO_SMALL == themis_secure_message_verify(public_key, public_key_length, signed_msg, signed_msg_length, verified, &verified_length), + "themis_secure_message_verify: verified buffer too small"); + + status=themis_secure_message_verify(public_key, public_key_length, signed_msg, signed_msg_length, verified, &verified_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "themis_secure_message_verify failed to verify"); + return; + } } -static void secure_message_api_test(void) +static void secure_message_old_api_test(void) { uint8_t plaintext[MAX_MESSAGE_SIZE]; size_t plaintext_length = 2048; //rand_int(MAX_MESSAGE_SIZE); @@ -319,7 +651,9 @@ void run_secure_message_test(){ testsuite_run_test(themis_secure_message_test); testsuite_enter_suite("generic secure message: api test"); - testsuite_run_test(secure_message_api_test); + testsuite_run_test(themis_secure_message_encrypt_decrypt_api_test); + testsuite_run_test(themis_secure_message_sign_verify_api_test); + testsuite_run_test(secure_message_old_api_test); } From 54aefceaab19957cce424b920684328e7998b8df Mon Sep 17 00:00:00 2001 From: ilammy Date: Wed, 20 Feb 2019 20:20:58 +0200 Subject: [PATCH 02/21] Cast test message as uint8_t "char" may be signed or unsigned depending on a platform. Thus it is necessary to cast it as "uint8_t" explicitly. --- tests/themis/themis_seccure_message.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/themis/themis_seccure_message.c b/tests/themis/themis_seccure_message.c index 4711b2b95..02623d573 100644 --- a/tests/themis/themis_seccure_message.c +++ b/tests/themis/themis_seccure_message.c @@ -363,12 +363,13 @@ static void themis_secure_message_test(){ "Hit http://ftp.us.debian.org[10] wheezy/contrib i386 Packages" "Hit http://ftp.us.debian.org[11] wheezy/contrib Translation-en"; + const uint8_t* message_bytes=(const uint8_t*)message; size_t message_length=strlen(message); - testsuite_fail_if(generic_themis_secure_message_encrypt_decrypt_test(RSA_ALG, message, message_length), "themis secure encrypt/decrypt message (RSA)"); - testsuite_fail_if(generic_themis_secure_message_encrypt_decrypt_test(EC_ALG, message, message_length), "themis secure encrypt/decrypt message (EC)"); - testsuite_fail_if(generic_themis_secure_message_sign_verify_test(RSA_ALG, message, message_length), "themis secure sign/verify message (RSA)"); - testsuite_fail_if(generic_themis_secure_message_sign_verify_test(EC_ALG, message, message_length), "themis secure sign/verify message (EC)"); + testsuite_fail_if(generic_themis_secure_message_encrypt_decrypt_test(RSA_ALG, message_bytes, message_length), "themis secure encrypt/decrypt message (RSA)"); + testsuite_fail_if(generic_themis_secure_message_encrypt_decrypt_test(EC_ALG, message_bytes, message_length), "themis secure encrypt/decrypt message (EC)"); + testsuite_fail_if(generic_themis_secure_message_sign_verify_test(RSA_ALG, message_bytes, message_length), "themis secure sign/verify message (RSA)"); + testsuite_fail_if(generic_themis_secure_message_sign_verify_test(EC_ALG, message_bytes, message_length), "themis secure sign/verify message (EC)"); testsuite_fail_if(themis_secure_signed_message_generic_test(RSA_ALG, message, message_length), "(deprecated) themis secure signed message (RSA)"); testsuite_fail_if(themis_secure_signed_message_generic_test(EC_ALG, message,message_length), "(deprecated) themis secure signed message (EC)"); From aaaccdbcce1dfc894a8f3fb25bcdb764b061b0ed Mon Sep 17 00:00:00 2001 From: ilammy Date: Wed, 20 Feb 2019 20:26:54 +0200 Subject: [PATCH 03/21] Implement new Secure Message API using primitives This effectively moves themis_secure_message_{wrap,unwrap} code to these specific functions. However, with changes and simplifications. For example, verification and decryption no longer has to guess the type of the container: we now expect only encrypted containers when decrypting and signed containers for verification. Initially I wanted to reimplement themis_secure_message_{wrap,unwrap} in terms of the new functions, however their behavior has several idiosyncrasies so the old functions are still there with the same implementation. For example, if the provided keys are invalid then in in encrypt/decrypt mode the old API returns THEMIS_INVALID_PARAM but in sign/verify mode it returns THEMIS_FAIL. The new API returns THEMIS_INVALID_PARAM consistently (as it is evident from the tests). Leave a comment in the code for developers so that they don't try to (incorrectly) reimplement, for example, unwrap with decrypt and verify or vice versa. --- src/themis/secure_message.c | 58 ++++++++++++++++++++++++-- tests/soter/nist-sts/assess | Bin 126504 -> 117236 bytes tests/themis/themis_seccure_message.c | 4 +- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/themis/secure_message.c b/src/themis/secure_message.c index bd2530dd7..967905fde 100644 --- a/src/themis/secure_message.c +++ b/src/themis/secure_message.c @@ -93,7 +93,14 @@ themis_status_t themis_secure_message_encrypt(const uint8_t* private_key, THEMIS_CHECK_PARAM(message!=NULL); THEMIS_CHECK_PARAM(message_length!=0); THEMIS_CHECK_PARAM(encrypted_message_length!=NULL); - return themis_secure_message_wrap(private_key, private_key_length, public_key, public_key_length, message, message_length, encrypted_message, encrypted_message_length); + + themis_secure_message_encrypter_t* ctx=NULL; + ctx = themis_secure_message_encrypter_init(private_key, private_key_length, public_key, public_key_length); + THEMIS_CHECK_PARAM(ctx); + + themis_status_t status=themis_secure_message_encrypter_proceed(ctx, message, message_length, encrypted_message, encrypted_message_length); + themis_secure_message_encrypter_destroy(ctx); + return status; } themis_status_t themis_secure_message_decrypt(const uint8_t* private_key, @@ -112,7 +119,18 @@ themis_status_t themis_secure_message_decrypt(const uint8_t* private_key, THEMIS_CHECK_PARAM(encrypted_message!=NULL); THEMIS_CHECK_PARAM(encrypted_message_length!=0); THEMIS_CHECK_PARAM(message_length!=NULL); - return themis_secure_message_unwrap(private_key, private_key_length, public_key, public_key_length, encrypted_message, encrypted_message_length, message, message_length); + + themis_secure_message_hdr_t* message_hdr=(themis_secure_message_hdr_t*)encrypted_message; + THEMIS_CHECK_PARAM(IS_THEMIS_SECURE_MESSAGE_ENCRYPTED(message_hdr->message_type)); + THEMIS_CHECK_PARAM(encrypted_message_length>=THEMIS_SECURE_MESSAGE_LENGTH(message_hdr)); + + themis_secure_message_decrypter_t* ctx=NULL; + ctx = themis_secure_message_decrypter_init(private_key, private_key_length, public_key, public_key_length); + THEMIS_CHECK_PARAM(ctx); + + themis_status_t status=themis_secure_message_decrypter_proceed(ctx, encrypted_message, encrypted_message_length, message, message_length); + themis_secure_message_decrypter_destroy(ctx); + return status; } themis_status_t themis_secure_message_sign(const uint8_t* private_key, @@ -127,7 +145,14 @@ themis_status_t themis_secure_message_sign(const uint8_t* private_key, THEMIS_CHECK_PARAM(message!=NULL); THEMIS_CHECK_PARAM(message_length!=0); THEMIS_CHECK_PARAM(signed_message_length!=NULL); - return themis_secure_message_wrap(private_key, private_key_length, NULL, 0, message, message_length, signed_message, signed_message_length); + + themis_secure_message_signer_t* ctx=NULL; + ctx = themis_secure_message_signer_init(private_key, private_key_length); + THEMIS_CHECK_PARAM(ctx); + + themis_status_t res=themis_secure_message_signer_proceed(ctx, message, message_length, signed_message, signed_message_length); + themis_secure_message_signer_destroy(ctx); + return res; } themis_status_t themis_secure_message_verify(const uint8_t* public_key, @@ -142,9 +167,34 @@ themis_status_t themis_secure_message_verify(const uint8_t* public_key, THEMIS_CHECK_PARAM(signed_message!=NULL); THEMIS_CHECK_PARAM(signed_message_length!=0); THEMIS_CHECK_PARAM(message_length!=NULL); - return themis_secure_message_unwrap(NULL, 0, public_key, public_key_length, signed_message, signed_message_length, message, message_length); + + themis_secure_message_hdr_t* message_hdr=(themis_secure_message_hdr_t*)signed_message; + THEMIS_CHECK_PARAM(IS_THEMIS_SECURE_MESSAGE_SIGNED(message_hdr->message_type)); + THEMIS_CHECK_PARAM(signed_message_length>=THEMIS_SECURE_MESSAGE_LENGTH(message_hdr)); + + themis_secure_message_verifier_t* ctx=NULL; + ctx = themis_secure_message_verifier_init(public_key, public_key_length); + THEMIS_CHECK_PARAM(ctx); + + themis_status_t status=themis_secure_message_verifier_proceed(ctx, signed_message, signed_message_length, message, message_length); + themis_secure_message_verifier_destroy(ctx); + return status; } +/* + * themis_secure_message_wrap() and themis_secure_message_unwrap() functions + * are deprecated in favor of more specific themis_secure_message_encrypt() + * and its friends. + * + * The old functions combined the interface of the new ones (wrap = encrypt + * or sign, unwrap = decrypt or verify). The new functions provide a more + * cleanly separated interface for distinct concerns. + * + * Note that while their implementation looks similar, they are not quite + * the same and differ slightly in error handling. Don't try to reimplement + * them in terms of each other. We will remove wrap and unwrap eventually. + */ + themis_status_t themis_secure_message_wrap(const uint8_t* private_key, const size_t private_key_length, const uint8_t* public_key, diff --git a/tests/soter/nist-sts/assess b/tests/soter/nist-sts/assess index 2d91db874f4cb18707f725b189fe8ae28f28fdb3..4e247dae03b20f525700b2b9be65c4a8a8013b42 100755 GIT binary patch literal 117236 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 z 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 cUEL6 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 Z)Hk-lz0(e5bZ4D)s)X%!>YGGc_ Date: Wed, 20 Feb 2019 20:41:46 +0200 Subject: [PATCH 04/21] Mark old Secure Message API as deprecated Actually mark the old functions them with a deprecation attribute. This will produce warnings during compilation which should prompt the users to migrate to the new API. Add an exception to our test code which still needs to test the old API. We treat warnings as errors and don't want to fail the build. --- src/themis/secure_message.h | 2 ++ tests/themis/themis_seccure_message.c | 21 ++++++++------------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/themis/secure_message.h b/src/themis/secure_message.h index 66a13007c..4f76c493b 100644 --- a/src/themis/secure_message.h +++ b/src/themis/secure_message.h @@ -169,6 +169,7 @@ themis_status_t themis_secure_message_verify(const uint8_t* public_key, * @return THEMIS_SUCCESS on success or THEMIS_FAIL on failure * @note If wrapped_message==NULL or wrapped_message_length is not enought for wrapped message storage then THEMIS_BUFFER_TOO_SMALL will return and wrapped_message_length will store length of buffer needed for wrapped message store */ +DEPRECATED("use 'themis_secure_message_encrypt' or 'themis_secure_message_sign' instead") themis_status_t themis_secure_message_wrap(const uint8_t* private_key, const size_t private_key_length, const uint8_t* public_key, @@ -191,6 +192,7 @@ themis_status_t themis_secure_message_wrap(const uint8_t* private_key, * @return THEMIS_SUCCESS on success or THEMIS_FAIL on failure * @note If message==NULL or message_length is not enought for plain message storage then THEMIS_BUFFER_TOO_SMALL will return and message_length will store length of buffer needed for plain message store */ +DEPRECATED("use 'themis_secure_message_decrypt' or 'themis_secure_message_verify' instead") themis_status_t themis_secure_message_unwrap(const uint8_t* private_key, const size_t private_key_length, const uint8_t* public_key, diff --git a/tests/themis/themis_seccure_message.c b/tests/themis/themis_seccure_message.c index 118c20503..88cedbee6 100644 --- a/tests/themis/themis_seccure_message.c +++ b/tests/themis/themis_seccure_message.c @@ -41,6 +41,14 @@ return -1; \ }} +/* + * Allow usage of deprecated Secure Message interface: + * - themis_secure_message_wrap() + * - themis_secure_message_unwrap() + */ +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif static themis_status_t themis_gen_key_pair(int alg, uint8_t* private_key, size_t* private_key_length, uint8_t* public_key, size_t* public_key_length){ themis_status_t res=THEMIS_FAIL; @@ -656,16 +664,3 @@ void run_secure_message_test(){ testsuite_run_test(themis_secure_message_sign_verify_api_test); testsuite_run_test(secure_message_old_api_test); } - - - - - - - - - - - - - From acb767b4e87040360b7ad813ad9e2431c9743acf Mon Sep 17 00:00:00 2001 From: ilammy Date: Wed, 20 Feb 2019 20:55:50 +0200 Subject: [PATCH 05/21] Use new Secure Message API: ThemisPP C++ methods are already using the new names so just replace the calls with the new ones and we're done here. --- src/wrappers/themis/themispp/secure_message.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/wrappers/themis/themispp/secure_message.hpp b/src/wrappers/themis/themispp/secure_message.hpp index 39b5aadbb..7817de240 100644 --- a/src/wrappers/themis/themispp/secure_message.hpp +++ b/src/wrappers/themis/themispp/secure_message.hpp @@ -58,12 +58,12 @@ namespace themispp { } themis_status_t status=THEMIS_FAIL; size_t encrypted_data_length=0; - status=themis_secure_message_wrap(&private_key_[0], private_key_.size(), &peer_public_key_[0], peer_public_key_.size(), &(*data_begin), data_end-data_begin, NULL, &encrypted_data_length); + status=themis_secure_message_encrypt(&private_key_[0], private_key_.size(), &peer_public_key_[0], peer_public_key_.size(), &(*data_begin), data_end-data_begin, NULL, &encrypted_data_length); if(status!=THEMIS_BUFFER_TOO_SMALL){ throw themispp::exception_t("Secure Message failed to encrypt message", status); } res_.resize(encrypted_data_length); - status=themis_secure_message_wrap(&private_key_[0], private_key_.size(), &peer_public_key_[0], peer_public_key_.size(), &(*data_begin), data_end-data_begin, &res_[0], &encrypted_data_length); + status=themis_secure_message_encrypt(&private_key_[0], private_key_.size(), &peer_public_key_[0], peer_public_key_.size(), &(*data_begin), data_end-data_begin, &res_[0], &encrypted_data_length); if(status!=THEMIS_SUCCESS){ throw themispp::exception_t("Secure Message failed to encrypt message", status); } @@ -86,12 +86,12 @@ namespace themispp { } themis_status_t status=THEMIS_FAIL; size_t decrypted_data_length=0; - status=themis_secure_message_unwrap(&private_key_[0], private_key_.size(), &peer_public_key_[0], peer_public_key_.size(), &(*data_begin), data_end-data_begin, NULL, &decrypted_data_length); + status=themis_secure_message_decrypt(&private_key_[0], private_key_.size(), &peer_public_key_[0], peer_public_key_.size(), &(*data_begin), data_end-data_begin, NULL, &decrypted_data_length); if(status!=THEMIS_BUFFER_TOO_SMALL){ throw themispp::exception_t("Secure Message failed to decrypt message", status); } res_.resize(decrypted_data_length); - status=themis_secure_message_unwrap(&private_key_[0], private_key_.size(), &peer_public_key_[0], peer_public_key_.size(), &(*data_begin), data_end-data_begin, &res_[0], &decrypted_data_length); + status=themis_secure_message_decrypt(&private_key_[0], private_key_.size(), &peer_public_key_[0], peer_public_key_.size(), &(*data_begin), data_end-data_begin, &res_[0], &decrypted_data_length); if(status!=THEMIS_SUCCESS){ throw themispp::exception_t("Secure Message failed to decrypt message", status); } @@ -111,12 +111,12 @@ namespace themispp { } themis_status_t status=THEMIS_FAIL; size_t encrypted_data_length=0; - status=themis_secure_message_wrap(&private_key_[0], private_key_.size(), NULL, 0, &(*data_begin), data_end-data_begin, NULL, &encrypted_data_length); + status=themis_secure_message_sign(&private_key_[0], private_key_.size(), &(*data_begin), data_end-data_begin, NULL, &encrypted_data_length); if(status!=THEMIS_BUFFER_TOO_SMALL){ throw themispp::exception_t("Secure Message failed to sign message", status); } res_.resize(encrypted_data_length); - status=themis_secure_message_wrap(&private_key_[0], private_key_.size(), NULL, 0, &(*data_begin), data_end-data_begin, &res_[0], &encrypted_data_length); + status=themis_secure_message_sign(&private_key_[0], private_key_.size(), &(*data_begin), data_end-data_begin, &res_[0], &encrypted_data_length); if(status!=THEMIS_SUCCESS){ throw themispp::exception_t("Secure Message failed to sign message", status); } @@ -136,12 +136,12 @@ namespace themispp { } themis_status_t status=THEMIS_FAIL; size_t decrypted_data_length=0; - status=themis_secure_message_unwrap(NULL, 0, &peer_public_key_[0], peer_public_key_.size(), &(*data_begin), data_end-data_begin, NULL, &decrypted_data_length); + status=themis_secure_message_verify(&peer_public_key_[0], peer_public_key_.size(), &(*data_begin), data_end-data_begin, NULL, &decrypted_data_length); if(status!=THEMIS_BUFFER_TOO_SMALL){ throw themispp::exception_t("Secure Message failed to verify signature", status); } res_.resize(decrypted_data_length); - status=themis_secure_message_unwrap(NULL, 0, &peer_public_key_[0], peer_public_key_.size(), &(*data_begin), data_end-data_begin, &res_[0], &decrypted_data_length); + status=themis_secure_message_verify(&peer_public_key_[0], peer_public_key_.size(), &(*data_begin), data_end-data_begin, &res_[0], &decrypted_data_length); if(status!=THEMIS_SUCCESS){ throw themispp::exception_t("Secure Message failed to verify signature", status); } From 9ff2cf026d3ff8cfb8183e57cf74596c53122d72 Mon Sep 17 00:00:00 2001 From: ilammy Date: Wed, 20 Feb 2019 21:08:07 +0200 Subject: [PATCH 06/21] Use new Secure Message API: rust-themis Rust methods already use new names so we only have to change the implementation. It's quite repetetive and verbose but meh... we have to call different functions with different interfaces. (It may be possible to avoid some of the copypasta with macros but I doubt they will be more maintainable than this.) --- .../themis/rust/src/secure_message.rs | 277 +++++++++++------- 1 file changed, 171 insertions(+), 106 deletions(-) diff --git a/src/wrappers/themis/rust/src/secure_message.rs b/src/wrappers/themis/rust/src/secure_message.rs index 948a2f0fb..a108ebf05 100644 --- a/src/wrappers/themis/rust/src/secure_message.rs +++ b/src/wrappers/themis/rust/src/secure_message.rs @@ -64,7 +64,10 @@ use std::ptr; -use bindings::{themis_secure_message_unwrap, themis_secure_message_wrap}; +use bindings::{ + themis_secure_message_decrypt, themis_secure_message_encrypt, themis_secure_message_sign, + themis_secure_message_verify, +}; use crate::error::{Error, ErrorKind, Result}; use crate::keys::{KeyPair, PrivateKey, PublicKey}; @@ -138,20 +141,102 @@ impl SecureMessage { /// # } /// ``` pub fn encrypt(&self, message: impl AsRef<[u8]>) -> Result> { - wrap( - self.key_pair.private_key_bytes(), - self.key_pair.public_key_bytes(), - message.as_ref(), - ) + let (private_key_ptr, private_key_len) = into_raw_parts(self.key_pair.private_key_bytes()); + let (public_key_ptr, public_key_len) = into_raw_parts(self.key_pair.public_key_bytes()); + let (message_ptr, message_len) = into_raw_parts(message.as_ref()); + + let mut encrypted = Vec::new(); + let mut encrypted_len = 0; + + unsafe { + let status = themis_secure_message_encrypt( + private_key_ptr, + private_key_len, + public_key_ptr, + public_key_len, + message_ptr, + message_len, + ptr::null_mut(), + &mut encrypted_len, + ); + let error = Error::from_themis_status(status); + if error.kind() != ErrorKind::BufferTooSmall { + return Err(error); + } + } + + encrypted.reserve(encrypted_len); + + unsafe { + let status = themis_secure_message_encrypt( + private_key_ptr, + private_key_len, + public_key_ptr, + public_key_len, + message_ptr, + message_len, + encrypted.as_mut_ptr(), + &mut encrypted_len, + ); + let error = Error::from_themis_status(status); + if error.kind() != ErrorKind::Success { + return Err(error); + } + debug_assert!(encrypted_len <= encrypted.capacity()); + encrypted.set_len(encrypted_len as usize); + } + + Ok(encrypted) } /// Decrypts an encrypted message back into its original form. pub fn decrypt(&self, message: impl AsRef<[u8]>) -> Result> { - unwrap( - self.key_pair.private_key_bytes(), - self.key_pair.public_key_bytes(), - message.as_ref(), - ) + let (private_key_ptr, private_key_len) = into_raw_parts(self.key_pair.private_key_bytes()); + let (public_key_ptr, public_key_len) = into_raw_parts(self.key_pair.public_key_bytes()); + let (wrapped_ptr, wrapped_len) = into_raw_parts(message.as_ref()); + + let mut decrypted = Vec::new(); + let mut decrypted_len = 0; + + unsafe { + let status = themis_secure_message_decrypt( + private_key_ptr, + private_key_len, + public_key_ptr, + public_key_len, + wrapped_ptr, + wrapped_len, + ptr::null_mut(), + &mut decrypted_len, + ); + let error = Error::from_themis_status(status); + if error.kind() != ErrorKind::BufferTooSmall { + return Err(error); + } + } + + decrypted.reserve(decrypted_len); + + unsafe { + let status = themis_secure_message_decrypt( + private_key_ptr, + private_key_len, + public_key_ptr, + public_key_len, + wrapped_ptr, + wrapped_len, + decrypted.as_mut_ptr(), + &mut decrypted_len, + ); + let error = Error::from_themis_status(status); + if error.kind() != ErrorKind::Success { + return Err(error); + } + debug_assert!(decrypted_len <= decrypted.capacity()); + decrypted.set_len(decrypted_len as usize); + } + + Ok(decrypted) } } @@ -243,7 +328,47 @@ impl SecureSign { /// # } /// ``` pub fn sign(&self, message: impl AsRef<[u8]>) -> Result> { - wrap(self.private_key.as_ref(), &[], message.as_ref()) + let (private_key_ptr, private_key_len) = into_raw_parts(self.private_key.as_ref()); + let (message_ptr, message_len) = into_raw_parts(message.as_ref()); + + let mut signed = Vec::new(); + let mut signed_len = 0; + + unsafe { + let status = themis_secure_message_sign( + private_key_ptr, + private_key_len, + message_ptr, + message_len, + ptr::null_mut(), + &mut signed_len, + ); + let error = Error::from_themis_status(status); + if error.kind() != ErrorKind::BufferTooSmall { + return Err(error); + } + } + + signed.reserve(signed_len); + + unsafe { + let status = themis_secure_message_sign( + private_key_ptr, + private_key_len, + message_ptr, + message_len, + signed.as_mut_ptr(), + &mut signed_len, + ); + let error = Error::from_themis_status(status); + if error.kind() != ErrorKind::Success { + return Err(error); + } + debug_assert!(signed_len <= signed.capacity()); + signed.set_len(signed_len as usize); + } + + Ok(signed) } } @@ -331,106 +456,46 @@ impl SecureVerify { /// Verifies the signature and returns the original message. pub fn verify(&self, message: impl AsRef<[u8]>) -> Result> { - unwrap(&[], self.public_key.as_ref(), message.as_ref()) - } -} + let (public_key_ptr, public_key_len) = into_raw_parts(self.public_key.as_ref()); + let (signed_ptr, signed_len) = into_raw_parts(message.as_ref()); -/// Wrap a message into a secure message. -fn wrap(private_key: &[u8], public_key: &[u8], message: &[u8]) -> Result> { - let (private_key_ptr, private_key_len) = into_raw_parts(private_key); - let (public_key_ptr, public_key_len) = into_raw_parts(public_key); - let (message_ptr, message_len) = into_raw_parts(message); - - let mut wrapped = Vec::new(); - let mut wrapped_len = 0; - - unsafe { - let status = themis_secure_message_wrap( - private_key_ptr, - private_key_len, - public_key_ptr, - public_key_len, - message_ptr, - message_len, - ptr::null_mut(), - &mut wrapped_len, - ); - let error = Error::from_themis_status(status); - if error.kind() != ErrorKind::BufferTooSmall { - return Err(error); - } - } + let mut original = Vec::new(); + let mut original_len = 0; - wrapped.reserve(wrapped_len); - - unsafe { - let status = themis_secure_message_wrap( - private_key_ptr, - private_key_len, - public_key_ptr, - public_key_len, - message_ptr, - message_len, - wrapped.as_mut_ptr(), - &mut wrapped_len, - ); - let error = Error::from_themis_status(status); - if error.kind() != ErrorKind::Success { - return Err(error); + unsafe { + let status = themis_secure_message_verify( + public_key_ptr, + public_key_len, + signed_ptr, + signed_len, + ptr::null_mut(), + &mut original_len, + ); + let error = Error::from_themis_status(status); + if error.kind() != ErrorKind::BufferTooSmall { + return Err(error); + } } - debug_assert!(wrapped_len <= wrapped.capacity()); - wrapped.set_len(wrapped_len as usize); - } - Ok(wrapped) -} + original.reserve(original_len); -/// Unwrap a secure message into a message. -fn unwrap(private_key: &[u8], public_key: &[u8], wrapped: &[u8]) -> Result> { - let (private_key_ptr, private_key_len) = into_raw_parts(private_key); - let (public_key_ptr, public_key_len) = into_raw_parts(public_key); - let (wrapped_ptr, wrapped_len) = into_raw_parts(wrapped); - - let mut message = Vec::new(); - let mut message_len = 0; - - unsafe { - let status = themis_secure_message_unwrap( - private_key_ptr, - private_key_len, - public_key_ptr, - public_key_len, - wrapped_ptr, - wrapped_len, - ptr::null_mut(), - &mut message_len, - ); - let error = Error::from_themis_status(status); - if error.kind() != ErrorKind::BufferTooSmall { - return Err(error); + unsafe { + let status = themis_secure_message_verify( + public_key_ptr, + public_key_len, + signed_ptr, + signed_len, + original.as_mut_ptr(), + &mut original_len, + ); + let error = Error::from_themis_status(status); + if error.kind() != ErrorKind::Success { + return Err(error); + } + debug_assert!(original_len <= original.capacity()); + original.set_len(original_len as usize); } - } - message.reserve(message_len); - - unsafe { - let status = themis_secure_message_unwrap( - private_key_ptr, - private_key_len, - public_key_ptr, - public_key_len, - wrapped_ptr, - wrapped_len, - message.as_mut_ptr(), - &mut message_len, - ); - let error = Error::from_themis_status(status); - if error.kind() != ErrorKind::Success { - return Err(error); - } - debug_assert!(message_len <= message.capacity()); - message.set_len(message_len as usize); + Ok(original) } - - Ok(message) } From c21a87fdba7e8a5898616ede4dfef53677364de5 Mon Sep 17 00:00:00 2001 From: ilammy Date: Wed, 20 Feb 2019 21:47:46 +0200 Subject: [PATCH 07/21] Use new Secure Message API: Java/Android Themis Java binding actually fails to build on CI due to deprecation warnings from the core library so we have to make these changes anyway. Current JNI interface is not flexible enough with a single boolean flag. Replace the flag with an integer action mode, provide and use the constants in Java code. JNI interface is an implementation detail so we can make this change freely. --- jni/themis_message.c | 53 ++++++++++++++----- .../com/cossacklabs/themis/SecureMessage.java | 18 ++++--- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/jni/themis_message.c b/jni/themis_message.c index e9ed80a70..d3c0f4e8f 100644 --- a/jni/themis_message.c +++ b/jni/themis_message.c @@ -18,7 +18,12 @@ #include #include -JNIEXPORT jbyteArray JNICALL Java_com_cossacklabs_themis_SecureMessage_process(JNIEnv *env, jobject thiz, jbyteArray private, jbyteArray public, jbyteArray message, jboolean is_wrap) +#define SECURE_MESSAGE_ENCRYPT 1 +#define SECURE_MESSAGE_DECRYPT 2 +#define SECURE_MESSAGE_SIGN 3 +#define SECURE_MESSAGE_VERIFY 4 + +JNIEXPORT jbyteArray JNICALL Java_com_cossacklabs_themis_SecureMessage_process(JNIEnv *env, jobject thiz, jbyteArray private, jbyteArray public, jbyteArray message, jint action) { size_t private_length = 0; size_t public_length = 0; @@ -67,13 +72,23 @@ JNIEXPORT jbyteArray JNICALL Java_com_cossacklabs_themis_SecureMessage_process(J goto err; } - if (is_wrap) - { - res = themis_secure_message_wrap((uint8_t *)priv_buf, private_length, (uint8_t *)pub_buf, public_length, (uint8_t *)message_buf, message_length, NULL, &output_length); - } - else + switch (action) { - res = themis_secure_message_unwrap((uint8_t *)priv_buf, private_length, (uint8_t *)pub_buf, public_length, (uint8_t *)message_buf, message_length, NULL, &output_length); + case SECURE_MESSAGE_ENCRYPT: + res = themis_secure_message_encrypt((uint8_t *)priv_buf, private_length, (uint8_t *)pub_buf, public_length, (uint8_t *)message_buf, message_length, NULL, &output_length); + break; + case SECURE_MESSAGE_DECRYPT: + res = themis_secure_message_decrypt((uint8_t *)priv_buf, private_length, (uint8_t *)pub_buf, public_length, (uint8_t *)message_buf, message_length, NULL, &output_length); + break; + case SECURE_MESSAGE_SIGN: + res = themis_secure_message_sign((uint8_t *)priv_buf, private_length, (uint8_t *)message_buf, message_length, NULL, &output_length); + break; + case SECURE_MESSAGE_VERIFY: + res = themis_secure_message_verify((uint8_t *)pub_buf, public_length, (uint8_t *)message_buf, message_length, NULL, &output_length); + break; + default: + res = THEMIS_NOT_SUPPORTED; + break; } if (THEMIS_BUFFER_TOO_SMALL != res) @@ -93,13 +108,23 @@ JNIEXPORT jbyteArray JNICALL Java_com_cossacklabs_themis_SecureMessage_process(J goto err; } - if (is_wrap) - { - res = themis_secure_message_wrap((uint8_t *)priv_buf, private_length, (uint8_t *)pub_buf, public_length, (uint8_t *)message_buf, message_length, (uint8_t *)output_buf, &output_length); - } - else - { - res = themis_secure_message_unwrap((uint8_t *)priv_buf, private_length, (uint8_t *)pub_buf, public_length, (uint8_t *)message_buf, message_length, (uint8_t *)output_buf, &output_length); + switch (action) + { + case SECURE_MESSAGE_ENCRYPT: + res = themis_secure_message_encrypt((uint8_t *)priv_buf, private_length, (uint8_t *)pub_buf, public_length, (uint8_t *)message_buf, message_length, (uint8_t *)output_buf, &output_length); + break; + case SECURE_MESSAGE_DECRYPT: + res = themis_secure_message_decrypt((uint8_t *)priv_buf, private_length, (uint8_t *)pub_buf, public_length, (uint8_t *)message_buf, message_length, (uint8_t *)output_buf, &output_length); + break; + case SECURE_MESSAGE_SIGN: + res = themis_secure_message_sign((uint8_t *)priv_buf, private_length, (uint8_t *)message_buf, message_length, (uint8_t *)output_buf, &output_length); + break; + case SECURE_MESSAGE_VERIFY: + res = themis_secure_message_verify((uint8_t *)pub_buf, public_length, (uint8_t *)message_buf, message_length, (uint8_t *)output_buf, &output_length); + break; + default: + res = THEMIS_NOT_SUPPORTED; + break; } err: diff --git a/src/wrappers/themis/java/com/cossacklabs/themis/SecureMessage.java b/src/wrappers/themis/java/com/cossacklabs/themis/SecureMessage.java index 277cc987b..c5a81ae6d 100644 --- a/src/wrappers/themis/java/com/cossacklabs/themis/SecureMessage.java +++ b/src/wrappers/themis/java/com/cossacklabs/themis/SecureMessage.java @@ -75,9 +75,13 @@ public SecureMessage(PrivateKey privateKey, PublicKey peerPublicKey) throws Null this.privateKey = privateKey; this.peerPublicKey = peerPublicKey; } - - static native byte[] process(byte[] privateKey, byte[] publicKey, byte[] message, boolean isWrap); - + + static final int SECURE_MESSAGE_ENCRYPT = 1; + static final int SECURE_MESSAGE_DECRYPT = 2; + static final int SECURE_MESSAGE_SIGN = 3; + static final int SECURE_MESSAGE_VERIFY = 4; + static native byte[] process(byte[] privateKey, byte[] publicKey, byte[] message, int action); + /** * Wraps message for peer * @param message to wrap @@ -96,7 +100,7 @@ public byte[] wrap(byte[] message, PublicKey peerPublicKey) throws NullArgumentE throw new NullArgumentException("No message was provided"); } - byte[] wrappedMessage = process(this.privateKey.toByteArray(), peerPublicKey.toByteArray(), message, true); + byte[] wrappedMessage = process(this.privateKey.toByteArray(), peerPublicKey.toByteArray(), message, SECURE_MESSAGE_ENCRYPT); if (null == wrappedMessage) { throw new SecureMessageWrapException(); @@ -134,7 +138,7 @@ public byte[] unwrap(byte[] message, PublicKey peerPublicKey) throws NullArgumen throw new NullArgumentException("No message was provided"); } - byte[] unwrappedMessage = process(this.privateKey.toByteArray(), peerPublicKey.toByteArray(), message, false); + byte[] unwrappedMessage = process(this.privateKey.toByteArray(), peerPublicKey.toByteArray(), message, SECURE_MESSAGE_DECRYPT); if (null == unwrappedMessage) { throw new SecureMessageWrapException(); @@ -171,7 +175,7 @@ public byte[] sign(byte[] message) throws NullArgumentException, SecureMessageWr throw new NullArgumentException("No message was provided"); } - byte[] signedMessage = process(this.privateKey.toByteArray(), null, message, true); + byte[] signedMessage = process(this.privateKey.toByteArray(), null, message, SECURE_MESSAGE_SIGN); if (null == signedMessage) { throw new SecureMessageWrapException(); @@ -198,7 +202,7 @@ public byte[] verify(byte[] message, PublicKey peerPublicKey) throws NullArgumen throw new NullArgumentException("No message was provided"); } - byte[] verifiedMessage = process(null, peerPublicKey.toByteArray(), message, false); + byte[] verifiedMessage = process(null, peerPublicKey.toByteArray(), message, SECURE_MESSAGE_VERIFY); if (null == verifiedMessage) { throw new SecureMessageWrapException(); From ac107b44a828d15930a7e58819b54d37f589dbfd Mon Sep 17 00:00:00 2001 From: ilammy Date: Wed, 20 Feb 2019 22:17:32 +0200 Subject: [PATCH 08/21] Use new Secure Message API: JsThemis Easy as with C++: just use the new functions and we're done. All object methods already have proper names. --- src/wrappers/themis/jsthemis/secure_message.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/wrappers/themis/jsthemis/secure_message.cpp b/src/wrappers/themis/jsthemis/secure_message.cpp index e080dd7a3..c5b1a7624 100644 --- a/src/wrappers/themis/jsthemis/secure_message.cpp +++ b/src/wrappers/themis/jsthemis/secure_message.cpp @@ -104,14 +104,14 @@ namespace jsthemis { return; } size_t encrypted_length=0; - status=themis_secure_message_wrap(&(obj->private_key_)[0], obj->private_key_.size(), &(obj->peer_public_key_)[0], obj->peer_public_key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), NULL, &encrypted_length); + status=themis_secure_message_encrypt(&(obj->private_key_)[0], obj->private_key_.size(), &(obj->peer_public_key_)[0], obj->peer_public_key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), NULL, &encrypted_length); if(status!=THEMIS_BUFFER_TOO_SMALL){ ThrowError("Secure Message failed to encrypt message", status); args.GetReturnValue().SetUndefined(); return; } uint8_t* encrypted_data=(uint8_t*)(malloc(encrypted_length)); - status=themis_secure_message_wrap(&(obj->private_key_)[0], obj->private_key_.size(), &(obj->peer_public_key_)[0], obj->peer_public_key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), encrypted_data, &encrypted_length); + status=themis_secure_message_encrypt(&(obj->private_key_)[0], obj->private_key_.size(), &(obj->peer_public_key_)[0], obj->peer_public_key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), encrypted_data, &encrypted_length); if(status!=THEMIS_SUCCESS){ ThrowError("Secure Message failed to encrypt message", status); free(encrypted_data); @@ -150,14 +150,14 @@ namespace jsthemis { return; } size_t decrypted_length=0; - status=themis_secure_message_unwrap(&(obj->private_key_)[0], obj->private_key_.size(), &(obj->peer_public_key_)[0], obj->peer_public_key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), NULL, &decrypted_length); + status=themis_secure_message_decrypt(&(obj->private_key_)[0], obj->private_key_.size(), &(obj->peer_public_key_)[0], obj->peer_public_key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), NULL, &decrypted_length); if(status!=THEMIS_BUFFER_TOO_SMALL){ ThrowError("Secure Message failed to decrypt message", status); args.GetReturnValue().SetUndefined(); return; } uint8_t* decrypted_data=(uint8_t*)(malloc(decrypted_length)); - status=themis_secure_message_unwrap(&(obj->private_key_)[0], obj->private_key_.size(), &(obj->peer_public_key_)[0], obj->peer_public_key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), decrypted_data, &decrypted_length); + status=themis_secure_message_decrypt(&(obj->private_key_)[0], obj->private_key_.size(), &(obj->peer_public_key_)[0], obj->peer_public_key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), decrypted_data, &decrypted_length); if(status!=THEMIS_SUCCESS){ ThrowError("Secure Message failed to decrypt message", status); free(decrypted_data); @@ -191,14 +191,14 @@ namespace jsthemis { return; } size_t encrypted_length=0; - status=themis_secure_message_wrap(&(obj->private_key_)[0], obj->private_key_.size(), NULL, 0, (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), NULL, &encrypted_length); + status=themis_secure_message_sign(&(obj->private_key_)[0], obj->private_key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), NULL, &encrypted_length); if(status!=THEMIS_BUFFER_TOO_SMALL){ ThrowError("Secure Message failed to sign message", status); args.GetReturnValue().SetUndefined(); return; } uint8_t* encrypted_data=(uint8_t*)(malloc(encrypted_length)); - status=themis_secure_message_wrap(&(obj->private_key_)[0], obj->private_key_.size(), NULL, 0, (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), encrypted_data, &encrypted_length); + status=themis_secure_message_sign(&(obj->private_key_)[0], obj->private_key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), encrypted_data, &encrypted_length); if(status!=THEMIS_SUCCESS){ ThrowError("Secure Message failed to sign message", status); free(encrypted_data); @@ -232,14 +232,14 @@ namespace jsthemis { return; } size_t decrypted_length=0; - status=themis_secure_message_unwrap(NULL, 0, &(obj->peer_public_key_)[0], obj->peer_public_key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), NULL, &decrypted_length); + status=themis_secure_message_verify(&(obj->peer_public_key_)[0], obj->peer_public_key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), NULL, &decrypted_length); if(status!=THEMIS_BUFFER_TOO_SMALL){ ThrowError("Secure Message failed to verify signature", status); args.GetReturnValue().SetUndefined(); return; } uint8_t* decrypted_data=(uint8_t*)(malloc(decrypted_length)); - status=themis_secure_message_unwrap(NULL, 0, &(obj->peer_public_key_)[0], obj->peer_public_key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), decrypted_data, &decrypted_length); + status=themis_secure_message_verify(&(obj->peer_public_key_)[0], obj->peer_public_key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), decrypted_data, &decrypted_length); if(status!=THEMIS_SUCCESS){ ThrowError("Secure Message failed to verify signature", status); free(decrypted_data); From 8306f708fbd742c648592c060a434e99c0f7c4d1 Mon Sep 17 00:00:00 2001 From: ilammy Date: Wed, 20 Feb 2019 22:24:15 +0200 Subject: [PATCH 09/21] Avoid magic constants in tests --- tests/themis/themis_seccure_message.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/themis/themis_seccure_message.c b/tests/themis/themis_seccure_message.c index 88cedbee6..71745c274 100644 --- a/tests/themis/themis_seccure_message.c +++ b/tests/themis/themis_seccure_message.c @@ -21,6 +21,7 @@ /* Fuzz parameters */ #define MAX_MESSAGE_SIZE 2048 +#define MAX_ENCRYPTED_MESSAGE_SIZE (MAX_MESSAGE_SIZE+1024) #define MESSAGES_TO_SEND 3 #define MAX_KEY_SIZE 4096 @@ -394,7 +395,7 @@ static void themis_secure_message_encrypt_decrypt_api_test(void){ size_t public_key_length = sizeof(public_key); uint8_t plaintext[MAX_MESSAGE_SIZE]={0}; - uint8_t encrypted[MAX_MESSAGE_SIZE+1024]={0}; + uint8_t encrypted[MAX_ENCRYPTED_MESSAGE_SIZE]={0}; uint8_t decrypted[MAX_MESSAGE_SIZE]={0}; size_t plaintext_length = sizeof(plaintext); size_t encrypted_length = sizeof(encrypted); @@ -490,7 +491,7 @@ static void themis_secure_message_sign_verify_api_test(void){ size_t public_key_length = sizeof(public_key); uint8_t plaintext[MAX_MESSAGE_SIZE]={0}; - uint8_t signed_msg[MAX_MESSAGE_SIZE+1024]={0}; + uint8_t signed_msg[MAX_ENCRYPTED_MESSAGE_SIZE]={0}; uint8_t verified[MAX_MESSAGE_SIZE]={0}; size_t plaintext_length = sizeof(plaintext); size_t signed_msg_length = sizeof(signed_msg); From 7f4f939d222605ff69852403013ad0fa3fc419fd Mon Sep 17 00:00:00 2001 From: vixentael Date: Thu, 21 Feb 2019 15:30:23 +0200 Subject: [PATCH 10/21] Improve wording in comments and deprecations Co-Authored-By: ilammy --- src/themis/secure_message.c | 3 ++- src/themis/secure_message.h | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/themis/secure_message.c b/src/themis/secure_message.c index 967905fde..6e2bcc132 100644 --- a/src/themis/secure_message.c +++ b/src/themis/secure_message.c @@ -184,7 +184,8 @@ themis_status_t themis_secure_message_verify(const uint8_t* public_key, /* * themis_secure_message_wrap() and themis_secure_message_unwrap() functions * are deprecated in favor of more specific themis_secure_message_encrypt() - * and its friends. + * themis_secure_message_decrypt(), themis_secure_message_sign(), + * themis_secure_message_verify(). * * The old functions combined the interface of the new ones (wrap = encrypt * or sign, unwrap = decrypt or verify). The new functions provide a more diff --git a/src/themis/secure_message.h b/src/themis/secure_message.h index 4f76c493b..820ed88b2 100644 --- a/src/themis/secure_message.h +++ b/src/themis/secure_message.h @@ -169,7 +169,7 @@ themis_status_t themis_secure_message_verify(const uint8_t* public_key, * @return THEMIS_SUCCESS on success or THEMIS_FAIL on failure * @note If wrapped_message==NULL or wrapped_message_length is not enought for wrapped message storage then THEMIS_BUFFER_TOO_SMALL will return and wrapped_message_length will store length of buffer needed for wrapped message store */ -DEPRECATED("use 'themis_secure_message_encrypt' or 'themis_secure_message_sign' instead") +DEPRECATED("use 'themis_secure_message_encrypt' with private and public keys to encrypt message, or 'themis_secure_message_sign' with private key to sign message") themis_status_t themis_secure_message_wrap(const uint8_t* private_key, const size_t private_key_length, const uint8_t* public_key, @@ -192,7 +192,7 @@ themis_status_t themis_secure_message_wrap(const uint8_t* private_key, * @return THEMIS_SUCCESS on success or THEMIS_FAIL on failure * @note If message==NULL or message_length is not enought for plain message storage then THEMIS_BUFFER_TOO_SMALL will return and message_length will store length of buffer needed for plain message store */ -DEPRECATED("use 'themis_secure_message_decrypt' or 'themis_secure_message_verify' instead") +DEPRECATED("use 'themis_secure_message_decrypt' with private and public key to decrypt message or 'themis_secure_message_verify' with public key to verify signed message") themis_status_t themis_secure_message_unwrap(const uint8_t* private_key, const size_t private_key_length, const uint8_t* public_key, From 35e3cde93c8b005170df519017b0329ebea80592 Mon Sep 17 00:00:00 2001 From: ilammy Date: Thu, 21 Feb 2019 11:55:53 +0200 Subject: [PATCH 11/21] Move key kind validation to Themis Core These functions should be useful for Themis users so make them available. We're going to use them internally as well to validate key inputs where appropriate. Note that in Rust we no longer need to link against Soter because the functions are now exported from Themis. Also, drop the long and detalied rant about pointer alignment. It's not very realistic to do anything about it and it does not cause any trouble yet. I guess we can let it pass until we encounter SIGBUS in the wild. Just assume that the pointers are correctly aligned (and try to keep them aligned in Rust). --- src/themis/secure_message.c | 43 +++++++++ src/themis/secure_message.h | 27 ++++++ .../themis/rust/libthemis-sys/build.rs | 7 -- .../themis/rust/libthemis-sys/src/wrapper.c | 96 ------------------- .../themis/rust/libthemis-sys/src/wrapper.h | 24 ----- src/wrappers/themis/rust/src/keys.rs | 2 +- tests/themis/themis_seccure_message.c | 82 ++++++++++++++++ 7 files changed, 153 insertions(+), 128 deletions(-) delete mode 100644 src/wrappers/themis/rust/libthemis-sys/src/wrapper.c diff --git a/src/themis/secure_message.c b/src/themis/secure_message.c index 6e2bcc132..a229250f6 100644 --- a/src/themis/secure_message.c +++ b/src/themis/secure_message.c @@ -14,10 +14,16 @@ * limitations under the License. */ +#include +#include + #include #include #include #include +#include +#include +#include #ifndef THEMIS_RSA_KEY_LENGTH #define THEMIS_RSA_KEY_LENGTH RSA_KEY_LENGTH_2048 @@ -77,6 +83,43 @@ themis_status_t themis_gen_ec_key_pair(uint8_t* private_key, return themis_gen_key_pair(SOTER_SIGN_ecdsa_none_pkcs8, private_key, private_key_length, public_key, public_key_length); } +themis_key_kind_t themis_get_key_kind(const uint8_t* key, size_t length){ + if(!key || (lengthsize)){ + return THEMIS_INVALID_PARAMETER; + } + if(SOTER_SUCCESS!=soter_verify_container_checksum(container)){ + return THEMIS_DATA_CORRUPT; + } + + return THEMIS_SUCCESS; +} + themis_status_t themis_secure_message_encrypt(const uint8_t* private_key, const size_t private_key_length, const uint8_t* public_key, diff --git a/src/themis/secure_message.h b/src/themis/secure_message.h index 820ed88b2..ff86dfcd1 100644 --- a/src/themis/secure_message.h +++ b/src/themis/secure_message.h @@ -64,6 +64,33 @@ themis_status_t themis_gen_ec_key_pair(uint8_t* private_key, uint8_t* public_key, size_t* public_key_length); +enum themis_key_kind +{ + THEMIS_KEY_INVALID, + THEMIS_KEY_RSA_PRIVATE, + THEMIS_KEY_RSA_PUBLIC, + THEMIS_KEY_EC_PRIVATE, + THEMIS_KEY_EC_PUBLIC, +}; + +typedef enum themis_key_kind themis_key_kind_t; + +/** + * @brief get Themis key kind + * @param [in] key key buffer + * @param [in] length length of key + * @return corresponding key kind if the buffer contains a key, or THEMIS_KEY_INVALID otherwise + */ +themis_key_kind_t themis_get_key_kind(const uint8_t* key, size_t length); + +/** + * @brief validate a Themis key + * @param [in] key key buffer to validate + * @param [in] length length of key + * @return THEMIS_SUCCESS if the buffer contains a valid Themis key, or an error code otherwise + */ +themis_status_t themis_is_valid_key(const uint8_t* key, size_t length); + /** * @brief encrypt message to secure message * @param [in] private_key private key diff --git a/src/wrappers/themis/rust/libthemis-sys/build.rs b/src/wrappers/themis/rust/libthemis-sys/build.rs index dce06429e..27114cbad 100644 --- a/src/wrappers/themis/rust/libthemis-sys/build.rs +++ b/src/wrappers/themis/rust/libthemis-sys/build.rs @@ -36,12 +36,6 @@ fn main() { bindings .write_to_file(out_path.join("bindings.rs")) .expect("writing bindings!"); - - cc::Build::new() - .file("src/wrapper.c") - .include("src") - .includes(&themis.include_paths) - .compile("themis_shims"); } /// Embarks on an incredible adventure and returns with a suitable Themis (or dies trying). @@ -51,7 +45,6 @@ fn get_themis() -> Library { let mut pkg_config = pkg_config::Config::new(); pkg_config.env_metadata(true); - pkg_config.arg("libsoter"); // TODO: remove this together with themis_shims #[cfg(feature = "vendored")] pkg_config.statik(true); diff --git a/src/wrappers/themis/rust/libthemis-sys/src/wrapper.c b/src/wrappers/themis/rust/libthemis-sys/src/wrapper.c deleted file mode 100644 index 82e3bd45e..000000000 --- a/src/wrappers/themis/rust/libthemis-sys/src/wrapper.c +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2018 (c) rust-themis developers -// -// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "wrapper.h" - -#include - -#include - -#include -#include -#include - -themis_status_t themis_is_valid_key(const uint8_t *key, size_t length) -{ - // FIXME: do something about alignment mismatch in Themis code - // - // Strictly speaking, this cast is actually an undefined behavior because `key` is not - // guaranteed to be aligned for soter_container_hdr_t. The compiler actually produces - // a warning for the cast. We silence it by using void*, just like the rest of C code - // in Themis. - // - // The behavior is undefined because some architectures (most of the modern ones) allow - // unaligned memory accesses. Other CPUs may issue an exception, which may be handled - // by the OS, which may result in a SIGBUS being sent to the process, which usually - // kills it. Or you may silently read data from a wrong memory address and that's it. - // - // This kinda tends to work because modern hardware and software is forgiving. Themis - // is mostly used and tested on i386 and AMD64 which handle unaligned accesses just fine - // (albeit slower). The next most popular architecture -- ARM -- used to have strict - // alignment requirements, but they are significantly relaxed now on modern processors. - // Furthermore, you need to get lucky with actually using an improperly unaligned pointer. - // That's why you almost never observe any catastrophic results from such unsafe casts. - // - // In rust-themis we do our best to keep the pointers aligned. That's the reason for copying - // the byte slices into a fresh new KeyBytes before passing them to any C function. - // - // However, this is still undefined behavior. I have killed yet another internet kitten - // for the following line. I am sorry and ready to bear my sin. - const soter_container_hdr_t *container = (const void*) key; - - if (!key || (length < sizeof(soter_container_hdr_t))) - { - return THEMIS_INVALID_PARAMETER; - } - - if (length != ntohl(container->size)) - { - return THEMIS_INVALID_PARAMETER; - } - - if (SOTER_SUCCESS != soter_verify_container_checksum(container)) - { - return THEMIS_DATA_CORRUPT; - } - - return THEMIS_SUCCESS; -} - -enum themis_key_kind themis_get_key_kind(const uint8_t *key, size_t length) -{ - if (!key || (length < sizeof(soter_container_hdr_t))) - { - return THEMIS_KEY_INVALID; - } - - if (!memcmp(key, RSA_PRIV_KEY_PREF, strlen(RSA_PRIV_KEY_PREF))) - { - return THEMIS_KEY_RSA_PRIVATE; - } - if (!memcmp(key, RSA_PUB_KEY_PREF, strlen(RSA_PUB_KEY_PREF))) - { - return THEMIS_KEY_RSA_PUBLIC; - } - if (!memcmp(key, EC_PRIV_KEY_PREF, strlen(EC_PRIV_KEY_PREF))) - { - return THEMIS_KEY_EC_PRIVATE; - } - if (!memcmp(key, EC_PUB_KEY_PREF, strlen(EC_PUB_KEY_PREF))) - { - return THEMIS_KEY_EC_PUBLIC; - } - - return THEMIS_KEY_INVALID; -} diff --git a/src/wrappers/themis/rust/libthemis-sys/src/wrapper.h b/src/wrappers/themis/rust/libthemis-sys/src/wrapper.h index 94a7dca36..9a4da33c8 100644 --- a/src/wrappers/themis/rust/libthemis-sys/src/wrapper.h +++ b/src/wrappers/themis/rust/libthemis-sys/src/wrapper.h @@ -15,27 +15,3 @@ //! Wrapper header for bindgen containing all Themis API declarations. #include - -// TODO: move shims into Themis core -// -// These shims are here because they use C macros which are not exported by bindgen. -// Ideally, Themis should provide this functionality, but it's too unstable now -// for inclusion into the core library. - -#include -#include - -enum themis_key_kind -{ - THEMIS_KEY_INVALID, - THEMIS_KEY_RSA_PRIVATE, - THEMIS_KEY_RSA_PUBLIC, - THEMIS_KEY_EC_PRIVATE, - THEMIS_KEY_EC_PUBLIC, -}; - -/// Checks if the buffer contains a valid Themis key. -themis_status_t themis_is_valid_key(const uint8_t *key, size_t length); - -/// Extracts the presumed key kind from the buffer. -enum themis_key_kind themis_get_key_kind(const uint8_t *key, size_t length); diff --git a/src/wrappers/themis/rust/src/keys.rs b/src/wrappers/themis/rust/src/keys.rs index 0b72b9936..1c9369868 100644 --- a/src/wrappers/themis/rust/src/keys.rs +++ b/src/wrappers/themis/rust/src/keys.rs @@ -503,7 +503,7 @@ impl PublicKey { // get_key_kind_trusted() again on the very same byte slice to get the result faster. // // There's also a reason why they receive &KeyBytes, not just &[u8]. This is to maintain correct -// pointer alignment. See "libthemis-sys/src/wrapper.c" for details. +// pointer alignment. fn get_key_kind(key: &KeyBytes) -> Result { is_valid_themis_key(key)?; diff --git a/tests/themis/themis_seccure_message.c b/tests/themis/themis_seccure_message.c index 71745c274..d6ac7fe4b 100644 --- a/tests/themis/themis_seccure_message.c +++ b/tests/themis/themis_seccure_message.c @@ -656,6 +656,85 @@ static void secure_message_old_api_test(void) testsuite_fail_unless((!memcmp(plaintext, decryptext, plaintext_length)), "generic secure message: normal flow 2"); } +static void key_validation_test(void){ + themis_status_t status=THEMIS_FAIL; + + uint8_t ec_private_key[MAX_KEY_SIZE]={0}; + uint8_t ec_public_key[MAX_KEY_SIZE]={0}; + uint8_t rsa_private_key[MAX_KEY_SIZE]={0}; + uint8_t rsa_public_key[MAX_KEY_SIZE]={0}; + size_t ec_private_key_length = sizeof(ec_private_key); + size_t ec_public_key_length = sizeof(ec_public_key); + size_t rsa_private_key_length = sizeof(rsa_private_key); + size_t rsa_public_key_length = sizeof(rsa_public_key); + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_gen_ec_key_pair(NULL, NULL, NULL, NULL), + "themis_gen_ec_key_pair: null pointers"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_gen_rsa_key_pair(NULL, NULL, NULL, NULL), + "themis_gen_rsa_key_pair: null pointers"); + + testsuite_fail_unless(THEMIS_BUFFER_TOO_SMALL == themis_gen_ec_key_pair(NULL, &ec_private_key_length, NULL, &ec_public_key_length), + "themis_gen_ec_key_pair: check length"); + testsuite_fail_unless(THEMIS_BUFFER_TOO_SMALL == themis_gen_rsa_key_pair(NULL, &rsa_private_key_length, NULL, &rsa_public_key_length), + " themis_gen_rsa_key_pair: check length"); + + ec_private_key_length -= 1; + ec_public_key_length -= 1; + rsa_private_key_length -= 1; + rsa_public_key_length -= 1; + testsuite_fail_unless(THEMIS_BUFFER_TOO_SMALL == themis_gen_ec_key_pair(ec_private_key, &ec_private_key_length, ec_public_key, &ec_public_key_length), + "themis_gen_ec_key_pair: small buffer"); + testsuite_fail_unless(THEMIS_BUFFER_TOO_SMALL == themis_gen_rsa_key_pair(rsa_private_key, &rsa_private_key_length, rsa_public_key, &rsa_public_key_length), + "themis_gen_rsa_key_pair: small buffer"); + + status=themis_gen_ec_key_pair(ec_private_key, &ec_private_key_length, ec_public_key, &ec_public_key_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "themis_gen_ec_key_pair failed"); + return; + } + + status=themis_gen_rsa_key_pair(rsa_private_key, &rsa_private_key_length, rsa_public_key, &rsa_public_key_length); + if(status!=THEMIS_SUCCESS){ + testsuite_fail_if(true, "themis_gen_rsa_key_pair failed"); + return; + } + + testsuite_fail_unless(THEMIS_KEY_EC_PRIVATE == themis_get_key_kind(ec_private_key, ec_private_key_length), + "themis_get_key_kind: EC private"); + testsuite_fail_unless(THEMIS_KEY_EC_PUBLIC == themis_get_key_kind(ec_public_key, ec_public_key_length), + "themis_get_key_kind: EC public"); + + testsuite_fail_unless(THEMIS_KEY_RSA_PRIVATE == themis_get_key_kind(rsa_private_key, rsa_private_key_length), + "themis_get_key_kind: RSA private"); + testsuite_fail_unless(THEMIS_KEY_RSA_PUBLIC == themis_get_key_kind(rsa_public_key, rsa_public_key_length), + "themis_get_key_kind: RSA public"); + + testsuite_fail_unless(THEMIS_SUCCESS == themis_is_valid_key(ec_private_key, ec_private_key_length), + "themis_is_valid_key: EC private"); + testsuite_fail_unless(THEMIS_SUCCESS == themis_is_valid_key(ec_public_key, ec_public_key_length), + "themis_is_valid_key: EC public"); + testsuite_fail_unless(THEMIS_SUCCESS == themis_is_valid_key(rsa_private_key, rsa_private_key_length), + "themis_is_valid_key: RSA private"); + testsuite_fail_unless(THEMIS_SUCCESS == themis_is_valid_key(rsa_public_key, rsa_public_key_length), + "themis_is_valid_key: RSA public"); + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_is_valid_key(NULL, 0), + "themis_is_valid_key: invalid arguments"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_is_valid_key(ec_private_key, ec_private_key_length-1), + "themis_is_valid_key: truncated buffer"); + + testsuite_fail_unless(THEMIS_KEY_INVALID == themis_get_key_kind(NULL, 0), + "themis_get_key_kind: invalid arguments"); + testsuite_fail_unless(THEMIS_KEY_INVALID == themis_get_key_kind(ec_private_key, 2), + "themis_get_key_kind: truncated buffer"); + + const char* input = "definitely not a valid key"; + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_is_valid_key((const uint8_t*)input, strlen(input)), + "themis_is_valid_key: garbage input"); + testsuite_fail_unless(THEMIS_KEY_INVALID == themis_get_key_kind((const uint8_t*)input, strlen(input)), + "themis_get_key_kind: garbage input"); +} + void run_secure_message_test(){ testsuite_enter_suite("generic secure message"); testsuite_run_test(themis_secure_message_test); @@ -664,4 +743,7 @@ void run_secure_message_test(){ testsuite_run_test(themis_secure_message_encrypt_decrypt_api_test); testsuite_run_test(themis_secure_message_sign_verify_api_test); testsuite_run_test(secure_message_old_api_test); + + testsuite_enter_suite("key generation and validation"); + testsuite_run_test(key_validation_test); } From c3415111dbf633695fcd3abe000fdbbeddd24f34 Mon Sep 17 00:00:00 2001 From: ilammy Date: Thu, 21 Feb 2019 14:32:38 +0200 Subject: [PATCH 12/21] Key kind checks in Secure Message Add key validation to the new Secure Message API in Themis Core. Now the functions validate the key checksums and make sure that correct private and public keys are used. This should avoid unexpected behavior when a private key is used instead of a public one or vice versa. --- src/themis/secure_message.c | 50 +++++++++++++++++++++++++++ tests/themis/themis_seccure_message.c | 12 +++++++ 2 files changed, 62 insertions(+) diff --git a/src/themis/secure_message.c b/src/themis/secure_message.c index a229250f6..84ab5ca1d 100644 --- a/src/themis/secure_message.c +++ b/src/themis/secure_message.c @@ -120,6 +120,48 @@ themis_status_t themis_is_valid_key(const uint8_t* key, size_t length){ return THEMIS_SUCCESS; } +static bool valid_private_key(const uint8_t* private_key, size_t private_key_length){ + if(themis_is_valid_key(private_key, private_key_length)==THEMIS_SUCCESS){ + themis_key_kind_t private_key_kind=themis_get_key_kind(private_key, private_key_length); + switch(private_key_kind){ + case THEMIS_KEY_EC_PRIVATE: + case THEMIS_KEY_RSA_PRIVATE: + return true; + default: + break; + } + } + return false; +} + +static bool valid_public_key(const uint8_t* public_key, size_t public_key_length){ + if(themis_is_valid_key(public_key, public_key_length)==THEMIS_SUCCESS){ + themis_key_kind_t public_key_kind=themis_get_key_kind(public_key, public_key_length); + switch(public_key_kind){ + case THEMIS_KEY_EC_PUBLIC: + case THEMIS_KEY_RSA_PUBLIC: + return true; + default: + break; + } + } + return false; +} + +static bool matching_key_kinds(const uint8_t* private_key, size_t private_key_length, + const uint8_t* public_key, size_t public_key_length) +{ + themis_key_kind_t private_key_kind=themis_get_key_kind(private_key, private_key_length); + themis_key_kind_t public_key_kind=themis_get_key_kind(public_key, public_key_length); + if(private_key_kind==THEMIS_KEY_EC_PRIVATE && public_key_kind==THEMIS_KEY_EC_PUBLIC){ + return true; + } + if(private_key_kind==THEMIS_KEY_RSA_PRIVATE && public_key_kind==THEMIS_KEY_RSA_PUBLIC){ + return true; + } + return false; +} + themis_status_t themis_secure_message_encrypt(const uint8_t* private_key, const size_t private_key_length, const uint8_t* public_key, @@ -136,6 +178,9 @@ themis_status_t themis_secure_message_encrypt(const uint8_t* private_key, THEMIS_CHECK_PARAM(message!=NULL); THEMIS_CHECK_PARAM(message_length!=0); THEMIS_CHECK_PARAM(encrypted_message_length!=NULL); + THEMIS_CHECK_PARAM(valid_private_key(private_key, private_key_length)); + THEMIS_CHECK_PARAM(valid_public_key(public_key, public_key_length)); + THEMIS_CHECK_PARAM(matching_key_kinds(private_key, private_key_length, public_key, public_key_length)); themis_secure_message_encrypter_t* ctx=NULL; ctx = themis_secure_message_encrypter_init(private_key, private_key_length, public_key, public_key_length); @@ -162,6 +207,9 @@ themis_status_t themis_secure_message_decrypt(const uint8_t* private_key, THEMIS_CHECK_PARAM(encrypted_message!=NULL); THEMIS_CHECK_PARAM(encrypted_message_length!=0); THEMIS_CHECK_PARAM(message_length!=NULL); + THEMIS_CHECK_PARAM(valid_private_key(private_key, private_key_length)); + THEMIS_CHECK_PARAM(valid_public_key(public_key, public_key_length)); + THEMIS_CHECK_PARAM(matching_key_kinds(private_key, private_key_length, public_key, public_key_length)); themis_secure_message_hdr_t* message_hdr=(themis_secure_message_hdr_t*)encrypted_message; THEMIS_CHECK_PARAM(IS_THEMIS_SECURE_MESSAGE_ENCRYPTED(message_hdr->message_type)); @@ -188,6 +236,7 @@ themis_status_t themis_secure_message_sign(const uint8_t* private_key, THEMIS_CHECK_PARAM(message!=NULL); THEMIS_CHECK_PARAM(message_length!=0); THEMIS_CHECK_PARAM(signed_message_length!=NULL); + THEMIS_CHECK_PARAM(valid_private_key(private_key, private_key_length)); themis_secure_message_signer_t* ctx=NULL; ctx = themis_secure_message_signer_init(private_key, private_key_length); @@ -210,6 +259,7 @@ themis_status_t themis_secure_message_verify(const uint8_t* public_key, THEMIS_CHECK_PARAM(signed_message!=NULL); THEMIS_CHECK_PARAM(signed_message_length!=0); THEMIS_CHECK_PARAM(message_length!=NULL); + THEMIS_CHECK_PARAM(valid_public_key(public_key, public_key_length)); themis_secure_message_hdr_t* message_hdr=(themis_secure_message_hdr_t*)signed_message; THEMIS_CHECK_PARAM(IS_THEMIS_SECURE_MESSAGE_SIGNED(message_hdr->message_type)); diff --git a/tests/themis/themis_seccure_message.c b/tests/themis/themis_seccure_message.c index d6ac7fe4b..087c00d29 100644 --- a/tests/themis/themis_seccure_message.c +++ b/tests/themis/themis_seccure_message.c @@ -427,6 +427,9 @@ static void themis_secure_message_encrypt_decrypt_api_test(void){ testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_encrypt(private_key, private_key_length, public_key, public_key_length-1, plaintext, plaintext_length, encrypted, &encrypted_length), "themis_secure_message_encrypt: public key is invalid"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_encrypt(public_key, public_key_length, private_key, private_key_length, plaintext, plaintext_length, encrypted, &encrypted_length), + "themis_secure_message_encrypt: misplaced keys"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_encrypt(private_key, private_key_length, public_key, public_key_length, NULL, plaintext_length, encrypted, &encrypted_length), "themis_secure_message_encrypt: plaintext is NULL"); testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_encrypt(private_key, private_key_length, public_key, public_key_length, plaintext, 0, encrypted, &encrypted_length), @@ -461,6 +464,9 @@ static void themis_secure_message_encrypt_decrypt_api_test(void){ testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_decrypt(private_key, private_key_length, public_key, public_key_length-1, encrypted, encrypted_length, decrypted, &decrypted_length), "themis_secure_message_decrypt: public key is invalid"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_decrypt(public_key, public_key_length, private_key, private_key_length, encrypted, encrypted_length, decrypted, &decrypted_length), + "themis_secure_message_decrypt: misplaced keys"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_decrypt(private_key, private_key_length, public_key, public_key_length, NULL, encrypted_length, decrypted, &decrypted_length), "themis_secure_message_decrypt: encrypted is NULL"); testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_decrypt(private_key, private_key_length, public_key, public_key_length, encrypted, 0, decrypted, &decrypted_length), @@ -516,6 +522,9 @@ static void themis_secure_message_sign_verify_api_test(void){ testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_sign(private_key, private_key_length-1, plaintext, plaintext_length, signed_msg, &signed_msg_length), "themis_secure_message_sign: private key is invalid"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_sign(public_key, public_key_length, plaintext, plaintext_length, signed_msg, &signed_msg_length), + "themis_secure_message_sign: using public key"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_sign(private_key, private_key_length, NULL, plaintext_length, signed_msg, &signed_msg_length), "themis_secure_message_sign: plaintext is NULL"); testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_sign(private_key, private_key_length, plaintext, 0, signed_msg, &signed_msg_length), @@ -543,6 +552,9 @@ static void themis_secure_message_sign_verify_api_test(void){ testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_verify(public_key, public_key_length-1, signed_msg, signed_msg_length, verified, &verified_length), "themis_secure_message_verify: public key is invalid"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_verify(private_key, private_key_length, signed_msg, signed_msg_length, verified, &verified_length), + "themis_secure_message_verify: using private key"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_verify(public_key, public_key_length, NULL, signed_msg_length, verified, &verified_length), "themis_secure_message_verify: signed_msg is NULL"); testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_secure_message_verify(public_key, public_key_length, signed_msg, 0, verified, &verified_length), From 0d3edf5467f18f4e57d65772f1f279ff44cb3cb6 Mon Sep 17 00:00:00 2001 From: ilammy Date: Thu, 21 Feb 2019 15:03:17 +0200 Subject: [PATCH 13/21] Key kind checks in Secure Message: ThemisPP Add validation and human-friendly error messages to ThemisPP if the user passes incorrect keys to constructor. --- .../themis/themispp/secure_message.hpp | 57 +++++++++++++++++-- tests/themispp/secure_message_test.hpp | 15 +++++ 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/wrappers/themis/themispp/secure_message.hpp b/src/wrappers/themis/themispp/secure_message.hpp index 7817de240..b8e97e9d0 100644 --- a/src/wrappers/themis/themispp/secure_message.hpp +++ b/src/wrappers/themis/themispp/secure_message.hpp @@ -30,19 +30,31 @@ namespace themispp { secure_message_t(data_t::const_iterator private_key_begin, data_t::const_iterator private_key_end, data_t::const_iterator peer_public_key_begin, data_t::const_iterator peer_public_key_end): private_key_(private_key_begin, private_key_end), - peer_public_key_(peer_public_key_begin, peer_public_key_end){} + peer_public_key_(peer_public_key_begin, peer_public_key_end) + { + validate_keys(); + } secure_message_t(const data_t& private_key, data_t::const_iterator peer_public_key_begin, data_t::const_iterator peer_public_key_end): private_key_(private_key.begin(), private_key.end()), - peer_public_key_(peer_public_key_begin, peer_public_key_end){} + peer_public_key_(peer_public_key_begin, peer_public_key_end) + { + validate_keys(); + } secure_message_t(data_t::const_iterator private_key_begin, data_t::const_iterator private_key_end, const data_t& peer_public_key): private_key_(private_key_begin, private_key_end), - peer_public_key_(peer_public_key.begin(), peer_public_key.end()){} - + peer_public_key_(peer_public_key.begin(), peer_public_key.end()) + { + validate_keys(); + } + secure_message_t(const data_t& private_key, const data_t& peer_public_key): private_key_(private_key.begin(), private_key.end()), - peer_public_key_(peer_public_key.begin(), peer_public_key.end()){} + peer_public_key_(peer_public_key.begin(), peer_public_key.end()) + { + validate_keys(); + } virtual ~secure_message_t(){} @@ -159,6 +171,41 @@ namespace themispp { private: secure_message_t(const secure_message_t&); secure_message_t& operator=(const secure_message_t&); + + void validate_keys(){ + if(!private_key_.empty()){ + if(THEMIS_SUCCESS!=themis_is_valid_key(&private_key_[0], private_key_.size())){ + throw themispp::exception_t("Secure Message: invalid private key"); + } + themis_key_kind_t kind=themis_get_key_kind(&private_key_[0], private_key_.size()); + switch(kind){ + case THEMIS_KEY_EC_PRIVATE: + case THEMIS_KEY_RSA_PRIVATE: + break; + case THEMIS_KEY_EC_PUBLIC: + case THEMIS_KEY_RSA_PUBLIC: + throw themispp::exception_t("Secure Message: using public key instead of private key"); + default: + throw themispp::exception_t("Secure Message: private key not supported"); + } + } + if(!peer_public_key_.empty()){ + if(THEMIS_SUCCESS!=themis_is_valid_key(&peer_public_key_[0], peer_public_key_.size())){ + throw themispp::exception_t("Secure Message: invalid public key"); + } + themis_key_kind_t kind=themis_get_key_kind(&peer_public_key_[0], peer_public_key_.size()); + switch(kind){ + case THEMIS_KEY_EC_PUBLIC: + case THEMIS_KEY_RSA_PUBLIC: + break; + case THEMIS_KEY_EC_PRIVATE: + case THEMIS_KEY_RSA_PRIVATE: + throw themispp::exception_t("Secure Message: using private key instead of public key"); + default: + throw themispp::exception_t("Secure Message: public key not supported"); + } + } + } }; } //end themispp diff --git a/tests/themispp/secure_message_test.hpp b/tests/themispp/secure_message_test.hpp index f53981391..fd9b67760 100644 --- a/tests/themispp/secure_message_test.hpp +++ b/tests/themispp/secure_message_test.hpp @@ -113,12 +113,27 @@ namespace themispp { return secure_message_sign_verify_test(); } + int secure_message_test_key_mismatch(){ + themispp::secure_key_pair_generator_t gen_ec; + std::vector private_key(gen_ec.get_priv()); + std::vector public_key(gen_ec.get_pub()); + try{ + themispp::secure_message_t a(public_key, private_key); + sput_fail_unless(false, "mismatched keys (no failure)", __LINE__); + } + catch(const themispp::exception_t& e){ + sput_fail_unless(e.status()==THEMIS_INVALID_PARAMETER, "mismatched keys", __LINE__); + } + return 0; + } + void run_secure_message_test() { sput_enter_suite("ThemisPP secure message test"); sput_run_test(secure_message_test_rsa, "secure_message_test_rsa", __FILE__); sput_run_test(secure_message_test_ec, "secure_message_test_ec", __FILE__); sput_run_test(secure_message_sign_verify_test_rsa, "secure_message_sign_verify_test_rsa", __FILE__); sput_run_test(secure_message_sign_verify_test_ec, "secure_message_sign_verify_test_ec", __FILE__); + sput_run_test(secure_message_test_key_mismatch, "secure_message_test_key_mismatch", __FILE__); } } } From 707520933fff2a41764966f417519d83ecd6e5ea Mon Sep 17 00:00:00 2001 From: ilammy Date: Thu, 21 Feb 2019 15:12:43 +0200 Subject: [PATCH 14/21] Key kind checks in Secure Message: JsThemis Add key validation to JavaScript wrapper as well. This is similar to ThemisPP but we use NAN's error reporting instead of C++ exceptions. --- .../themis/jsthemis/secure_message.cpp | 46 +++++++++++++++++++ .../themis/jsthemis/secure_message.hpp | 2 + tests/jsthemis/test.js | 3 ++ 3 files changed, 51 insertions(+) diff --git a/src/wrappers/themis/jsthemis/secure_message.cpp b/src/wrappers/themis/jsthemis/secure_message.cpp index c5b1a7624..ac25afe9c 100644 --- a/src/wrappers/themis/jsthemis/secure_message.cpp +++ b/src/wrappers/themis/jsthemis/secure_message.cpp @@ -63,6 +63,10 @@ namespace jsthemis { } std::vector private_key((uint8_t*)(node::Buffer::Data(args[0])), (uint8_t*)(node::Buffer::Data(args[0])+node::Buffer::Length(args[0]))); std::vector public_key((uint8_t*)(node::Buffer::Data(args[1])), (uint8_t*)(node::Buffer::Data(args[1])+node::Buffer::Length(args[1]))); + if(!ValidateKeys(private_key, public_key)){ + args.GetReturnValue().SetUndefined(); + return; + } SecureMessage* obj = new SecureMessage(private_key, public_key); obj->Wrap(args.This()); args.GetReturnValue().Set(args.This()); @@ -75,6 +79,48 @@ namespace jsthemis { } } + bool SecureMessage::ValidateKeys(const std::vector& private_key, const std::vector& public_key) { + if(!private_key.empty()){ + if(THEMIS_SUCCESS!=themis_is_valid_key(&private_key[0], private_key.size())){ + ThrowParameterError("Secure Message constructor", "invalid private key"); + return false; + } + themis_key_kind_t kind=themis_get_key_kind(&private_key[0], private_key.size()); + switch(kind){ + case THEMIS_KEY_EC_PRIVATE: + case THEMIS_KEY_RSA_PRIVATE: + break; + case THEMIS_KEY_EC_PUBLIC: + case THEMIS_KEY_RSA_PUBLIC: + ThrowParameterError("Secure Message constructor", "using public key instead of private key"); + return false; + default: + ThrowParameterError("Secure Message constructor", "private key not supported"); + return false; + } + } + if(!public_key.empty()){ + if(THEMIS_SUCCESS!=themis_is_valid_key(&public_key[0], public_key.size())){ + ThrowParameterError("Secure Message constructor", "invalid public key"); + return false; + } + themis_key_kind_t kind=themis_get_key_kind(&public_key[0], public_key.size()); + switch(kind){ + case THEMIS_KEY_EC_PUBLIC: + case THEMIS_KEY_RSA_PUBLIC: + break; + case THEMIS_KEY_EC_PRIVATE: + case THEMIS_KEY_RSA_PRIVATE: + ThrowParameterError("Secure Message constructor", "using private key instead of public key"); + return false; + default: + ThrowParameterError("Secure Message constructor", "public key not supported"); + return false; + } + } + return true; + } + void SecureMessage::encrypt(const Nan::FunctionCallbackInfo& args) { themis_status_t status = THEMIS_FAIL; SecureMessage* obj = Nan::ObjectWrap::Unwrap(args.This()); diff --git a/src/wrappers/themis/jsthemis/secure_message.hpp b/src/wrappers/themis/jsthemis/secure_message.hpp index 12a23df02..36b5c3e49 100644 --- a/src/wrappers/themis/jsthemis/secure_message.hpp +++ b/src/wrappers/themis/jsthemis/secure_message.hpp @@ -38,6 +38,8 @@ namespace jsthemis{ static Nan::Persistent constructor; + static bool ValidateKeys(const std::vector& private_key, const std::vector& public_key); + std::vector private_key_; std::vector peer_public_key_; }; diff --git a/tests/jsthemis/test.js b/tests/jsthemis/test.js index a45133a1c..19857c699 100644 --- a/tests/jsthemis/test.js +++ b/tests/jsthemis/test.js @@ -38,6 +38,9 @@ describe("jsthemis", function(){ assert.throws(function(){empty_secure_message.sign(message);}, expect_code(addon.INVALID_PARAMETER)); assert.throws(function(){empty_secure_message.verify(signed_message);}, expect_code(addon.INVALID_PARAMETER)); }) + it("mismatched keys", function(){ + assert.throws(function(){new addon.SecureMessage(keypair.public(), keypair.private())}, expect_code(addon.INVALID_PARAMETER)); + }) }) }) From 3549f9de1ed55bbb0433711115237146762248d6 Mon Sep 17 00:00:00 2001 From: ilammy Date: Thu, 21 Feb 2019 17:28:28 +0200 Subject: [PATCH 15/21] Move keygen and validation into a new file All language wrappers actually have a separate module named "keygen" or something like that. However, the core library keeps key generation routines in secure_message.c which is weird. Move key generation and validation into a separate source file at least. C does not have modules per se, but this makes much easier to locate keygen functions. --- src/themis/secure_keygen.c | 121 ++++++++++++++++++++++++++++++++++++ src/themis/secure_keygen.h | 102 ++++++++++++++++++++++++++++++ src/themis/secure_message.c | 102 +----------------------------- src/themis/secure_message.h | 55 ---------------- src/themis/themis.h | 1 + 5 files changed, 225 insertions(+), 156 deletions(-) create mode 100644 src/themis/secure_keygen.c create mode 100644 src/themis/secure_keygen.h diff --git a/src/themis/secure_keygen.c b/src/themis/secure_keygen.c new file mode 100644 index 000000000..4cb70aded --- /dev/null +++ b/src/themis/secure_keygen.c @@ -0,0 +1,121 @@ +/* +* Copyright (c) 2019 Cossack Labs Limited +* +* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "secure_keygen.h" + +#include +#include + +#include +#include +#include +#include +#include + +#ifndef THEMIS_RSA_KEY_LENGTH +#define THEMIS_RSA_KEY_LENGTH RSA_KEY_LENGTH_2048 +#endif +themis_status_t themis_gen_key_pair(soter_sign_alg_t alg, + uint8_t* private_key, + size_t* private_key_length, + uint8_t* public_key, + size_t* public_key_length) +{ + soter_sign_ctx_t* ctx=soter_sign_create(alg,NULL,0,NULL,0); + THEMIS_CHECK(ctx!=NULL); + soter_status_t res=soter_sign_export_key(ctx, private_key, private_key_length, true); + if(res!=THEMIS_SUCCESS && res!=THEMIS_BUFFER_TOO_SMALL){ + soter_sign_destroy(ctx); + return res; + } + soter_status_t res2=soter_sign_export_key(ctx, public_key, public_key_length, false); + if(res2!=THEMIS_SUCCESS && res2!=THEMIS_BUFFER_TOO_SMALL){ + soter_sign_destroy(ctx); + return res; + } + soter_sign_destroy(ctx); + if(res==THEMIS_BUFFER_TOO_SMALL || res2==THEMIS_BUFFER_TOO_SMALL){ + return THEMIS_BUFFER_TOO_SMALL; + } + return THEMIS_SUCCESS; +} + +themis_status_t themis_gen_rsa_key_pair(uint8_t* private_key, + size_t* private_key_length, + uint8_t* public_key, + size_t* public_key_length){ + soter_rsa_key_pair_gen_t* key_pair_ctx=soter_rsa_key_pair_gen_create(THEMIS_RSA_KEY_LENGTH); + THEMIS_CHECK(key_pair_ctx!=NULL); + soter_status_t res=soter_rsa_key_pair_gen_export_key(key_pair_ctx, private_key, private_key_length, true); + if(res!=THEMIS_SUCCESS && res != THEMIS_BUFFER_TOO_SMALL){ + soter_rsa_key_pair_gen_destroy(key_pair_ctx); + return res; + } + soter_status_t res2=soter_rsa_key_pair_gen_export_key(key_pair_ctx, public_key, public_key_length, false); + if(res2!=THEMIS_SUCCESS && res2!=THEMIS_BUFFER_TOO_SMALL){ + soter_rsa_key_pair_gen_destroy(key_pair_ctx); + return res2; + } + soter_rsa_key_pair_gen_destroy(key_pair_ctx); + if(res==THEMIS_BUFFER_TOO_SMALL || res2==THEMIS_BUFFER_TOO_SMALL){ + return THEMIS_BUFFER_TOO_SMALL; + } + return THEMIS_SUCCESS; +} + +themis_status_t themis_gen_ec_key_pair(uint8_t* private_key, + size_t* private_key_length, + uint8_t* public_key, + size_t* public_key_length){ + return themis_gen_key_pair(SOTER_SIGN_ecdsa_none_pkcs8, private_key, private_key_length, public_key, public_key_length); +} + +themis_key_kind_t themis_get_key_kind(const uint8_t* key, size_t length){ + if(!key || (lengthsize)){ + return THEMIS_INVALID_PARAMETER; + } + if(SOTER_SUCCESS!=soter_verify_container_checksum(container)){ + return THEMIS_DATA_CORRUPT; + } + + return THEMIS_SUCCESS; +} diff --git a/src/themis/secure_keygen.h b/src/themis/secure_keygen.h new file mode 100644 index 000000000..fc07fc6b5 --- /dev/null +++ b/src/themis/secure_keygen.h @@ -0,0 +1,102 @@ +/* +* Copyright (c) 2019 Cossack Labs Limited +* +* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/** + * @file secure_keygen.h + * @brief secure key generation + */ + +#ifndef _THEMIS_SECURE_KEYGEN_H_ +#define _THEMIS_SECURE_KEYGEN_H_ + +#include + +#ifdef __cplusplus +extern "C"{ +#endif + +/** + * @addtogroup THEMIS + * @{ + * @defgroup THEMIS_KEYS secure key generation + * @brief securely generating random key pairs + * @{ + */ + +/** + * @brief generate RSA key pair + * @param [out] private_key buffer for private key to store. May be set to NULL for private key length determination + * @param [in, out] private_key_length length of private_key + * @param [out] public_key buffer for public key to store. May be set to NULL for public key length determination + * @param [in, out] public_key_length length of public key + * @return THEMIS_SUCCESS on success or THEMIS_FAIL on failure + * @note If private_key==NULL or public_key==NULL or private_key_length is not enought for private key storage or public_key_length is not enought for public key storage then THEMIS_BUFFER_TOO_SMALL will return and private_key_length and public_key_length will store lengths of buffers needed for private key and public key store respectively + */ +themis_status_t themis_gen_rsa_key_pair(uint8_t* private_key, + size_t* private_key_length, + uint8_t* public_key, + size_t* public_key_length); + +/** + * @brief generate EC key pair + * @param [out] private_key buffer for private key to store. May be set to NULL for private key length determination + * @param [in, out] private_key_length length of private_key + * @param [out] public_key buffer for public key to store. May be set to NULL for public key length determination + * @param [in, out] public_key_length length of public key + * @return THEMIS_SUCCESS on success or THEMIS_FAIL on failure + * @note If private_key==NULL or public_key==NULL or private_key_length is not enought for private key storage or public_key_length is not enought for public key storage then THEMIS_BUFFER_TOO_SMALL will return and private_key_length and public_key_length will store lengths of buffers needed for private key and public key store respectively + */ +themis_status_t themis_gen_ec_key_pair(uint8_t* private_key, + size_t* private_key_length, + uint8_t* public_key, + size_t* public_key_length); + +enum themis_key_kind +{ + THEMIS_KEY_INVALID, + THEMIS_KEY_RSA_PRIVATE, + THEMIS_KEY_RSA_PUBLIC, + THEMIS_KEY_EC_PRIVATE, + THEMIS_KEY_EC_PUBLIC, +}; + +typedef enum themis_key_kind themis_key_kind_t; + +/** + * @brief get Themis key kind + * @param [in] key key buffer + * @param [in] length length of key + * @return corresponding key kind if the buffer contains a key, or THEMIS_KEY_INVALID otherwise + */ +themis_key_kind_t themis_get_key_kind(const uint8_t* key, size_t length); + +/** + * @brief validate a Themis key + * @param [in] key key buffer to validate + * @param [in] length length of key + * @return THEMIS_SUCCESS if the buffer contains a valid Themis key, or an error code otherwise + */ +themis_status_t themis_is_valid_key(const uint8_t* key, size_t length); + +/** @} */ +/** @} */ + +#ifdef __cplusplus +} +#endif + + +#endif /* _THEMIS_SECURE_KEYGEN_H_ */ diff --git a/src/themis/secure_message.c b/src/themis/secure_message.c index 84ab5ca1d..da7f00f49 100644 --- a/src/themis/secure_message.c +++ b/src/themis/secure_message.c @@ -14,111 +14,11 @@ * limitations under the License. */ -#include -#include +#include "secure_message.h" #include #include #include -#include -#include -#include -#include - -#ifndef THEMIS_RSA_KEY_LENGTH -#define THEMIS_RSA_KEY_LENGTH RSA_KEY_LENGTH_2048 -#endif -themis_status_t themis_gen_key_pair(soter_sign_alg_t alg, - uint8_t* private_key, - size_t* private_key_length, - uint8_t* public_key, - size_t* public_key_length) -{ - soter_sign_ctx_t* ctx=soter_sign_create(alg,NULL,0,NULL,0); - THEMIS_CHECK(ctx!=NULL); - soter_status_t res=soter_sign_export_key(ctx, private_key, private_key_length, true); - if(res!=THEMIS_SUCCESS && res!=THEMIS_BUFFER_TOO_SMALL){ - soter_sign_destroy(ctx); - return res; - } - soter_status_t res2=soter_sign_export_key(ctx, public_key, public_key_length, false); - if(res2!=THEMIS_SUCCESS && res2!=THEMIS_BUFFER_TOO_SMALL){ - soter_sign_destroy(ctx); - return res; - } - soter_sign_destroy(ctx); - if(res==THEMIS_BUFFER_TOO_SMALL || res2==THEMIS_BUFFER_TOO_SMALL){ - return THEMIS_BUFFER_TOO_SMALL; - } - return THEMIS_SUCCESS; -} - -themis_status_t themis_gen_rsa_key_pair(uint8_t* private_key, - size_t* private_key_length, - uint8_t* public_key, - size_t* public_key_length){ - soter_rsa_key_pair_gen_t* key_pair_ctx=soter_rsa_key_pair_gen_create(THEMIS_RSA_KEY_LENGTH); - THEMIS_CHECK(key_pair_ctx!=NULL); - soter_status_t res=soter_rsa_key_pair_gen_export_key(key_pair_ctx, private_key, private_key_length, true); - if(res!=THEMIS_SUCCESS && res != THEMIS_BUFFER_TOO_SMALL){ - soter_rsa_key_pair_gen_destroy(key_pair_ctx); - return res; - } - soter_status_t res2=soter_rsa_key_pair_gen_export_key(key_pair_ctx, public_key, public_key_length, false); - if(res2!=THEMIS_SUCCESS && res2!=THEMIS_BUFFER_TOO_SMALL){ - soter_rsa_key_pair_gen_destroy(key_pair_ctx); - return res2; - } - soter_rsa_key_pair_gen_destroy(key_pair_ctx); - if(res==THEMIS_BUFFER_TOO_SMALL || res2==THEMIS_BUFFER_TOO_SMALL){ - return THEMIS_BUFFER_TOO_SMALL; - } - return THEMIS_SUCCESS; -} - -themis_status_t themis_gen_ec_key_pair(uint8_t* private_key, - size_t* private_key_length, - uint8_t* public_key, - size_t* public_key_length){ - return themis_gen_key_pair(SOTER_SIGN_ecdsa_none_pkcs8, private_key, private_key_length, public_key, public_key_length); -} - -themis_key_kind_t themis_get_key_kind(const uint8_t* key, size_t length){ - if(!key || (lengthsize)){ - return THEMIS_INVALID_PARAMETER; - } - if(SOTER_SUCCESS!=soter_verify_container_checksum(container)){ - return THEMIS_DATA_CORRUPT; - } - - return THEMIS_SUCCESS; -} static bool valid_private_key(const uint8_t* private_key, size_t private_key_length){ if(themis_is_valid_key(private_key, private_key_length)==THEMIS_SUCCESS){ diff --git a/src/themis/secure_message.h b/src/themis/secure_message.h index ff86dfcd1..b1d11c788 100644 --- a/src/themis/secure_message.h +++ b/src/themis/secure_message.h @@ -36,61 +36,6 @@ extern "C"{ * @{ */ -/** - * @brief generate RSA key pair - * @param [out] private_key buffer for private key to store. May be set to NULL for private key length determination - * @param [in, out] private_key_length length of private_key - * @param [out] public_key buffer for public key to store. May be set to NULL for public key length determination - * @param [in, out] public_key_length length of public key - * @return THEMIS_SUCCESS on success or THEMIS_FAIL on failure - * @note If private_key==NULL or public_key==NULL or private_key_length is not enought for private key storage or public_key_length is not enought for public key storage then THEMIS_BUFFER_TOO_SMALL will return and private_key_length and public_key_length will store lengths of buffers needed for private key and public key store respectively - */ -themis_status_t themis_gen_rsa_key_pair(uint8_t* private_key, - size_t* private_key_length, - uint8_t* public_key, - size_t* public_key_length); - -/** - * @brief generate EC key pair - * @param [out] private_key buffer for private key to store. May be set to NULL for private key length determination - * @param [in, out] private_key_length length of private_key - * @param [out] public_key buffer for public key to store. May be set to NULL for public key length determination - * @param [in, out] public_key_length length of public key - * @return THEMIS_SUCCESS on success or THEMIS_FAIL on failure - * @note If private_key==NULL or public_key==NULL or private_key_length is not enought for private key storage or public_key_length is not enought for public key storage then THEMIS_BUFFER_TOO_SMALL will return and private_key_length and public_key_length will store lengths of buffers needed for private key and public key store respectively - */ -themis_status_t themis_gen_ec_key_pair(uint8_t* private_key, - size_t* private_key_length, - uint8_t* public_key, - size_t* public_key_length); - -enum themis_key_kind -{ - THEMIS_KEY_INVALID, - THEMIS_KEY_RSA_PRIVATE, - THEMIS_KEY_RSA_PUBLIC, - THEMIS_KEY_EC_PRIVATE, - THEMIS_KEY_EC_PUBLIC, -}; - -typedef enum themis_key_kind themis_key_kind_t; - -/** - * @brief get Themis key kind - * @param [in] key key buffer - * @param [in] length length of key - * @return corresponding key kind if the buffer contains a key, or THEMIS_KEY_INVALID otherwise - */ -themis_key_kind_t themis_get_key_kind(const uint8_t* key, size_t length); - -/** - * @brief validate a Themis key - * @param [in] key key buffer to validate - * @param [in] length length of key - * @return THEMIS_SUCCESS if the buffer contains a valid Themis key, or an error code otherwise - */ -themis_status_t themis_is_valid_key(const uint8_t* key, size_t length); - /** * @brief encrypt message to secure message * @param [in] private_key private key diff --git a/src/themis/themis.h b/src/themis/themis.h index 2d47a6bef..c122eac53 100644 --- a/src/themis/themis.h +++ b/src/themis/themis.h @@ -33,6 +33,7 @@ #define THEMIS_VERSION_TEXT "themis 0.9: " #include +#include #include #include #include From fdc4bc61c912eca6e74a7ed31b294a8f24db70f8 Mon Sep 17 00:00:00 2001 From: ilammy Date: Thu, 21 Feb 2019 17:49:43 +0200 Subject: [PATCH 16/21] Move key checking utilities to secure_keygen.hpp (ThemisPP) Put reusable code there and keep business logic of key validation in Secure Message. Later we will be able to reuse the key validation routines for Secure Session. --- .../themis/themispp/secure_keygen.hpp | 37 +++++++++++++++++++ .../themis/themispp/secure_message.hpp | 25 +++---------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/wrappers/themis/themispp/secure_keygen.hpp b/src/wrappers/themis/themispp/secure_keygen.hpp index c93ea8580..e6481e356 100644 --- a/src/wrappers/themis/themispp/secure_keygen.hpp +++ b/src/wrappers/themis/themispp/secure_keygen.hpp @@ -70,6 +70,43 @@ namespace themispp{ std::vector public_key; }; + inline bool is_valid_key(const std::vector& key){ + if(!key.empty()){ + if(THEMIS_SUCCESS==themis_is_valid_key(&key[0], key.size())){ + return true; + } + } + return false; + } + + inline bool is_private_key(const std::vector& key){ + if(!key.empty()){ + themis_key_kind_t kind=themis_get_key_kind(&key[0], key.size()); + switch(kind){ + case THEMIS_KEY_EC_PRIVATE: + case THEMIS_KEY_RSA_PRIVATE: + return true; + default: + break; + } + } + return false; + } + + inline bool is_public_key(const std::vector& key){ + if(!key.empty()){ + themis_key_kind_t kind=themis_get_key_kind(&key[0], key.size()); + switch(kind){ + case THEMIS_KEY_EC_PUBLIC: + case THEMIS_KEY_RSA_PUBLIC: + return true; + default: + break; + } + } + return false; + } + }// ns themis #endif diff --git a/src/wrappers/themis/themispp/secure_message.hpp b/src/wrappers/themis/themispp/secure_message.hpp index b8e97e9d0..986fe90fc 100644 --- a/src/wrappers/themis/themispp/secure_message.hpp +++ b/src/wrappers/themis/themispp/secure_message.hpp @@ -18,6 +18,7 @@ #define THEMISPP_SECURE_MESSAGE_HPP_ #include "exception.hpp" +#include "secure_keygen.hpp" #include #include @@ -174,35 +175,19 @@ namespace themispp { void validate_keys(){ if(!private_key_.empty()){ - if(THEMIS_SUCCESS!=themis_is_valid_key(&private_key_[0], private_key_.size())){ + if(!is_valid_key(private_key_)){ throw themispp::exception_t("Secure Message: invalid private key"); } - themis_key_kind_t kind=themis_get_key_kind(&private_key_[0], private_key_.size()); - switch(kind){ - case THEMIS_KEY_EC_PRIVATE: - case THEMIS_KEY_RSA_PRIVATE: - break; - case THEMIS_KEY_EC_PUBLIC: - case THEMIS_KEY_RSA_PUBLIC: + if(!is_private_key(private_key_)){ throw themispp::exception_t("Secure Message: using public key instead of private key"); - default: - throw themispp::exception_t("Secure Message: private key not supported"); } } if(!peer_public_key_.empty()){ - if(THEMIS_SUCCESS!=themis_is_valid_key(&peer_public_key_[0], peer_public_key_.size())){ + if(!is_valid_key(peer_public_key_)){ throw themispp::exception_t("Secure Message: invalid public key"); } - themis_key_kind_t kind=themis_get_key_kind(&peer_public_key_[0], peer_public_key_.size()); - switch(kind){ - case THEMIS_KEY_EC_PUBLIC: - case THEMIS_KEY_RSA_PUBLIC: - break; - case THEMIS_KEY_EC_PRIVATE: - case THEMIS_KEY_RSA_PRIVATE: + if(!is_public_key(peer_public_key_)){ throw themispp::exception_t("Secure Message: using private key instead of public key"); - default: - throw themispp::exception_t("Secure Message: public key not supported"); } } } From 7e75bb78f9e612dd55470957470933c0385ab7ee Mon Sep 17 00:00:00 2001 From: ilammy Date: Thu, 21 Feb 2019 17:59:16 +0200 Subject: [PATCH 17/21] Move key checking utilities to secure_keygen.hpp (JsThemis) Put reusable code there and keep business logic of key validation in Secure Message. Later we will be able to reuse the key validation routines for Secure Session. --- .../themis/jsthemis/secure_keygen.cpp | 40 ++++++++++++++++++- .../themis/jsthemis/secure_keygen.hpp | 4 ++ .../themis/jsthemis/secure_message.cpp | 27 +++---------- 3 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/wrappers/themis/jsthemis/secure_keygen.cpp b/src/wrappers/themis/jsthemis/secure_keygen.cpp index 492a0537e..68cbdfb44 100644 --- a/src/wrappers/themis/jsthemis/secure_keygen.cpp +++ b/src/wrappers/themis/jsthemis/secure_keygen.cpp @@ -105,5 +105,43 @@ namespace jsthemis { void KeyPair::public_key(const Nan::FunctionCallbackInfo& args){ KeyPair* obj = Nan::ObjectWrap::Unwrap(args.This()); args.GetReturnValue().Set(Nan::CopyBuffer((char*)(&(obj->public_key_)[0]), obj->public_key_.size()).ToLocalChecked()); - } + } + + bool IsValidKey(const std::vector& key){ + if(!key.empty()){ + if(THEMIS_SUCCESS==themis_is_valid_key(&key[0], key.size())){ + return true; + } + } + return false; + } + + bool IsPrivateKey(const std::vector& key){ + if(!key.empty()){ + themis_key_kind_t kind=themis_get_key_kind(&key[0], key.size()); + switch(kind){ + case THEMIS_KEY_EC_PRIVATE: + case THEMIS_KEY_RSA_PRIVATE: + return true; + default: + break; + } + } + return false; + } + + bool IsPublicKey(const std::vector& key){ + if(!key.empty()){ + themis_key_kind_t kind=themis_get_key_kind(&key[0], key.size()); + switch(kind){ + case THEMIS_KEY_EC_PUBLIC: + case THEMIS_KEY_RSA_PUBLIC: + return true; + default: + break; + } + } + return false; + } + } //end jsthemis diff --git a/src/wrappers/themis/jsthemis/secure_keygen.hpp b/src/wrappers/themis/jsthemis/secure_keygen.hpp index ece17a3b1..d318923a9 100644 --- a/src/wrappers/themis/jsthemis/secure_keygen.hpp +++ b/src/wrappers/themis/jsthemis/secure_keygen.hpp @@ -41,5 +41,9 @@ namespace jsthemis{ std::vector public_key_; }; + bool IsValidKey(const std::vector& key); + bool IsPrivateKey(const std::vector& key); + bool IsPublicKey(const std::vector& key); + } #endif /* JSTHEMIS_KEY_PAIR_HPP_ */ diff --git a/src/wrappers/themis/jsthemis/secure_message.cpp b/src/wrappers/themis/jsthemis/secure_message.cpp index ac25afe9c..d81286453 100644 --- a/src/wrappers/themis/jsthemis/secure_message.cpp +++ b/src/wrappers/themis/jsthemis/secure_message.cpp @@ -18,6 +18,7 @@ #include #include #include "errors.hpp" +#include "secure_keygen.hpp" #include "secure_message.hpp" namespace jsthemis { @@ -81,41 +82,23 @@ namespace jsthemis { bool SecureMessage::ValidateKeys(const std::vector& private_key, const std::vector& public_key) { if(!private_key.empty()){ - if(THEMIS_SUCCESS!=themis_is_valid_key(&private_key[0], private_key.size())){ + if(!IsValidKey(private_key)){ ThrowParameterError("Secure Message constructor", "invalid private key"); return false; } - themis_key_kind_t kind=themis_get_key_kind(&private_key[0], private_key.size()); - switch(kind){ - case THEMIS_KEY_EC_PRIVATE: - case THEMIS_KEY_RSA_PRIVATE: - break; - case THEMIS_KEY_EC_PUBLIC: - case THEMIS_KEY_RSA_PUBLIC: + if(!IsPrivateKey(private_key)){ ThrowParameterError("Secure Message constructor", "using public key instead of private key"); return false; - default: - ThrowParameterError("Secure Message constructor", "private key not supported"); - return false; } } if(!public_key.empty()){ - if(THEMIS_SUCCESS!=themis_is_valid_key(&public_key[0], public_key.size())){ + if(!IsValidKey(public_key)){ ThrowParameterError("Secure Message constructor", "invalid public key"); return false; } - themis_key_kind_t kind=themis_get_key_kind(&public_key[0], public_key.size()); - switch(kind){ - case THEMIS_KEY_EC_PUBLIC: - case THEMIS_KEY_RSA_PUBLIC: - break; - case THEMIS_KEY_EC_PRIVATE: - case THEMIS_KEY_RSA_PRIVATE: + if(!IsPublicKey(public_key)){ ThrowParameterError("Secure Message constructor", "using private key instead of public key"); return false; - default: - ThrowParameterError("Secure Message constructor", "public key not supported"); - return false; } } return true; From dfbd1b3bfba309cfdfa03f79572eff1286cbdb4f Mon Sep 17 00:00:00 2001 From: ilammy Date: Thu, 21 Feb 2019 18:01:56 +0200 Subject: [PATCH 18/21] Remove redundant checks for null and empty keys These checks are now performed by valid_{private,public}_key() calls. We don't need to explicitly check for NULL and empty from now on. --- src/themis/secure_message.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/themis/secure_message.c b/src/themis/secure_message.c index da7f00f49..9f1e7e682 100644 --- a/src/themis/secure_message.c +++ b/src/themis/secure_message.c @@ -71,10 +71,6 @@ themis_status_t themis_secure_message_encrypt(const uint8_t* private_key, uint8_t* encrypted_message, size_t* encrypted_message_length) { - THEMIS_CHECK_PARAM(private_key!=NULL); - THEMIS_CHECK_PARAM(private_key_length!=0); - THEMIS_CHECK_PARAM(public_key!=NULL); - THEMIS_CHECK_PARAM(public_key_length!=0); THEMIS_CHECK_PARAM(message!=NULL); THEMIS_CHECK_PARAM(message_length!=0); THEMIS_CHECK_PARAM(encrypted_message_length!=NULL); @@ -100,10 +96,6 @@ themis_status_t themis_secure_message_decrypt(const uint8_t* private_key, uint8_t* message, size_t* message_length) { - THEMIS_CHECK_PARAM(private_key!=NULL); - THEMIS_CHECK_PARAM(private_key_length!=0); - THEMIS_CHECK_PARAM(public_key!=NULL); - THEMIS_CHECK_PARAM(public_key_length!=0); THEMIS_CHECK_PARAM(encrypted_message!=NULL); THEMIS_CHECK_PARAM(encrypted_message_length!=0); THEMIS_CHECK_PARAM(message_length!=NULL); @@ -131,8 +123,6 @@ themis_status_t themis_secure_message_sign(const uint8_t* private_key, uint8_t* signed_message, size_t* signed_message_length) { - THEMIS_CHECK_PARAM(private_key!=NULL); - THEMIS_CHECK_PARAM(private_key_length!=0); THEMIS_CHECK_PARAM(message!=NULL); THEMIS_CHECK_PARAM(message_length!=0); THEMIS_CHECK_PARAM(signed_message_length!=NULL); @@ -154,8 +144,6 @@ themis_status_t themis_secure_message_verify(const uint8_t* public_key, uint8_t* message, size_t* message_length) { - THEMIS_CHECK_PARAM(public_key!=NULL); - THEMIS_CHECK_PARAM(public_key_length!=0); THEMIS_CHECK_PARAM(signed_message!=NULL); THEMIS_CHECK_PARAM(signed_message_length!=0); THEMIS_CHECK_PARAM(message_length!=NULL); From ec67c8408769d67a7909d8dab40e3bc1c88250cf Mon Sep 17 00:00:00 2001 From: ilammy Date: Thu, 21 Feb 2019 18:23:18 +0200 Subject: [PATCH 19/21] Provide more detailed key validation The users may actually be interested in the status code returned for key validation so provide a separate function that returns more detailed information about the keys. --- src/wrappers/themis/jsthemis/secure_keygen.cpp | 14 ++++++++------ src/wrappers/themis/jsthemis/secure_keygen.hpp | 2 ++ src/wrappers/themis/themispp/secure_keygen.hpp | 14 ++++++++------ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/wrappers/themis/jsthemis/secure_keygen.cpp b/src/wrappers/themis/jsthemis/secure_keygen.cpp index 68cbdfb44..fca22ed7e 100644 --- a/src/wrappers/themis/jsthemis/secure_keygen.cpp +++ b/src/wrappers/themis/jsthemis/secure_keygen.cpp @@ -107,13 +107,15 @@ namespace jsthemis { args.GetReturnValue().Set(Nan::CopyBuffer((char*)(&(obj->public_key_)[0]), obj->public_key_.size()).ToLocalChecked()); } - bool IsValidKey(const std::vector& key){ - if(!key.empty()){ - if(THEMIS_SUCCESS==themis_is_valid_key(&key[0], key.size())){ - return true; - } + themis_status_t ValidateKey(const std::vector& key){ + if(key.empty()){ + return false; } - return false; + return themis_is_valid_key(&key[0], key.size()); + } + + bool IsValidKey(const std::vector& key){ + return ValidateKey(key)==THEMIS_SUCCESS; } bool IsPrivateKey(const std::vector& key){ diff --git a/src/wrappers/themis/jsthemis/secure_keygen.hpp b/src/wrappers/themis/jsthemis/secure_keygen.hpp index d318923a9..034383063 100644 --- a/src/wrappers/themis/jsthemis/secure_keygen.hpp +++ b/src/wrappers/themis/jsthemis/secure_keygen.hpp @@ -19,6 +19,7 @@ #include #include +#include namespace jsthemis{ @@ -41,6 +42,7 @@ namespace jsthemis{ std::vector public_key_; }; + themis_status_t ValidateKey(const std::vector& key); bool IsValidKey(const std::vector& key); bool IsPrivateKey(const std::vector& key); bool IsPublicKey(const std::vector& key); diff --git a/src/wrappers/themis/themispp/secure_keygen.hpp b/src/wrappers/themis/themispp/secure_keygen.hpp index e6481e356..4b7dea689 100644 --- a/src/wrappers/themis/themispp/secure_keygen.hpp +++ b/src/wrappers/themis/themispp/secure_keygen.hpp @@ -70,13 +70,15 @@ namespace themispp{ std::vector public_key; }; - inline bool is_valid_key(const std::vector& key){ - if(!key.empty()){ - if(THEMIS_SUCCESS==themis_is_valid_key(&key[0], key.size())){ - return true; - } + inline themis_status_t validate_key(const std::vector& key){ + if(key.empty()){ + return THEMIS_INVALID_PARAMETER; } - return false; + return themis_is_valid_key(&key[0], key.size()); + } + + inline bool is_valid_key(const std::vector& key){ + return validate_key(key)==THEMIS_SUCCESS; } inline bool is_private_key(const std::vector& key){ From 27970d3355d8ca239e22c30a95eefb6890ecaa6f Mon Sep 17 00:00:00 2001 From: ilammy Date: Fri, 22 Feb 2019 11:46:45 +0200 Subject: [PATCH 20/21] Improve key validation accuracy Check for the key kind in themis_is_valid_key(), it's not just keys using Soter containers. For example, Secure Session messages use them too and they are not keys. Also use the correct tag field instead of the whole struct. Do not cause unnecessary undefined behavior. --- src/themis/secure_keygen.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/themis/secure_keygen.c b/src/themis/secure_keygen.c index 4cb70aded..281ef59ad 100644 --- a/src/themis/secure_keygen.c +++ b/src/themis/secure_keygen.c @@ -84,20 +84,22 @@ themis_status_t themis_gen_ec_key_pair(uint8_t* private_key, } themis_key_kind_t themis_get_key_kind(const uint8_t* key, size_t length){ + const soter_container_hdr_t* container=(const void*)key; + if(!key || (lengthtag, RSA_PRIV_KEY_PREF, strlen(RSA_PRIV_KEY_PREF))){ return THEMIS_KEY_RSA_PRIVATE; } - if(!memcmp(key, RSA_PUB_KEY_PREF, strlen(RSA_PUB_KEY_PREF))){ + if(!memcmp(container->tag, RSA_PUB_KEY_PREF, strlen(RSA_PUB_KEY_PREF))){ return THEMIS_KEY_RSA_PUBLIC; } - if(!memcmp(key, EC_PRIV_KEY_PREF, strlen(EC_PRIV_KEY_PREF))){ + if(!memcmp(container->tag, EC_PRIV_KEY_PREF, strlen(EC_PRIV_KEY_PREF))){ return THEMIS_KEY_EC_PRIVATE; } - if(!memcmp(key, EC_PUB_KEY_PREF, strlen(EC_PUB_KEY_PREF))){ + if(!memcmp(container->tag, EC_PUB_KEY_PREF, strlen(EC_PUB_KEY_PREF))){ return THEMIS_KEY_EC_PUBLIC; } @@ -110,6 +112,9 @@ themis_status_t themis_is_valid_key(const uint8_t* key, size_t length){ if(!key || (lengthsize)){ return THEMIS_INVALID_PARAMETER; } From 2f021587fa88cc8b25bb260bc128b9038242772b Mon Sep 17 00:00:00 2001 From: ilammy Date: Fri, 22 Feb 2019 11:55:10 +0200 Subject: [PATCH 21/21] Rename key validation functions Use "asym" as a part of their names because there are three kinds of keys visible for Themis users: - ECDSA asymmetric keys for Secure Message and Secure Session - RSA asymmetric keys for Secure Message (and maybe Secure Session) - symmetric (AES) keys derived from master key for Secure Cell Let's clearly state that we dead with asymmetric keys here. --- src/themis/secure_keygen.c | 6 +- src/themis/secure_keygen.h | 4 +- src/themis/secure_message.c | 12 ++-- .../themis/jsthemis/secure_keygen.cpp | 6 +- src/wrappers/themis/rust/src/keys.rs | 6 +- .../themis/themispp/secure_keygen.hpp | 6 +- tests/themis/themis_seccure_message.c | 64 +++++++++---------- 7 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/themis/secure_keygen.c b/src/themis/secure_keygen.c index 281ef59ad..33be2ee06 100644 --- a/src/themis/secure_keygen.c +++ b/src/themis/secure_keygen.c @@ -83,7 +83,7 @@ themis_status_t themis_gen_ec_key_pair(uint8_t* private_key, return themis_gen_key_pair(SOTER_SIGN_ecdsa_none_pkcs8, private_key, private_key_length, public_key, public_key_length); } -themis_key_kind_t themis_get_key_kind(const uint8_t* key, size_t length){ +themis_key_kind_t themis_get_asym_key_kind(const uint8_t* key, size_t length){ const soter_container_hdr_t* container=(const void*)key; if(!key || (lengthsize)){ diff --git a/src/themis/secure_keygen.h b/src/themis/secure_keygen.h index fc07fc6b5..f09939781 100644 --- a/src/themis/secure_keygen.h +++ b/src/themis/secure_keygen.h @@ -81,7 +81,7 @@ typedef enum themis_key_kind themis_key_kind_t; * @param [in] length length of key * @return corresponding key kind if the buffer contains a key, or THEMIS_KEY_INVALID otherwise */ -themis_key_kind_t themis_get_key_kind(const uint8_t* key, size_t length); +themis_key_kind_t themis_get_asym_key_kind(const uint8_t* key, size_t length); /** * @brief validate a Themis key @@ -89,7 +89,7 @@ themis_key_kind_t themis_get_key_kind(const uint8_t* key, size_t length); * @param [in] length length of key * @return THEMIS_SUCCESS if the buffer contains a valid Themis key, or an error code otherwise */ -themis_status_t themis_is_valid_key(const uint8_t* key, size_t length); +themis_status_t themis_is_valid_asym_key(const uint8_t* key, size_t length); /** @} */ /** @} */ diff --git a/src/themis/secure_message.c b/src/themis/secure_message.c index 9f1e7e682..31921aa2f 100644 --- a/src/themis/secure_message.c +++ b/src/themis/secure_message.c @@ -21,8 +21,8 @@ #include static bool valid_private_key(const uint8_t* private_key, size_t private_key_length){ - if(themis_is_valid_key(private_key, private_key_length)==THEMIS_SUCCESS){ - themis_key_kind_t private_key_kind=themis_get_key_kind(private_key, private_key_length); + if(themis_is_valid_asym_key(private_key, private_key_length)==THEMIS_SUCCESS){ + themis_key_kind_t private_key_kind=themis_get_asym_key_kind(private_key, private_key_length); switch(private_key_kind){ case THEMIS_KEY_EC_PRIVATE: case THEMIS_KEY_RSA_PRIVATE: @@ -35,8 +35,8 @@ static bool valid_private_key(const uint8_t* private_key, size_t private_key_len } static bool valid_public_key(const uint8_t* public_key, size_t public_key_length){ - if(themis_is_valid_key(public_key, public_key_length)==THEMIS_SUCCESS){ - themis_key_kind_t public_key_kind=themis_get_key_kind(public_key, public_key_length); + if(themis_is_valid_asym_key(public_key, public_key_length)==THEMIS_SUCCESS){ + themis_key_kind_t public_key_kind=themis_get_asym_key_kind(public_key, public_key_length); switch(public_key_kind){ case THEMIS_KEY_EC_PUBLIC: case THEMIS_KEY_RSA_PUBLIC: @@ -51,8 +51,8 @@ static bool valid_public_key(const uint8_t* public_key, size_t public_key_length static bool matching_key_kinds(const uint8_t* private_key, size_t private_key_length, const uint8_t* public_key, size_t public_key_length) { - themis_key_kind_t private_key_kind=themis_get_key_kind(private_key, private_key_length); - themis_key_kind_t public_key_kind=themis_get_key_kind(public_key, public_key_length); + themis_key_kind_t private_key_kind=themis_get_asym_key_kind(private_key, private_key_length); + themis_key_kind_t public_key_kind=themis_get_asym_key_kind(public_key, public_key_length); if(private_key_kind==THEMIS_KEY_EC_PRIVATE && public_key_kind==THEMIS_KEY_EC_PUBLIC){ return true; } diff --git a/src/wrappers/themis/jsthemis/secure_keygen.cpp b/src/wrappers/themis/jsthemis/secure_keygen.cpp index fca22ed7e..a71860471 100644 --- a/src/wrappers/themis/jsthemis/secure_keygen.cpp +++ b/src/wrappers/themis/jsthemis/secure_keygen.cpp @@ -111,7 +111,7 @@ namespace jsthemis { if(key.empty()){ return false; } - return themis_is_valid_key(&key[0], key.size()); + return themis_is_valid_asym_key(&key[0], key.size()); } bool IsValidKey(const std::vector& key){ @@ -120,7 +120,7 @@ namespace jsthemis { bool IsPrivateKey(const std::vector& key){ if(!key.empty()){ - themis_key_kind_t kind=themis_get_key_kind(&key[0], key.size()); + themis_key_kind_t kind=themis_get_asym_key_kind(&key[0], key.size()); switch(kind){ case THEMIS_KEY_EC_PRIVATE: case THEMIS_KEY_RSA_PRIVATE: @@ -134,7 +134,7 @@ namespace jsthemis { bool IsPublicKey(const std::vector& key){ if(!key.empty()){ - themis_key_kind_t kind=themis_get_key_kind(&key[0], key.size()); + themis_key_kind_t kind=themis_get_asym_key_kind(&key[0], key.size()); switch(kind){ case THEMIS_KEY_EC_PUBLIC: case THEMIS_KEY_RSA_PUBLIC: diff --git a/src/wrappers/themis/rust/src/keys.rs b/src/wrappers/themis/rust/src/keys.rs index 1c9369868..cc2b2dabb 100644 --- a/src/wrappers/themis/rust/src/keys.rs +++ b/src/wrappers/themis/rust/src/keys.rs @@ -130,7 +130,7 @@ use std::fmt; -use bindings::{themis_get_key_kind, themis_is_valid_key}; +use bindings::{themis_get_asym_key_kind, themis_is_valid_asym_key}; use zeroize::Zeroize; use crate::error::{Error, ErrorKind, Result}; @@ -517,7 +517,7 @@ fn get_key_kind_trusted(key: &KeyBytes) -> KeyKind { fn is_valid_themis_key(key: &KeyBytes) -> Result<()> { let (ptr, len) = into_raw_parts(key.as_bytes()); - let status = unsafe { themis_is_valid_key(ptr, len) }; + let status = unsafe { themis_is_valid_asym_key(ptr, len) }; let error = Error::from_themis_status(status); if error.kind() != ErrorKind::Success { return Err(error); @@ -528,7 +528,7 @@ fn is_valid_themis_key(key: &KeyBytes) -> Result<()> { fn try_get_key_kind(key: &KeyBytes) -> Result { use bindings::themis_key_kind::*; let (ptr, len) = into_raw_parts(key.as_bytes()); - let kind = unsafe { themis_get_key_kind(ptr, len) }; + let kind = unsafe { themis_get_asym_key_kind(ptr, len) }; match kind { THEMIS_KEY_RSA_PRIVATE => Ok(KeyKind::RsaPrivate), THEMIS_KEY_RSA_PUBLIC => Ok(KeyKind::RsaPublic), diff --git a/src/wrappers/themis/themispp/secure_keygen.hpp b/src/wrappers/themis/themispp/secure_keygen.hpp index 4b7dea689..aa850797d 100644 --- a/src/wrappers/themis/themispp/secure_keygen.hpp +++ b/src/wrappers/themis/themispp/secure_keygen.hpp @@ -74,7 +74,7 @@ namespace themispp{ if(key.empty()){ return THEMIS_INVALID_PARAMETER; } - return themis_is_valid_key(&key[0], key.size()); + return themis_is_valid_asym_key(&key[0], key.size()); } inline bool is_valid_key(const std::vector& key){ @@ -83,7 +83,7 @@ namespace themispp{ inline bool is_private_key(const std::vector& key){ if(!key.empty()){ - themis_key_kind_t kind=themis_get_key_kind(&key[0], key.size()); + themis_key_kind_t kind=themis_get_asym_key_kind(&key[0], key.size()); switch(kind){ case THEMIS_KEY_EC_PRIVATE: case THEMIS_KEY_RSA_PRIVATE: @@ -97,7 +97,7 @@ namespace themispp{ inline bool is_public_key(const std::vector& key){ if(!key.empty()){ - themis_key_kind_t kind=themis_get_key_kind(&key[0], key.size()); + themis_key_kind_t kind=themis_get_asym_key_kind(&key[0], key.size()); switch(kind){ case THEMIS_KEY_EC_PUBLIC: case THEMIS_KEY_RSA_PUBLIC: diff --git a/tests/themis/themis_seccure_message.c b/tests/themis/themis_seccure_message.c index 087c00d29..9ddc57a31 100644 --- a/tests/themis/themis_seccure_message.c +++ b/tests/themis/themis_seccure_message.c @@ -711,40 +711,40 @@ static void key_validation_test(void){ return; } - testsuite_fail_unless(THEMIS_KEY_EC_PRIVATE == themis_get_key_kind(ec_private_key, ec_private_key_length), - "themis_get_key_kind: EC private"); - testsuite_fail_unless(THEMIS_KEY_EC_PUBLIC == themis_get_key_kind(ec_public_key, ec_public_key_length), - "themis_get_key_kind: EC public"); - - testsuite_fail_unless(THEMIS_KEY_RSA_PRIVATE == themis_get_key_kind(rsa_private_key, rsa_private_key_length), - "themis_get_key_kind: RSA private"); - testsuite_fail_unless(THEMIS_KEY_RSA_PUBLIC == themis_get_key_kind(rsa_public_key, rsa_public_key_length), - "themis_get_key_kind: RSA public"); - - testsuite_fail_unless(THEMIS_SUCCESS == themis_is_valid_key(ec_private_key, ec_private_key_length), - "themis_is_valid_key: EC private"); - testsuite_fail_unless(THEMIS_SUCCESS == themis_is_valid_key(ec_public_key, ec_public_key_length), - "themis_is_valid_key: EC public"); - testsuite_fail_unless(THEMIS_SUCCESS == themis_is_valid_key(rsa_private_key, rsa_private_key_length), - "themis_is_valid_key: RSA private"); - testsuite_fail_unless(THEMIS_SUCCESS == themis_is_valid_key(rsa_public_key, rsa_public_key_length), - "themis_is_valid_key: RSA public"); - - testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_is_valid_key(NULL, 0), - "themis_is_valid_key: invalid arguments"); - testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_is_valid_key(ec_private_key, ec_private_key_length-1), - "themis_is_valid_key: truncated buffer"); - - testsuite_fail_unless(THEMIS_KEY_INVALID == themis_get_key_kind(NULL, 0), - "themis_get_key_kind: invalid arguments"); - testsuite_fail_unless(THEMIS_KEY_INVALID == themis_get_key_kind(ec_private_key, 2), - "themis_get_key_kind: truncated buffer"); + testsuite_fail_unless(THEMIS_KEY_EC_PRIVATE == themis_get_asym_key_kind(ec_private_key, ec_private_key_length), + "themis_get_asym_key_kind: EC private"); + testsuite_fail_unless(THEMIS_KEY_EC_PUBLIC == themis_get_asym_key_kind(ec_public_key, ec_public_key_length), + "themis_get_asym_key_kind: EC public"); + + testsuite_fail_unless(THEMIS_KEY_RSA_PRIVATE == themis_get_asym_key_kind(rsa_private_key, rsa_private_key_length), + "themis_get_asym_key_kind: RSA private"); + testsuite_fail_unless(THEMIS_KEY_RSA_PUBLIC == themis_get_asym_key_kind(rsa_public_key, rsa_public_key_length), + "themis_get_asym_key_kind: RSA public"); + + testsuite_fail_unless(THEMIS_SUCCESS == themis_is_valid_asym_key(ec_private_key, ec_private_key_length), + "themis_is_valid_asym_key: EC private"); + testsuite_fail_unless(THEMIS_SUCCESS == themis_is_valid_asym_key(ec_public_key, ec_public_key_length), + "themis_is_valid_asym_key: EC public"); + testsuite_fail_unless(THEMIS_SUCCESS == themis_is_valid_asym_key(rsa_private_key, rsa_private_key_length), + "themis_is_valid_asym_key: RSA private"); + testsuite_fail_unless(THEMIS_SUCCESS == themis_is_valid_asym_key(rsa_public_key, rsa_public_key_length), + "themis_is_valid_asym_key: RSA public"); + + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_is_valid_asym_key(NULL, 0), + "themis_is_valid_asym_key: invalid arguments"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_is_valid_asym_key(ec_private_key, ec_private_key_length-1), + "themis_is_valid_asym_key: truncated buffer"); + + testsuite_fail_unless(THEMIS_KEY_INVALID == themis_get_asym_key_kind(NULL, 0), + "themis_get_asym_key_kind: invalid arguments"); + testsuite_fail_unless(THEMIS_KEY_INVALID == themis_get_asym_key_kind(ec_private_key, 2), + "themis_get_asym_key_kind: truncated buffer"); const char* input = "definitely not a valid key"; - testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_is_valid_key((const uint8_t*)input, strlen(input)), - "themis_is_valid_key: garbage input"); - testsuite_fail_unless(THEMIS_KEY_INVALID == themis_get_key_kind((const uint8_t*)input, strlen(input)), - "themis_get_key_kind: garbage input"); + testsuite_fail_unless(THEMIS_INVALID_PARAMETER == themis_is_valid_asym_key((const uint8_t*)input, strlen(input)), + "themis_is_valid_asym_key: garbage input"); + testsuite_fail_unless(THEMIS_KEY_INVALID == themis_get_asym_key_kind((const uint8_t*)input, strlen(input)), + "themis_get_asym_key_kind: garbage input"); } void run_secure_message_test(){