Skip to content

Commit

Permalink
Introduce new Secure Message API (#389)
Browse files Browse the repository at this point in the history
* 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.

* 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.

* 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.

* 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.

* 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.)

* 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.

* 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.

* 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).

* 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.

* Key kind checks in Secure Message: ThemisPP

Add validation and human-friendly error messages to ThemisPP if the
user passes incorrect keys to constructor.

* 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.

* 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.
  • Loading branch information
ilammy committed Feb 25, 2019
1 parent 16b0f9e commit 7ab2c94
Show file tree
Hide file tree
Showing 22 changed files with 1,327 additions and 374 deletions.
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
126 changes: 126 additions & 0 deletions src/themis/secure_keygen.c
@@ -0,0 +1,126 @@
/*
* 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 <string.h>
#include <arpa/inet.h>

#include <themis/themis_error.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
#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_asym_key_kind(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_KEY_INVALID;
}

if(!memcmp(container->tag, RSA_PRIV_KEY_PREF, strlen(RSA_PRIV_KEY_PREF))){
return THEMIS_KEY_RSA_PRIVATE;
}
if(!memcmp(container->tag, RSA_PUB_KEY_PREF, strlen(RSA_PUB_KEY_PREF))){
return THEMIS_KEY_RSA_PUBLIC;
}
if(!memcmp(container->tag, EC_PRIV_KEY_PREF, strlen(EC_PRIV_KEY_PREF))){
return THEMIS_KEY_EC_PRIVATE;
}
if(!memcmp(container->tag, EC_PUB_KEY_PREF, strlen(EC_PUB_KEY_PREF))){
return THEMIS_KEY_EC_PUBLIC;
}

return THEMIS_KEY_INVALID;
}

themis_status_t themis_is_valid_asym_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(THEMIS_KEY_INVALID==themis_get_asym_key_kind(key, length)){
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;
}
102 changes: 102 additions & 0 deletions 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 <themis/themis.h>

#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_asym_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_asym_key(const uint8_t* key, size_t length);

/** @} */
/** @} */

#ifdef __cplusplus
}
#endif


#endif /* _THEMIS_SECURE_KEYGEN_H_ */

0 comments on commit 7ab2c94

Please sign in to comment.