Skip to content

Commit

Permalink
Improve error reporting in JsThemis (#384)
Browse files Browse the repository at this point in the history
* Error reporting helpers

Add some functions to help with error reporting. They provided unified
error message format and convert Themis status code into human-readable
strings.

Secure Session and Secure Comparator code are handled separately
because some of their values overlap with generic codes.

* Secure Message: use new error reporting functions
* Secure Message: length and type checks for arguments
* Secure Message: tests for empty keys and messages

Keys and messages must be non-empty. Themis does check for that but
it returns a generic 'invalid parameter' error. Perform early checks
and report more accurate errors.

Furthermore, check the argument count and their types and report
appropriate errors as well. Otherwise we risk crashing node.js process
due to assertion failure in node::Buffer::Data().

* Secure Cell: use new error reporting functions
* Secure Cell: length and type checks for arguments
* Secure Cell: tests for empty arguments

* Improve error messages, simplify calls

Use more descriptive error messages which suggestions on how to fix
the error.

* Return original Themis status code

If we have a status code, add it as a "code" property to the Error
object we throw. Verify the code in tests where appropriate.

* Retain status code for early errors

Add THEMIS_INVALID_PARAMETER status code to all early parameter checks
that we perform. Now all Themis errors will have a "code" property.
Rename the helper function accordinly.

* Secure Session: use new error reporting functions
* Secure Session: length and type checks for arguments
* Secure Session: tests for invalid arguments

* Secure Comparator: use new error reporting functions
* Secure Comparator: length and type checks for arguments
* Secure Comparator: tests for invalid arguments

* Key generation: update error reporting and checks

* Pin mocha to 5.2.0

Recently released mocha 6.0.0 is not compatible with Node in our CI
environment. Pin the version to ^5.2.0 while it is still available.

* Avoid using deprecated NAN methods

Call() method on callbacks has been deprecated, it is suggested to use
Nan::Call() function for synchronous calls. Do this to avoid warnings
about using deprecated API, who knows when it's going to be removed.

* Secure Session: add checks to pubkey callback

It turned out that public key callback did not have enough checks and
could crash Node process if the user returns unexpected value from it.
Add type checks for result: an array is okay, null and undefined are
explicit "not found" values, anything else is also considered "not
found" but with a different error code (the exact value does not matter
at the moment). Also add a length check to avoid buffer overflow when
doing memcpy().
  • Loading branch information
ilammy authored Feb 19, 2019
1 parent 463f105 commit 4a3fcea
Show file tree
Hide file tree
Showing 13 changed files with 817 additions and 87 deletions.
2 changes: 2 additions & 0 deletions src/wrappers/themis/jsthemis/addon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

#include <node.h>
#include "errors.hpp"
#include "secure_message.hpp"
#include "secure_keygen.hpp"
#include "secure_session.hpp"
Expand All @@ -24,6 +25,7 @@
#include "secure_comparator.hpp"

void InitAll(v8::Handle<v8::Object> exports) {
jsthemis::Errors::Init(exports);
jsthemis::SecureMessage::Init(exports);
jsthemis::KeyPair::Init(exports);
jsthemis::SecureSession::Init(exports);
Expand Down
4 changes: 2 additions & 2 deletions src/wrappers/themis/jsthemis/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"targets": [
{
"target_name": "jsthemis",
"sources": [ "addon.cpp", "secure_message.cpp", "secure_keygen.cpp", "secure_session.cpp", "secure_cell_seal.cpp", "secure_cell_context_imprint.cpp", "secure_cell_token_protect.cpp", "secure_comparator.cpp" ],
"sources": [ "addon.cpp", "errors.cpp", "secure_message.cpp", "secure_keygen.cpp", "secure_session.cpp", "secure_cell_seal.cpp", "secure_cell_context_imprint.cpp", "secure_cell_token_protect.cpp", "secure_comparator.cpp" ],
"libraries": ["-L/usr/local/lib/", "-L/usr/lib/", "-lsoter", "-lthemis"],
"include_dirs": [
"<!(node -e \"require('nan')\")"
]
}
]
}
}
136 changes: 136 additions & 0 deletions src/wrappers/themis/jsthemis/errors.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* 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 "errors.hpp"

#include <string>

#include <nan.h>
#include <node.h>

namespace jsthemis {

static inline void ExportStatusCode(v8::Handle<v8::Object>& exports, const char* name, themis_status_t status) {
exports->Set(Nan::New(name).ToLocalChecked(), Nan::New(status));
}

void Errors::Init(v8::Handle<v8::Object> exports) {
ExportStatusCode(exports, "SUCCESS", THEMIS_SUCCESS);
ExportStatusCode(exports, "FAIL", THEMIS_FAIL);
ExportStatusCode(exports, "INVALID_PARAMETER", THEMIS_INVALID_PARAMETER);
ExportStatusCode(exports, "NO_MEMORY", THEMIS_NO_MEMORY);
ExportStatusCode(exports, "BUFFER_TOO_SMALL", THEMIS_BUFFER_TOO_SMALL);
ExportStatusCode(exports, "DATA_CORRUPT", THEMIS_DATA_CORRUPT);
ExportStatusCode(exports, "INVALID_SIGNATURE", THEMIS_INVALID_SIGNATURE);
ExportStatusCode(exports, "NOT_SUPPORTED", THEMIS_NOT_SUPPORTED);
ExportStatusCode(exports, "SSESSION_KA_NOT_FINISHED", THEMIS_SSESSION_KA_NOT_FINISHED);
ExportStatusCode(exports, "SSESSION_TRANSPORT_ERROR", THEMIS_SSESSION_TRANSPORT_ERROR);
ExportStatusCode(exports, "SSESSION_GET_PUB_FOR_ID_CALLBACK_ERROR", THEMIS_SSESSION_GET_PUB_FOR_ID_CALLBACK_ERROR);
ExportStatusCode(exports, "SCOMPARE_NOT_READY", THEMIS_SCOMPARE_NOT_READY);
}

static const char* ErrorDescription(themis_status_t status) {
switch (status) {
case THEMIS_SUCCESS:
return "success";
case THEMIS_FAIL:
return "failure";
case THEMIS_INVALID_PARAMETER:
return "invalid parameter";
case THEMIS_NO_MEMORY:
return "out of memory";
case THEMIS_BUFFER_TOO_SMALL:
return "buffer too small";
case THEMIS_DATA_CORRUPT:
return "corrupted data";
case THEMIS_INVALID_SIGNATURE:
return "invalid signature";
case THEMIS_NOT_SUPPORTED:
return "operation not supported";
default:
return "unknown error";
}
}

static const char* ErrorDescriptionSecureSession(themis_status_t status) {
switch (status) {
case THEMIS_SSESSION_SEND_OUTPUT_TO_PEER:
return "send key agreement data to peer";
case THEMIS_SSESSION_KA_NOT_FINISHED:
return "key agreement not finished";
case THEMIS_SSESSION_TRANSPORT_ERROR:
return "transport layer error";
case THEMIS_SSESSION_GET_PUB_FOR_ID_CALLBACK_ERROR:
return "failed to get public key for ID";
default:
return ErrorDescription(status);
}
}

static const char* ErrorDescriptionSecureComparator(themis_status_t status) {
switch (status) {
case THEMIS_SCOMPARE_SEND_OUTPUT_TO_PEER:
return "send comparison data to peer";
case THEMIS_SCOMPARE_NOT_READY:
return "comparator not ready";
case THEMIS_SCOMPARE_MATCH:
return "data matches";
case THEMIS_SCOMPARE_NO_MATCH:
return "data does not match";
default:
return ErrorDescription(status);
}
}

static v8::Local<v8::Value> WithStatus(v8::Local<v8::Value> error, themis_status_t status) {
v8::Local<v8::Object> object = error.As<v8::Object>();
object->Set(Nan::New("code").ToLocalChecked(), Nan::New(status));
return error;
}

void ThrowError(const char* domain, themis_status_t status) {
std::string message;
message += domain;
message += ": ";
message += ErrorDescription(status);
Nan::ThrowError(WithStatus(Nan::Error(message.c_str()), status));
}

void ThrowParameterError(const char* domain, const char* description) {
std::string message;
message += domain;
message += ": ";
message += description;
Nan::ThrowError(WithStatus(Nan::Error(message.c_str()), THEMIS_INVALID_PARAMETER));
}

void ThrowSecureSessionError(const char* domain, themis_status_t status) {
std::string message;
message += domain;
message += ": ";
message += ErrorDescriptionSecureSession(status);
Nan::ThrowError(WithStatus(Nan::Error(message.c_str()), status));
}

void ThrowSecureComparatorError(const char* domain, themis_status_t status) {
std::string message;
message += domain;
message += ": ";
message += ErrorDescriptionSecureComparator(status);
Nan::ThrowError(WithStatus(Nan::Error(message.c_str()), status));
}

} // namespace jsthemis
42 changes: 42 additions & 0 deletions src/wrappers/themis/jsthemis/errors.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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.
*/

#ifndef JSTHEMIS_ERRORS_HPP_
#define JSTHEMIS_ERRORS_HPP_

#include <nan.h>

#include <themis/themis.h>

namespace jsthemis {

namespace Errors {

void Init(v8::Handle<v8::Object> exports);

} // namespace Errors

void ThrowError(const char* domain, themis_status_t status);

void ThrowParameterError(const char* domain, const char* description);

void ThrowSecureSessionError(const char* domain, themis_status_t status);

void ThrowSecureComparatorError(const char* domain, themis_status_t status);

} // namespace jsthemis

#endif /* JSTHEMIS_ERRORS_HPP_ */
88 changes: 80 additions & 8 deletions src/wrappers/themis/jsthemis/secure_cell_context_imprint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <node_buffer.h>
#include <themis/themis.h>
#include <vector>
#include "errors.hpp"
#include "secure_cell_context_imprint.hpp"

namespace jsthemis {
Expand All @@ -42,6 +43,21 @@ namespace jsthemis {

void SecureCellContextImprint::New(const Nan::FunctionCallbackInfo<v8::Value>& args) {
if (args.IsConstructCall()) {
if(args.Length()<1){
ThrowParameterError("Secure Cell (Context Imprint) constructor", "not enough arguments, expected master key");
args.GetReturnValue().SetUndefined();
return;
}
if(!args[0]->IsUint8Array()){
ThrowParameterError("Secure Cell (Context Imprint) constructor", "master key is not a byte buffer");
args.GetReturnValue().SetUndefined();
return;
}
if(node::Buffer::Length(args[0])==0){
ThrowParameterError("Secure Cell (Context Imprint) constructor", "master key is empty");
args.GetReturnValue().SetUndefined();
return;
}
std::vector<uint8_t> key((uint8_t*)(node::Buffer::Data(args[0])), (uint8_t*)(node::Buffer::Data(args[0])+node::Buffer::Length(args[0])));
SecureCellContextImprint* obj = new SecureCellContextImprint(key);
obj->Wrap(args.This());
Expand All @@ -55,18 +71,46 @@ namespace jsthemis {
}

void SecureCellContextImprint::encrypt(const Nan::FunctionCallbackInfo<v8::Value>& args) {
themis_status_t status = THEMIS_FAIL;
SecureCellContextImprint* obj = Nan::ObjectWrap::Unwrap<SecureCellContextImprint>(args.This());
if(args.Length()<2){
ThrowParameterError("Secure Cell (Context Imprint) failed to encrypt", "not enough arguments, missing message and context");
args.GetReturnValue().SetUndefined();
return;
}
if(!args[0]->IsUint8Array()){
ThrowParameterError("Secure Cell (Context Imprint) failed to encrypt", "message is not a byte buffer, use ByteBuffer or Uint8Array");
args.GetReturnValue().SetUndefined();
return;
}
if(node::Buffer::Length(args[0])==0){
ThrowParameterError("Secure Cell (Context Imprint) failed to encrypt", "message is empty");
args.GetReturnValue().SetUndefined();
return;
}
if(!args[1]->IsUint8Array()){
ThrowParameterError("Secure Cell (Context Imprint) failed to encrypt", "context is not a byte buffer, use ByteBuffer or Uint8Array");
args.GetReturnValue().SetUndefined();
return;
}
if(node::Buffer::Length(args[1])==0){
ThrowParameterError("Secure Cell (Context Imprint) failed to encrypt", "context is empty");
args.GetReturnValue().SetUndefined();
return;
}
size_t length=0;
const uint8_t* context=(const uint8_t*)(node::Buffer::Data(args[1]));
size_t context_length=node::Buffer::Length(args[1]);
if(themis_secure_cell_encrypt_context_imprint(&(obj->key_)[0], obj->key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), context, context_length, NULL, &length)!=THEMIS_BUFFER_TOO_SMALL){
Nan::ThrowError("Secure Cell (Context Imprint) failed encrypting");
status=themis_secure_cell_encrypt_context_imprint(&(obj->key_)[0], obj->key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), context, context_length, NULL, &length);
if(status!=THEMIS_BUFFER_TOO_SMALL){
ThrowError("Secure Cell (Context Imprint) failed to encrypt", status);
args.GetReturnValue().SetUndefined();
return;
}
uint8_t* data=(uint8_t*)(malloc(length));
if(themis_secure_cell_encrypt_context_imprint(&(obj->key_)[0], obj->key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), context, context_length, data, &length)!=THEMIS_SUCCESS){
Nan::ThrowError("Secure Cell (Context Imprint) failed encrypting");
status=themis_secure_cell_encrypt_context_imprint(&(obj->key_)[0], obj->key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), context, context_length, data, &length);
if(status!=THEMIS_SUCCESS){
ThrowError("Secure Cell (Context Imprint) failed to encrypt", status);
free(data);
args.GetReturnValue().SetUndefined();
return;
Expand All @@ -75,18 +119,46 @@ namespace jsthemis {
}

void SecureCellContextImprint::decrypt(const Nan::FunctionCallbackInfo<v8::Value>& args) {
themis_status_t status = THEMIS_FAIL;
SecureCellContextImprint* obj = Nan::ObjectWrap::Unwrap<SecureCellContextImprint>(args.This());
if(args.Length()<2){
ThrowParameterError("Secure Cell (Context Imprint) failed to decrypt", "not enough arguments, expected message and context");
args.GetReturnValue().SetUndefined();
return;
}
if(!args[0]->IsUint8Array()){
ThrowParameterError("Secure Cell (Context Imprint) failed to decrypt", "message is not a byte buffer, use ByteBuffer or Uint8Array");
args.GetReturnValue().SetUndefined();
return;
}
if(node::Buffer::Length(args[0])==0){
ThrowParameterError("Secure Cell (Context Imprint) failed to decrypt", "message is empty");
args.GetReturnValue().SetUndefined();
return;
}
if(!args[1]->IsUint8Array()){
ThrowParameterError("Secure Cell (Context Imprint) failed to decrypt", "context is not a byte buffer, use ByteBuffer or Uint8Array");
args.GetReturnValue().SetUndefined();
return;
}
if(node::Buffer::Length(args[1])==0){
ThrowParameterError("Secure Cell (Context Imprint) failed to decrypt", "context is empty");
args.GetReturnValue().SetUndefined();
return;
}
size_t length=0;
const uint8_t* context=(const uint8_t*)(node::Buffer::Data(args[1]));
size_t context_length=node::Buffer::Length(args[1]);
if(themis_secure_cell_decrypt_context_imprint(&(obj->key_)[0], obj->key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), context, context_length, NULL, &length)!=THEMIS_BUFFER_TOO_SMALL){
Nan::ThrowError("Secure Cell (Context Imprint) failed decrypting");
status=themis_secure_cell_decrypt_context_imprint(&(obj->key_)[0], obj->key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), context, context_length, NULL, &length);
if(status!=THEMIS_BUFFER_TOO_SMALL){
ThrowError("Secure Cell (Context Imprint) failed to decrypt", status);
args.GetReturnValue().SetUndefined();
return;
}
uint8_t* data=(uint8_t*)(malloc(length));
if(themis_secure_cell_decrypt_context_imprint(&(obj->key_)[0], obj->key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), context, context_length, data, &length)!=THEMIS_SUCCESS){
Nan::ThrowError("Secure Cell (Context Imprint) failed decrypting");
status=themis_secure_cell_decrypt_context_imprint(&(obj->key_)[0], obj->key_.size(), (const uint8_t*)(node::Buffer::Data(args[0])), node::Buffer::Length(args[0]), context, context_length, data, &length);
if(status!=THEMIS_SUCCESS){
ThrowError("Secure Cell (Context Imprint) failed to decrypt", status);
free(data);
args.GetReturnValue().SetUndefined();
return;
Expand Down
Loading

0 comments on commit 4a3fcea

Please sign in to comment.