Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce new Secure Message API #389

Merged
merged 22 commits into from Feb 25, 2019
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ef65c98
Introduce new Secure Message API
ilammy Feb 20, 2019
54aefce
Cast test message as uint8_t
ilammy Feb 20, 2019
aaaccdb
Implement new Secure Message API using primitives
ilammy Feb 20, 2019
609a6bf
Mark old Secure Message API as deprecated
ilammy Feb 20, 2019
02b38ea
Merge branch 'master' into new-smessage-api
ilammy Feb 20, 2019
acb767b
Use new Secure Message API: ThemisPP
ilammy Feb 20, 2019
9ff2cf0
Use new Secure Message API: rust-themis
ilammy Feb 20, 2019
c21a87f
Use new Secure Message API: Java/Android Themis
ilammy Feb 20, 2019
ac107b4
Use new Secure Message API: JsThemis
ilammy Feb 20, 2019
8306f70
Avoid magic constants in tests
ilammy Feb 20, 2019
7f4f939
Improve wording in comments and deprecations
vixentael Feb 21, 2019
35e3cde
Move key kind validation to Themis Core
ilammy Feb 21, 2019
c341511
Key kind checks in Secure Message
ilammy Feb 21, 2019
0d3edf5
Key kind checks in Secure Message: ThemisPP
ilammy Feb 21, 2019
7075209
Key kind checks in Secure Message: JsThemis
ilammy Feb 21, 2019
3549f9d
Move keygen and validation into a new file
ilammy Feb 21, 2019
fdc4bc6
Move key checking utilities to secure_keygen.hpp (ThemisPP)
ilammy Feb 21, 2019
7e75bb7
Move key checking utilities to secure_keygen.hpp (JsThemis)
ilammy Feb 21, 2019
dfbd1b3
Remove redundant checks for null and empty keys
ilammy Feb 21, 2019
ec67c84
Provide more detailed key validation
ilammy Feb 21, 2019
27970d3
Improve key validation accuracy
ilammy Feb 22, 2019
2f02158
Rename key validation functions
ilammy Feb 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
53 changes: 39 additions & 14 deletions jni/themis_message.c
Expand Up @@ -18,7 +18,12 @@
#include <themis/themis_error.h>
#include <themis/secure_message.h>

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;
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down
212 changes: 212 additions & 0 deletions src/themis/secure_message.c
Expand Up @@ -14,10 +14,16 @@
* limitations under the License.
*/

#include <string.h>
#include <arpa/inet.h>

#include <themis/themis_error.h>
#include <themis/secure_message.h>
#include <themis/secure_message_wrapper.h>
#include <soter/soter_t.h>
#include <soter/soter_container.h>
#include <soter/soter_ec_key.h>
#include <soter/soter_rsa_key.h>

#ifndef THEMIS_RSA_KEY_LENGTH
#define THEMIS_RSA_KEY_LENGTH RSA_KEY_LENGTH_2048
Expand Down Expand Up @@ -77,6 +83,212 @@ 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 || (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;
}

themis_status_t themis_is_valid_key(const uint8_t* key, size_t length){
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;
}

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,
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);
ilammy marked this conversation as resolved.
Show resolved Hide resolved
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);
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));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now library more user friendly than before)


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,
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);
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));
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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it's time to verify the type of key to avoid misusage incorrect type of key (public/private)? : )
we can do simple check on prefix of key value for known prefixes of private keys:

if (memcmp(private_key, RSA_PRIV_KEY_PREF, sizeof(RSA_PRIV_KEY_PREF) != 0 && 
    memcmp(private_key, RSA_PRIV_KEY_PREF, sizeof(EC_PRIV_KEY_PREF) != 0) { 
return THEMIS_INVALID_PARAMETER;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but better will be to extend api and add some functions that will validate keys: validate_private_key/validate_public_key and for now, it will check the only prefix. In the future we can extend these validations with verifying full key's structure

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Come to think of it... I've added the following utilities for the Rust wrapper to query the key kind and validate its content:

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);

Can they be of some use? What do you think of moving these functions to the core library (and using them for checks internally as well)? I believe this API may be useful for applications to verify key content independently.

As for Secure Messages, I guess we can even go a bit further and verify that both private and public keys are of ECDSA or RSA flavor. Ideally, we'd also verify that both use the same public parameters, but that's harder to do.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good checks, I think it's good to move into the core themis library

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, good checks, make sense at core lvl

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Lagovas @vixentael I've moved the key validation routines to Themis core, added some tests for them, added validation to new Secure Message API, and added some early checks to ThemisPP and JsThemis which are most affected by poor error reporting. Please give it a look.

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);
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);
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,
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);
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));
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()
* 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
* 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,
Expand Down