...
 
Commits (5)
......@@ -25,7 +25,7 @@ include(CheckLibraryExists)
include(CMakePushCheckState)
include(CMakePackageConfigHelpers)
cmake_minimum_required(VERSION 3.11) # we need CMake 3.11 for defining 'package_source' target as custom target
cmake_minimum_required(VERSION 3.1)
option(ENABLE_SHARED "Build shared library." YES)
option(ENABLE_STATIC "Build static library." YES)
......@@ -36,6 +36,7 @@ option(ENABLE_UNIT_TESTS "Enable compilation of unit tests." YES)
option(ENABLE_PROFILING "Enable profiling, GCC only" NO)
option(ENABLE_C_INTERFACE "Enable support of C89 foreign function interface" NO)
option(ENABLE_JNI "Enable support of Java foreign function interface" NO)
option(ENABLE_PACKAGE_SOURCE "Create 'package_source' target for source archive making (CMake >= 3.11)" OFF)
set (LANGUAGES_LIST CXX)
if (ENABLE_C_INTERFACE)
......@@ -45,7 +46,7 @@ if (ENABLE_JNI)
set (LANGUAGES_LIST ${LANGUAGES_LIST} Java)
endif()
project(lime VERSION 4.3.0 LANGUAGES ${LANGUAGES_LIST})
project(lime VERSION 4.4.0 LANGUAGES ${LANGUAGES_LIST})
set(LIME_SO_VERSION "0")
set(LIME_VERSION ${PROJECT_VERSION})
......@@ -190,4 +191,6 @@ if (DOXYGEN_FOUND)
)
endif()
add_subdirectory(build)
if (ENABLE_PACKAGE_SOURCE)
add_subdirectory(build)
endif()
......@@ -20,6 +20,8 @@
#
############################################################################
cmake_minimum_required(VERSION 3.11) # we need CMake 3.11 for defining 'package_source' target as custom target
if(NOT CPACK_PACKAGE_NAME)
set(CPACK_PACKAGE_NAME "lime")
endif()
......
......@@ -59,7 +59,7 @@ if(ENABLE_STATIC)
add_library(lime-static STATIC ${LIME_PRIVATE_HEADER_FILES} ${LIME_SOURCE_FILES_CXX})
set_target_properties(lime-static PROPERTIES OUTPUT_NAME lime)
target_include_directories(lime-static PUBLIC ${SOCI_INCLUDE_DIRS} ${SOCI_INCLUDE_DIRS}/soci ${JNI_INCLUDE_DIRS})
target_link_libraries(lime-static INTERFACE bctoolbox ${SOCI_LIBRARIES} ${SOCI_sqlite3_PLUGIN} ${JNI_LIBRARIES})
target_link_libraries(lime-static INTERFACE bctoolbox ${SOCI_sqlite3_PLUGIN} ${SOCI_LIBRARIES} ${JNI_LIBRARIES})
if(ENABLE_PROFILING)
set_target_properties(lime-static PROPERTIES LINK_FLAGS "-pg")
endif()
......@@ -71,7 +71,7 @@ if(ENABLE_SHARED)
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
)
target_link_libraries(lime PRIVATE bctoolbox ${SOCI_LIBRARIES} ${SOCI_sqlite3_PLUGIN} ${JNI_LIBRARIES})
target_link_libraries(lime PRIVATE bctoolbox ${SOCI_sqlite3_PLUGIN} ${SOCI_LIBRARIES} ${JNI_LIBRARIES})
if(APPLE)
if(IOS)
set(MIN_OS ${LINPHONE_IOS_DEPLOYMENT_TARGET})
......
......@@ -110,6 +110,9 @@ namespace lime {
user_not_found=0x06,
db_error=0x07,
bad_request=0x08,
server_failure=0x09,
resource_limit_reached=0x0a,
unknown_error_code=0xfe,
unset_error_code=0xff};
/* X3DH protocol messages builds */
/**
......@@ -420,6 +423,7 @@ namespace lime {
if (body.size()<X3DH_headerSize) {
LIME_LOGE<<"Got an invalid response from X3DH server"<<endl<< message_trace.str()<<endl<<" Invalid Incoming X3DH message";
if (callback) callback(lime::CallbackReturn::fail, "Got an invalid response from X3DH server");
LIME_LOGE<<message_trace.str()<<endl;
return false;
}
......@@ -427,6 +431,7 @@ namespace lime {
if (body[0] != static_cast<uint8_t>(X3DH_protocolVersion)) {
LIME_LOGE<<"X3DH server runs an other version of X3DH protocol(server "<<static_cast<unsigned int>(body[0])<<" - local "<<static_cast<unsigned int>(X3DH_protocolVersion)<<")"<<endl<<message_trace.str()<<endl<<" Invalid Incoming X3DH message";
if (callback) callback(lime::CallbackReturn::fail, "X3DH server and client protocol version mismatch");
LIME_LOGE<<message_trace.str()<<endl;
return false;
}
......@@ -490,6 +495,7 @@ namespace lime {
// retrieve the error code if needed
if (message_type == x3dh_message_type::error) {
if (body.size()<X3DH_headerSize+1) { // error message contains at least 1 byte of error code + possible message
LIME_LOGE<<message_trace.str()<<endl;
return false;
}
......@@ -527,8 +533,15 @@ namespace lime {
case static_cast<uint8_t>(x3dh_error_code::bad_request):
error_code = x3dh_error_code::bad_request;
break;
default: // unknown error code: invalid packet
return false;
case static_cast<uint8_t>(x3dh_error_code::server_failure):
error_code = x3dh_error_code::server_failure;
break;
case static_cast<uint8_t>(x3dh_error_code::resource_limit_reached):
error_code = x3dh_error_code::resource_limit_reached;
break;
default: // unknown error code - could be an updated server
LIME_LOGW<<"Receive unknown error code in X3DH message"<< static_cast<uint8_t>(body[X3DH_headerSize]);
error_code = x3dh_error_code::unknown_error_code;
}
}
LIME_LOGD<<message_trace.str()<<endl<<" Valid Incoming X3DH message";
......
......@@ -3986,6 +3986,89 @@ static void lime_multithread(void) {
}
}
/**
* Scenario: Server is set to accept 200 OPks per device
* - Create user alice with 500 OPks -> it shall fail
* - Create user alice with 100 OPks -> it shall pass
* - Add 100 OPks to alice -> it shall pass
* - Add 100 OPks to alice -> is shall fail
*/
static void lime_server_resource_limit_reached_test(const lime::CurveId curve, const std::string &dbBaseFilename, const std::string &x3dh_server_url) {
// create DB
std::string dbFilenameAlice{dbBaseFilename};
dbFilenameAlice.append(".alice.").append((curve==CurveId::c25519)?"C25519":"C448").append(".sqlite3");
remove(dbFilenameAlice.data()); // delete the database file if already exists
lime_tester::events_counters_t counters={};
int expected_success=0;
int expected_failure=0;
limeCallback callback([&counters](lime::CallbackReturn returnCode, std::string anythingToSay) {
if (returnCode == lime::CallbackReturn::success) {
counters.operation_success++;
} else {
counters.operation_failed++;
LIME_LOGE<<"Lime operation failed : "<<anythingToSay;
}
});
try {
// create Manager and device for alice
auto aliceManager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, X3DHServerPost));
auto aliceDeviceId = lime_tester::makeRandomDeviceName("alice.");
aliceManager->create_user(*aliceDeviceId, x3dh_server_url, curve, 500, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_failed, ++expected_failure,lime_tester::wait_for_timeout));
// Try again to create the device, with 100 OPks, it shall pass
aliceManager->create_user(*aliceDeviceId, x3dh_server_url, curve, 100, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success, ++expected_success,lime_tester::wait_for_timeout));
// call the update, set the serverLimit 200 and upload an other 100
aliceManager->update(callback, 200, 100);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success, ++expected_success,lime_tester::wait_for_timeout));
BC_ASSERT_EQUAL((int)lime_tester::get_OPks(dbFilenameAlice, *aliceDeviceId), 200, int, "%d");
// call the update, set the serverLimit 300 and upload an other 100 -> it shall fail but we have 300 OPks in DB
aliceManager->update(callback, 300, 100);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_failed, ++expected_failure,lime_tester::wait_for_timeout));
BC_ASSERT_EQUAL((int)lime_tester::get_OPks(dbFilenameAlice, *aliceDeviceId), 300, int, "%d");
// update again, with correct values, server already holds 200 keys, so the only effect would be to set the failed 100 OPks status to dispatched as they are not on server
aliceManager->update(callback, 125, 25);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success, ++expected_success,lime_tester::wait_for_timeout));
BC_ASSERT_EQUAL((int)lime_tester::get_OPks(dbFilenameAlice, *aliceDeviceId), 300, int, "%d");
// forward time by OPK_limboTime_days
aliceManager=nullptr; // destroy manager before modifying DB
lime_tester::forwardTime(dbFilenameAlice, lime::settings::OPk_limboTime_days+1);
aliceManager = std::unique_ptr<LimeManager>(new LimeManager(dbFilenameAlice, X3DHServerPost));
// update one last time, with correct values, server already holds 200 keys
// so the only effect would be to remove the OPk keys status was set to dispatch before we forward the time
// We now hold 200 keys
aliceManager->update(callback, 125, 25);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success, ++expected_success,lime_tester::wait_for_timeout));
BC_ASSERT_EQUAL((int)lime_tester::get_OPks(dbFilenameAlice, *aliceDeviceId), 200, int, "%d");
if (cleanDatabase) {
aliceManager->delete_user(*aliceDeviceId, callback);
BC_ASSERT_TRUE(lime_tester::wait_for(bc_stack,&counters.operation_success,++expected_success,lime_tester::wait_for_timeout));
remove(dbFilenameAlice.data());
}
} catch (BctbxException &e) {
LIME_LOGE << e;
BC_FAIL("");
}
}
static void lime_server_resource_limit_reached() {
#ifdef EC25519_ENABLED
lime_server_resource_limit_reached_test(lime::CurveId::c25519, "lime_server_resource_limit_reached", std::string("https://").append(lime_tester::test_x3dh_server_url).append(":").append(lime_tester::test_x3dh_c25519_server_port).data());
#endif
#ifdef EC448_ENABLED
lime_server_resource_limit_reached_test(lime::CurveId::c448, "lime_server_resource_limit_reached", std::string("https://").append(lime_tester::test_x3dh_server_url).append(":").append(lime_tester::test_x3dh_c448_server_port).data());
#endif
}
static test_t tests[] = {
TEST_NO_TAG("Basic", x3dh_basic),
......@@ -4008,7 +4091,8 @@ static test_t tests[] = {
TEST_NO_TAG("Encryption Policy", lime_encryptionPolicy),
TEST_NO_TAG("Encryption Policy Error", lime_encryptionPolicyError),
TEST_NO_TAG("Identity theft", lime_identity_theft),
TEST_NO_TAG("Multithread", lime_multithread)
TEST_NO_TAG("Multithread", lime_multithread),
TEST_NO_TAG("Server resource limit reached", lime_server_resource_limit_reached)
};
test_suite_t lime_lime_test_suite = {
......
......@@ -5,8 +5,8 @@
"main": "x3dh.js",
"dependencies": {
"rwlock": "^5.0.0",
"sqlite3": "^4.0.2",
"yargs": "^9.0.1"
"sqlite3": "^4.2.0",
"yargs": "^15.3.1"
},
"devDependencies": {},
"scripts": {
......
......@@ -86,6 +86,14 @@ const enum_curveId = {
CURVE448 : 2
};
// Resource abuse protection
// Setting to 0 disable the function
//lime_max_device_per_user does not apply to this test server as it is used to test the lime lib only and device id are not gruub in the tests
// Do not set this value too low, it shall not be lower than the server_low_limit+batch_size used by client - default is 100+25
const lime_max_opk_per_device = 200;
var curveId = enum_curveId.CURVE25519;
const X3DH_protocolVersion = 0x01;
......@@ -114,7 +122,9 @@ const enum_errorCodes = {
user_already_in : 0x05,
user_not_found : 0x06,
db_error : 0x07,
bad_request : 0x08
bad_request : 0x08,
server_failure : 0x09,
resource_limit_reached : 0x0a
};
const enum_keyBundleFlag = {
......@@ -317,6 +327,10 @@ https.createServer(options, (req, res) => {
// Now get the OPk count
var bufferIndex = X3DH_headerSize;
var OPk_number = body.readUInt16BE(bufferIndex+x3dh_expectedSize-2);
if ((lime_max_opk_per_device > 0) && (OPk_number > lime_max_opk_per_device)) { // too much OPk, reject registration
returnError(enum_errorCodes.resource_limit_reached, userId+" is trying to register itself with "+OPk_number+" OPks but server has a limit of "+lime_max_opk_per_device);
return;
}
// And check again the size with OPks
x3dh_expectedSize += OPk_number*(keySizes[curveId]['X_pub'] + 4);
if (body.length<X3DH_headerSize + x3dh_expectedSize) {
......@@ -482,6 +496,11 @@ https.createServer(options, (req, res) => {
console.log("It contains "+OPk_number+" keys");
if ((lime_max_opk_per_device > 0) && (OPk_number > lime_max_opk_per_device)) { // too much OPk, reject registration
returnError(enum_errorCodes.resource_limit_reached, userId+" is trying to insert "+OPk_number+" OPks but server has a limit of "+lime_max_opk_per_device);
return;
}
// check we have a matching user in DB
lock.writeLock(function (release) {
db.get("SELECT Uid FROM Users WHERE UserId = ?;", userId , function (err, row) {
......@@ -490,43 +509,107 @@ https.createServer(options, (req, res) => {
returnError(enum_errorCodes.user_not_found, "Post OPks but "+userId+" not found in db");
} else {
var Uid = row['Uid'];
// parse all OPks to be inserted
var OPks_param = [];
for (let i = 0; i < OPk_number; i++) {
var OPk = body.slice(bufferIndex, bufferIndex + keySizes[curveId]['X_pub']);
bufferIndex += keySizes[curveId]['X_pub'];
var OPk_id = body.readUInt32BE(bufferIndex); // SPk id is a 32 bits unsigned integer in Big endian
bufferIndex += 4;
OPks_param.push([Uid, OPk, OPk_id]);
}
// bulk insert of OPks
var stmt_ran = 0;
var stmt_success = 0;
var stmt_err_message = '';
db.run("begin transaction");
var stmt = db.prepare("INSERT INTO OPk(Uid, OPk, OPk_id) VALUES(?,?,?);")
for (let i=0; i<OPks_param.length; i++) {
param = OPks_param[i];
stmt.run(param, function(stmt_err, stmt_res){ // callback is called for each insertion, so we shall count errors and success
stmt_ran++;
if (stmt_err) {
stmt_err_message +=' ## '+stmt_err;
} else {
stmt_success++;
// Check we won't get over the maximum OPks allowed
if (lime_max_opk_per_device > 0) {
db.get("SELECT COUNT(OPK_id) as OPk_count FROM Users as u INNER JOIN OPk as o ON u.Uid=o.Uid WHERE UserId = ?;", userId, function (err, row) {
if (err) {
release();
returnError(enum_errorCodes.db_error, " Database error in postOPks by "+userId+" : "+err2);
return;
}
if (stmt_ran == OPk_number) { // and react only when we reach correct count
if (stmt_ran != stmt_success) {
db.run("rollback")
release();
returnError(enum_errorCodes.db_error, "Error while trying to insert OPk for user "+userId+". Backend says :"+stmt_err);
} else {
db.run("commit")
release();
returnOk(returnHeader);
}
var OPk_count = 0;
if (row != undefined) {
OPk_count = row['OPk_count'];
}
if (OPk_count+OPk_number > lime_max_opk_per_device) { // too much OPks
release();
returnError(enum_errorCodes.resource_limit_reached, userId+" is trying to insert "+OPk_number+" OPks but server has a limit of "+lime_max_opk_per_device+" and it already holds "+OPk_count);
return;
}
// parse all OPks to be inserted
var OPks_param = [];
for (let i = 0; i < OPk_number; i++) {
var OPk = body.slice(bufferIndex, bufferIndex + keySizes[curveId]['X_pub']);
bufferIndex += keySizes[curveId]['X_pub'];
var OPk_id = body.readUInt32BE(bufferIndex); // OPk id is a 32 bits unsigned integer in Big endian
bufferIndex += 4;
OPks_param.push([Uid, OPk, OPk_id]);
}
// bulk insert of OPks
var stmt_ran = 0;
var stmt_success = 0;
var stmt_err_message = '';
db.run("begin transaction");
var stmt = db.prepare("INSERT INTO OPk(Uid, OPk, OPk_id) VALUES(?,?,?);")
for (let i=0; i<OPks_param.length; i++) {
param = OPks_param[i];
stmt.run(param, function(stmt_err, stmt_res){ // callback is called for each insertion, so we shall count errors and success
stmt_ran++;
if (stmt_err) {
stmt_err_message +=' ## '+stmt_err;
} else {
stmt_success++;
}
if (stmt_ran == OPk_number) { // and react only when we reach correct count
if (stmt_ran != stmt_success) {
db.run("rollback")
release();
returnError(enum_errorCodes.db_error, "Error while trying to insert OPk for user "+userId+". Backend says :"+stmt_err);
} else {
db.run("commit")
release();
returnOk(returnHeader);
}
}
});
}
return;
});
} else { // Note : this code shall be factorized
// parse all OPks to be inserted
var OPks_param = [];
for (let i = 0; i < OPk_number; i++) {
var OPk = body.slice(bufferIndex, bufferIndex + keySizes[curveId]['X_pub']);
bufferIndex += keySizes[curveId]['X_pub'];
var OPk_id = body.readUInt32BE(bufferIndex); // OPk id is a 32 bits unsigned integer in Big endian
bufferIndex += 4;
OPks_param.push([Uid, OPk, OPk_id]);
}
// bulk insert of OPks
var stmt_ran = 0;
var stmt_success = 0;
var stmt_err_message = '';
db.run("begin transaction");
var stmt = db.prepare("INSERT INTO OPk(Uid, OPk, OPk_id) VALUES(?,?,?);")
for (let i=0; i<OPks_param.length; i++) {
param = OPks_param[i];
stmt.run(param, function(stmt_err, stmt_res){ // callback is called for each insertion, so we shall count errors and success
stmt_ran++;
if (stmt_err) {
stmt_err_message +=' ## '+stmt_err;
} else {
stmt_success++;
}
if (stmt_ran == OPk_number) { // and react only when we reach correct count
if (stmt_ran != stmt_success) {
db.run("rollback")
release();
returnError(enum_errorCodes.db_error, "Error while trying to insert OPk for user "+userId+". Backend says :"+stmt_err);
} else {
db.run("commit")
release();
returnOk(returnHeader);
}
}
});
}
}
}
});
......@@ -680,7 +763,7 @@ https.createServer(options, (req, res) => {
case enum_messageTypes.getSelfOPks:
console.log("Process a getSelfOPks Message from "+userId);
// check we have a matching user in DB
db.get("SELECT Uid FROM Users WHERE UserId = ?;", userId , function (errU, row) {
db.get("SELECT Uid FROM Users WHERE UserId = ?;", userId , function (err, row) {
if (row == undefined) { // user not found in DB
returnError(enum_errorCodes.user_not_found, "Get Self OPks but "+userId+" not found in db");
} else {
......