Initial Commit
This commit is contained in:
commit
8390564a37
47 changed files with 8243 additions and 0 deletions
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
INSTALL
|
||||
aclocal.m4
|
||||
*~
|
||||
*.swp
|
||||
configure
|
||||
Makefile.in
|
||||
Makefile
|
||||
build-aux
|
||||
m4
|
||||
autom4te.cache
|
||||
*.o
|
||||
*.lo
|
||||
*.la
|
||||
config.log
|
||||
config.status
|
||||
.deps
|
||||
.libs
|
||||
libtool
|
||||
compile
|
||||
depcomp
|
||||
missing
|
||||
install-sh
|
||||
config.guess
|
||||
config.h.in
|
||||
config.sub
|
||||
ltmain.sh
|
||||
stamp-h1
|
||||
.kdev4/
|
||||
config.h
|
||||
*.orig
|
||||
lime.kdev4
|
||||
?build*
|
3
AUTHORS
Normal file
3
AUTHORS
Normal file
|
@ -0,0 +1,3 @@
|
|||
Johan Pascal
|
||||
Belledonne Communications SARL <info@belledonne-communications.com>
|
||||
|
169
CMakeLists.txt
Normal file
169
CMakeLists.txt
Normal file
|
@ -0,0 +1,169 @@
|
|||
|
||||
############################################################################
|
||||
# CMakeLists.txt
|
||||
# Copyright (C) 2017 Belledonne Communications, Grenoble France
|
||||
#
|
||||
############################################################################
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(LIME VERSION 0.0.1 LANGUAGES C CXX)
|
||||
|
||||
set(LIME_SO_VERSION "0")
|
||||
|
||||
option(ENABLE_SHARED "Build shared library." ON)
|
||||
option(ENABLE_STATIC "Build static library." ON)
|
||||
option(ENABLE_STRICT "Build with strict compile options." YES)
|
||||
option(ENABLE_Curve25519 "Enable support of Curve 25519." YES)
|
||||
option(ENABLE_Curve448 "Enable support of Curve 448(goldilock)." YES)
|
||||
option(ENABLE_UNIT_TESTS "Enable compilation of unit tests." YES)
|
||||
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
if(NOT CPACK_GENERATOR AND NOT CMAKE_INSTALL_RPATH AND CMAKE_INSTALL_PREFIX)
|
||||
set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_FULL_LIBDIR})
|
||||
message(STATUS "Setting install rpath to ${CMAKE_INSTALL_RPATH}")
|
||||
endif()
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(CheckSymbolExists)
|
||||
include(CMakePushCheckState)
|
||||
|
||||
if(LINPHONE_BUILDER_GROUP_EXTERNAL_SOURCE_PATH_BUILDERS)
|
||||
include("${EP_bellesip_CONFIG_DIR}/BelleSIPConfig.cmake")
|
||||
set(BcToolbox_FIND_COMPONENTS tester)
|
||||
include("${EP_bctoolbox_CONFIG_DIR}/BcToolboxConfig.cmake")
|
||||
else()
|
||||
find_package(BcToolbox 0.5.1 REQUIRED OPTIONAL_COMPONENTS tester)
|
||||
find_package(BelleSIP REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(Soci REQUIRED)
|
||||
find_package(Sqlite3 REQUIRED)
|
||||
|
||||
set(LIME_LDFLAGS "${BELLESIP_LDFLAGS}")
|
||||
|
||||
include_directories(
|
||||
include/
|
||||
src/
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
if(MSVC)
|
||||
include_directories(${MSVC_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
set(LIME_INCLUDE_DIRS
|
||||
${BELLESIP_INCLUDE_DIRS}
|
||||
${BCTOOLBOX_CORE_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h)
|
||||
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/config.h PROPERTIES GENERATED ON)
|
||||
add_definitions("-DHAVE_CONFIG_H")
|
||||
|
||||
set(LIME_CPPFLAGS ${BELLESIP_CPPFLAGS} ${BCTOOLBOX_CPPFLAGS})
|
||||
if(LIME_CPPFLAGS)
|
||||
list(REMOVE_DUPLICATES LIME_CPPFLAGS)
|
||||
add_definitions(${LIME_CPPFLAGS})
|
||||
endif()
|
||||
add_definitions("-DLIME_EXPORTS")
|
||||
|
||||
set(STRICT_OPTIONS_CPP )
|
||||
set(STRICT_OPTIONS_C )
|
||||
set(STRICT_OPTIONS_CXX )
|
||||
set(STRICT_OPTIONS_OBJC )
|
||||
if(MSVC)
|
||||
if(ENABLE_STRICT)
|
||||
list(APPEND STRICT_OPTIONS_CPP "/WX")
|
||||
endif()
|
||||
else()
|
||||
list(APPEND STRICT_OPTIONS_CXX "-std=c++14 -O2 -g")
|
||||
#list(APPEND STRICT_OPTIONS_CPP "-Wall" "-Wuninitialized" "-Wno-error=deprecated-declarations") # turn off deprecated-declaration warning to avoid being flooded by soci.h
|
||||
list(APPEND STRICT_OPTIONS_CPP "-Wall" "-Wuninitialized" "-Wno-deprecated-declarations")
|
||||
list(APPEND STRICT_OPTIONS_C "-Wdeclaration-after-statement" "-Wstrict-prototypes" "-Wno-error=strict-prototypes")
|
||||
if(CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
||||
list(APPEND STRICT_OPTIONS_CPP "-Qunused-arguments" "-Wno-array-bounds")
|
||||
endif()
|
||||
if(APPLE)
|
||||
list(APPEND STRICT_OPTIONS_CPP "-Wno-error=unknown-warning-option" "-Qunused-arguments" "-Wno-tautological-compare" "-Wno-unused-function" "-Wno-array-bounds")
|
||||
list(APPEND STRICT_OPTIONS_CXX "-stdlib=libc++")
|
||||
endif()
|
||||
if(ENABLE_STRICT)
|
||||
list(APPEND STRICT_OPTIONS_CPP "-Werror" "-Wextra" "-Wno-unused-parameter" "-fno-strict-aliasing")
|
||||
endif()
|
||||
endif()
|
||||
if(STRICT_OPTIONS_CPP)
|
||||
list(REMOVE_DUPLICATES STRICT_OPTIONS_CPP)
|
||||
endif()
|
||||
if(STRICT_OPTIONS_C)
|
||||
list(REMOVE_DUPLICATES STRICT_OPTIONS_C)
|
||||
endif()
|
||||
|
||||
if(LINPHONE_BUILDER_GROUP_EXTERNAL_SOURCE_PATH_BUILDERS)
|
||||
set(EXPORT_TARGETS_NAME "LinphoneBuilder")
|
||||
else()
|
||||
set(EXPORT_TARGETS_NAME "lime")
|
||||
endif()
|
||||
|
||||
if (ENABLE_Curve25519)
|
||||
add_definitions("-DEC25519_ENABLED")
|
||||
message(STATUS "Support Curve 25519")
|
||||
endif()
|
||||
|
||||
if (ENABLE_Curve448)
|
||||
add_definitions("-DEC448_ENABLED")
|
||||
message(STATUS "Support Curve 448")
|
||||
endif()
|
||||
|
||||
add_subdirectory(include)
|
||||
add_subdirectory(src)
|
||||
if(ENABLE_UNIT_TESTS AND BCTOOLBOX_TESTER_FOUND)
|
||||
enable_testing()
|
||||
add_subdirectory(tester)
|
||||
endif()
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
export(EXPORT ${EXPORT_TARGETS_NAME}Targets
|
||||
FILE "${CMAKE_CURRENT_BINARY_DIR}/LimeTargets.cmake"
|
||||
)
|
||||
configure_file(cmake/LimeConfig.cmake.in
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/LimeConfig.cmake"
|
||||
@ONLY
|
||||
)
|
||||
set(ConfigPackageLocation share/Lime/cmake)
|
||||
install(EXPORT ${EXPORT_TARGETS_NAME}Targets
|
||||
FILE LimeTargets.cmake
|
||||
DESTINATION ${ConfigPackageLocation}
|
||||
)
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/LimeConfig.cmake"
|
||||
DESTINATION ${ConfigPackageLocation}
|
||||
)
|
||||
|
||||
# CPack settings
|
||||
set(CPACK_PACKAGE_NAME "lime")
|
||||
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
|
||||
set(CPACK_SOURCE_GENERATOR "TGZ")
|
||||
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
|
||||
set(CPACK_SOURCE_IGNORE_FILES
|
||||
"^${CMAKE_BINARY_DIR}"
|
||||
"/\\\\..+"
|
||||
)
|
||||
|
||||
include(CPack)
|
674
COPYING
Normal file
674
COPYING
Normal file
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
0
ChangeLog
Normal file
0
ChangeLog
Normal file
2
NEWS
Normal file
2
NEWS
Normal file
|
@ -0,0 +1,2 @@
|
|||
lime-0.0.1 - October 22nd 2017
|
||||
* Initial release
|
45
README.md
Normal file
45
README.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
Lime
|
||||
=======
|
||||
|
||||
Lime is a C++ library implementing Open Whisper System Signal protocol :
|
||||
Sesame, double ratchet and X3DH.
|
||||
It is designed to work jointly with *Linphone*[3]
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
- *bctoolbox[1]* : portability layer, built with Elliptic Curve Cryptography
|
||||
- *bellesip[2]* : for the https stack
|
||||
- *soci-sqlite3* : Db access
|
||||
|
||||
|
||||
Build instrucitons
|
||||
------------------
|
||||
|
||||
cmake . -DCMAKE_INSTALL_PREFIX=<install_prefix> -DCMAKE_PREFIX_PATH=<search_prefix>
|
||||
|
||||
make
|
||||
make install
|
||||
|
||||
Testing
|
||||
-------
|
||||
To test on local machine, you must run a local X3DH server.
|
||||
A nodejs version of UNSECURE X3DH server is provided in tester/server
|
||||
See README from this directory for instructions.
|
||||
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
- `CMAKE_INSTALL_PREFIX=<string>` : installation prefix
|
||||
- `CMAKE_PREFIX_PATH=<string>` : prefix where depedencies are installed
|
||||
- `ENABLE_UNIT_TESTS=NO` : do not compile non-regression tests
|
||||
- `ENABLE_SHARED=NO` : do not build the shared library.
|
||||
- `ENABLE_STATIC=NO` : do not build the static library.
|
||||
- `ENABLE_STRICT=NO` : do not build with strict complier flags e.g. `-Wall -Werror`
|
||||
- `ENABLE_Curve25519` : Enable support of Curve 25519.
|
||||
- `ENABLE_Curve448` : Enable support of Curve 448.
|
||||
------------------
|
||||
|
||||
- [1] bctoolbox: git://git.linphone.org/bctoolbox.git or <http://www.linphone.org/releases/sources/bctoolbox>
|
||||
- [2] belle-sip: git://git.linphone.org/belle-sip.git or <https://www.linphone.org/releases/sources/belle-sip>
|
||||
- [3] linphone-desktop: git://git.linphone.org/linphone-desktop.git
|
16
build/android/Android.mk
Normal file
16
build/android/Android.mk
Normal file
|
@ -0,0 +1,16 @@
|
|||
LOCAL_PATH:= $(call my-dir)/../../src
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_CPP_EXTENSION := .cpp
|
||||
|
||||
LOCAL_C_INCLUDES += \
|
||||
$(LOCAL_PATH)/../include \
|
||||
$(LOCAL_PATH)/../../lime/include \
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
lime_x3dh.cpp
|
||||
|
||||
LOCAL_MODULE:= liblime
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
87
cmake/FindSoci.cmake
Normal file
87
cmake/FindSoci.cmake
Normal file
|
@ -0,0 +1,87 @@
|
|||
###############################################################################
|
||||
# CMake module to search for SOCI library
|
||||
#
|
||||
# WARNING: This module is experimental work in progress.
|
||||
#
|
||||
# This module defines:
|
||||
# SOCI_INCLUDE_DIRS = include dirs to be used when using the soci library
|
||||
# SOCI_LIBRARIES = full path to the soci library
|
||||
# SOCI_VERSION = the soci version found (not yet. soci does not provide that info.)
|
||||
# SOCI_FOUND = true if soci was found
|
||||
#
|
||||
# For each component you specify in find_package(), the following variables are set.
|
||||
#
|
||||
# SOCI_${COMPONENT}_PLUGIN = full path to the soci plugin
|
||||
# SOCI_${COMPONENT}_FOUND
|
||||
#
|
||||
# Copyright (c) 2011 Michael Jansen <info@michael-jansen.biz>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
#
|
||||
###############################################################################
|
||||
#
|
||||
### Global Configuration Section
|
||||
#
|
||||
SET(_SOCI_ALL_PLUGINS mysql sqlite3)
|
||||
SET(_SOCI_REQUIRED_VARS SOCI_INCLUDE_DIRS SOCI_LIBRARIES)
|
||||
|
||||
#
|
||||
### FIRST STEP: Find the soci headers.
|
||||
#
|
||||
FIND_PATH(SOCI_INCLUDE_DIRS soci/soci.h
|
||||
DOC "Soci (http://soci.sourceforge.net) include directory")
|
||||
MARK_AS_ADVANCED(SOCI_INCLUDE_DIRS)
|
||||
|
||||
#
|
||||
### SECOND STEP: Find the soci core library. Respect LIB_SUFFIX
|
||||
#
|
||||
FIND_LIBRARY(SOCI_LIBRARIES
|
||||
NAMES soci_core
|
||||
PATH_SUFFIXES lib lib64)
|
||||
MARK_AS_ADVANCED(SOCI_LIBRARIES)
|
||||
|
||||
GET_FILENAME_COMPONENT(SOCI_LIBRARY_DIR ${SOCI_LIBRARIES} PATH)
|
||||
MARK_AS_ADVANCED(SOCI_LIBRARY_DIR)
|
||||
|
||||
#
|
||||
### THIRD STEP: Find all installed plugins if the library was found
|
||||
#
|
||||
IF(SOCI_INCLUDE_DIRS AND SOCI_LIBRARIES)
|
||||
|
||||
MESSAGE(STATUS "Soci found: Looking for plugins")
|
||||
FOREACH(plugin IN LISTS _SOCI_ALL_PLUGINS)
|
||||
|
||||
FIND_LIBRARY(
|
||||
SOCI_${plugin}_PLUGIN
|
||||
NAMES soci_${plugin}
|
||||
PATH_SUFFIXES lib lib64)
|
||||
MARK_AS_ADVANCED(SOCI_${plugin}_PLUGIN)
|
||||
|
||||
IF(SOCI_${plugin}_PLUGIN)
|
||||
MESSAGE(STATUS " * Plugin ${plugin} found ${SOCI_${plugin}_PLUGIN}.")
|
||||
SET(SOCI_${plugin}_FOUND True)
|
||||
ELSE()
|
||||
MESSAGE(STATUS " * Plugin ${plugin} not found.")
|
||||
SET(SOCI_${plugin}_FOUND False)
|
||||
ENDIF()
|
||||
|
||||
ENDFOREACH()
|
||||
|
||||
#
|
||||
### FOURTH CHECK: Check if the required components were all found
|
||||
#
|
||||
FOREACH(component ${Soci_FIND_COMPONENTS})
|
||||
IF(NOT SOCI_${component}_FOUND)
|
||||
MESSAGE(SEND_ERROR "Required component ${component} not found. It seems that Soci was built without support of ${component}, consider rebuilding it.")
|
||||
ENDIF()
|
||||
ENDFOREACH()
|
||||
|
||||
ENDIF()
|
||||
|
||||
#
|
||||
### ADHERE TO STANDARDS
|
||||
#
|
||||
include(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Soci DEFAULT_MSG ${_SOCI_REQUIRED_VARS})
|
||||
|
57
cmake/FindSqlite3.cmake
Normal file
57
cmake/FindSqlite3.cmake
Normal file
|
@ -0,0 +1,57 @@
|
|||
############################################################################
|
||||
# FindSqlite3.cmake
|
||||
# Copyright (C) 2014 Belledonne Communications, Grenoble France
|
||||
#
|
||||
############################################################################
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
############################################################################
|
||||
#
|
||||
# - Find the sqlite3 include file and library
|
||||
#
|
||||
# SQLITE3_FOUND - system has sqlite3
|
||||
# SQLITE3_INCLUDE_DIRS - the sqlite3 include directory
|
||||
# SQLITE3_LIBRARIES - The libraries needed to use sqlite3
|
||||
|
||||
if(APPLE AND NOT IOS)
|
||||
set(SQLITE3_HINTS "/usr")
|
||||
endif()
|
||||
if(SQLITE3_HINTS)
|
||||
set(SQLITE3_LIBRARIES_HINTS "${SQLITE3_HINTS}/lib")
|
||||
endif()
|
||||
|
||||
find_path(SQLITE3_INCLUDE_DIRS
|
||||
NAMES sqlite3.h
|
||||
HINTS "${SQLITE3_HINTS}"
|
||||
PATH_SUFFIXES include
|
||||
)
|
||||
|
||||
if(SQLITE3_INCLUDE_DIRS)
|
||||
set(HAVE_SQLITE3_H 1)
|
||||
endif()
|
||||
|
||||
find_library(SQLITE3_LIBRARIES
|
||||
NAMES sqlite3
|
||||
HINTS "${SQLITE3_LIBRARIES_HINTS}"
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Sqlite3
|
||||
DEFAULT_MSG
|
||||
SQLITE3_INCLUDE_DIRS SQLITE3_LIBRARIES HAVE_SQLITE3_H
|
||||
)
|
||||
|
||||
mark_as_advanced(SQLITE3_INCLUDE_DIRS SQLITE3_LIBRARIES HAVE_SQLITE3_H)
|
61
cmake/LimeConfig.cmake.in
Normal file
61
cmake/LimeConfig.cmake.in
Normal file
|
@ -0,0 +1,61 @@
|
|||
############################################################################
|
||||
# LimeConfig.cmake
|
||||
# Copyright (C) 2017 Belledonne Communications, Grenoble France
|
||||
#
|
||||
############################################################################
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
############################################################################
|
||||
#
|
||||
# Config file for the lime package.
|
||||
# It defines the following variables:
|
||||
#
|
||||
# LIME_FOUND - system has lime
|
||||
# LIME_INCLUDE_DIRS - the lime include directory
|
||||
# LIME_LIBRARIES - The libraries needed to use lime
|
||||
# LIME_CPPFLAGS - The compilation flags needed to use lime
|
||||
|
||||
if(NOT LINPHONE_BUILDER_GROUP_EXTERNAL_SOURCE_PATH_BUILDERS)
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/LimeTargets.cmake")
|
||||
endif()
|
||||
|
||||
if(@ENABLE_SHARED@)
|
||||
set(LIME_TARGETNAME lime)
|
||||
set(LIME_LIBRARIES ${LIME_TARGETNAME})
|
||||
else()
|
||||
set(LIME_TARGETNAME lime-static)
|
||||
if(TARGET ${LIME_TARGETNAME})
|
||||
if(LINPHONE_BUILDER_GROUP_EXTERNAL_SOURCE_PATH_BUILDERS)
|
||||
set(LIME_LIBRARIES ${LIME_TARGETNAME})
|
||||
else()
|
||||
get_target_property(LIME_LIBRARIES ${LIME_TARGETNAME} LOCATION)
|
||||
endif()
|
||||
get_target_property(LIME_LINK_LIBRARIES ${LIME_TARGETNAME} INTERFACE_LINK_LIBRARIES)
|
||||
if(LIME_LINK_LIBRARIES)
|
||||
list(APPEND LIME_LIBRARIES ${LIME_LINK_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
get_target_property(LIME_INCLUDE_DIRS ${LIME_TARGETNAME} INTERFACE_INCLUDE_DIRECTORIES)
|
||||
if(LINPHONE_BUILDER_GROUP_EXTERNAL_SOURCE_PATH_BUILDERS)
|
||||
list(INSERT LIME_INCLUDE_DIRS 0 "${EP_lime_INCLUDE_DIR}")
|
||||
else()
|
||||
list(INSERT LIME_INCLUDE_DIRS 0 "@CMAKE_INSTALL_FULL_INCLUDEDIR@")
|
||||
endif()
|
||||
list(REMOVE_DUPLICATES LIME_INCLUDE_DIRS)
|
||||
|
||||
set(LIME_CPPFLAGS @LIME_CPPFLAGS@)
|
||||
set(LIME_FOUND 1)
|
29
config.h.cmake
Normal file
29
config.h.cmake
Normal file
|
@ -0,0 +1,29 @@
|
|||
/***************************************************************************
|
||||
* config.h.cmake
|
||||
* Copyright (C) 2017 Belledonne Communications, Grenoble France
|
||||
*
|
||||
****************************************************************************
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#define LIME_MAJOR_VERSION ${LIME_MAJOR_VERSION}
|
||||
#define LIME_MINOR_VERSION ${LIME_MINOR_VERSION}
|
||||
#define LIME_MICRO_VERSION ${LIME_MICRO_VERSION}
|
||||
#define LIME_VERSION "${LIME_VERSION}"
|
||||
|
||||
#cmakedefine HAVE_BCUNIT_BCUNIT_H 1
|
||||
#cmakedefine HAVE_CU_GET_SUITE 1
|
35
include/CMakeLists.txt
Normal file
35
include/CMakeLists.txt
Normal file
|
@ -0,0 +1,35 @@
|
|||
############################################################################
|
||||
# CMakeLists.txt
|
||||
# Copyright (C) 2017 Belledonne Communications, Grenoble France
|
||||
#
|
||||
############################################################################
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
set(HEADER_FILES
|
||||
lime.hpp
|
||||
)
|
||||
|
||||
set(LIME_HEADER_FILES )
|
||||
foreach(HEADER_FILE ${HEADER_FILES})
|
||||
list(APPEND LIME_HEADER_FILES "${CMAKE_CURRENT_LIST_DIR}/lime/${HEADER_FILE}")
|
||||
endforeach()
|
||||
set(LIME_HEADER_FILES ${LIME_HEADER_FILES} PARENT_SCOPE)
|
||||
|
||||
install(FILES ${LIME_HEADER_FILES}
|
||||
DESTINATION include/lime
|
||||
PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
|
75
include/lime/lime.hpp
Normal file
75
include/lime/lime.hpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
lime.hpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef lime_hpp
|
||||
#define lime_hpp
|
||||
|
||||
#include <memory> // unique_ptr
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "belle-sip/belle-sip.h"
|
||||
|
||||
namespace lime {
|
||||
|
||||
/* This enum identifies the elliptic curve used in lime, the values assigned are used in localStorage and X3DH server
|
||||
* so do not modify it or we'll loose sync with existing DB and X3DH server */
|
||||
enum class CurveId : uint8_t {unset=0, c25519=1, c448=2};
|
||||
|
||||
/* Struct used to manage recipient list for encrypt function input: give a recipient GRUU and get it back with the header which must be sent to recipient with the cipher text*/
|
||||
struct recipientData {
|
||||
std::string deviceId; // recipient deviceId (shall be GRUU)
|
||||
std::vector<uint8_t> cipherHeader; // after encrypt calls back, it will hold the header targeted to the specified recipient. This header may contain an X3DH init message.
|
||||
recipientData(std::string deviceId) : deviceId{deviceId}, cipherHeader{} {};
|
||||
};
|
||||
|
||||
/* Enum of what a Lime callback could possibly say */
|
||||
enum class callbackReturn : uint8_t {success, fail};
|
||||
// a callback function must return a code and may return a string(could actually be empty) to detail what's happening
|
||||
// callback is used on every operation possibly involving a connection to X3DH server: create_user, delete_user, encrypt
|
||||
using limeCallback = std::function<void(lime::callbackReturn, std::string)>;
|
||||
|
||||
|
||||
/* Forward declare the class managing one lime user*/
|
||||
class LimeGeneric;
|
||||
|
||||
/* class to manage and cache Lime objects(its one per user), then adressed using their userId (GRUU) */
|
||||
class LimeManager {
|
||||
private :
|
||||
std::unordered_map<std::string, std::shared_ptr<LimeGeneric>> m_users_cache; // cache of already opened Lime Session, identified by user Id (GRUU)
|
||||
std::string m_db_access; // DB access information forwarded to SOCI to correctly access database
|
||||
belle_http_provider_t *m_http_provider;
|
||||
public :
|
||||
void create_user(const std::string &userId, const std::string &x3dhServerUrl, const lime::CurveId curve, const limeCallback &callback);
|
||||
void delete_user(const std::string &userId, const limeCallback &callback);
|
||||
void encrypt(const std::string &localUserId, std::shared_ptr<const std::string> recipientUserId, std::shared_ptr<std::vector<recipientData>> recipients, std::shared_ptr<const std::vector<uint8_t>> plainMessage, std::shared_ptr<std::vector<uint8_t>> cipherMessage, const limeCallback &callback);
|
||||
bool decrypt(const std::string &localUserId, const std::string &recipientUserId, const std::string &senderDeviceId, const std::vector<uint8_t> &cipherHeader, const std::vector<uint8_t> &cipherMessage, std::vector<uint8_t> &plainMessage);
|
||||
LimeManager() = delete; // no manager without Database and http provider
|
||||
LimeManager(const LimeManager&) = delete; // no copy constructor
|
||||
LimeManager operator=(const LimeManager &) = delete; // nor copy operator
|
||||
/**
|
||||
* @brief Lime Manager constructor
|
||||
*
|
||||
* @param[in] db_access string used to access DB: can be filename for sqlite3 or access params for mysql, directly forwarded to SOCI session opening
|
||||
* @param[in] http_provider An http provider used to access X3DH server, no scheduling is done on it internally
|
||||
*/
|
||||
LimeManager(const std::string &db_access, belle_http_provider_t *http_provider)
|
||||
: m_users_cache{}, m_db_access{db_access}, m_http_provider{http_provider} {};
|
||||
|
||||
~LimeManager() = default;
|
||||
};
|
||||
} //namespace lime
|
||||
#endif /* lime_hpp */
|
9
lime.pc.in
Normal file
9
lime.pc.in
Normal file
|
@ -0,0 +1,9 @@
|
|||
# This is a comment
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
|
||||
Name: @PROJECT_NAME@
|
||||
Description: Lime is an Instant Messaging encryption library implementing Open Whisper System Sesame, Double Ratchet and X3DH protocols
|
||||
Version: @PROJECT_VERSION@
|
||||
Libs: -L@CMAKE_INSTALL_FULL_LIBDIR@ -llime
|
||||
Libs.private: @LIBS_PRIVATE@
|
||||
Cflags: -I@CMAKE_INSTALL_FULL_INCLUDEDIR@
|
92
src/CMakeLists.txt
Normal file
92
src/CMakeLists.txt
Normal file
|
@ -0,0 +1,92 @@
|
|||
############################################################################
|
||||
# CMakeLists.txt
|
||||
# Copyright (C) 2017 Belledonne Communications, Grenoble France
|
||||
#
|
||||
############################################################################
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
set(LIME_HEADER_FILES
|
||||
lime_utils.hpp
|
||||
lime_keys.hpp
|
||||
lime_impl.hpp
|
||||
lime_x3dh_protocol.hpp
|
||||
lime_localStorage.hpp
|
||||
lime_double_ratchet.hpp
|
||||
lime_double_ratchet_protocol.hpp
|
||||
lime_lime.hpp
|
||||
)
|
||||
set(LIME_SOURCE_FILES_C )
|
||||
set(LIME_SOURCE_FILES_CXX
|
||||
lime.cpp
|
||||
lime_keys.cpp
|
||||
lime_x3dh.cpp
|
||||
lime_x3dh_protocol.cpp
|
||||
lime_localStorage.cpp
|
||||
lime_double_ratchet.cpp
|
||||
lime_double_ratchet_protocol.cpp
|
||||
lime_manager.cpp
|
||||
)
|
||||
|
||||
bc_apply_compile_flags(LIME_SOURCE_FILES_C STRICT_OPTIONS_CPP STRICT_OPTIONS_C)
|
||||
bc_apply_compile_flags(LIME_SOURCE_FILES_CXX STRICT_OPTIONS_CPP STRICT_OPTIONS_CXX)
|
||||
|
||||
if(ENABLE_STATIC)
|
||||
add_library(lime-static STATIC ${LIME_HEADER_FILES} ${LIME_SOURCE_FILES_C} ${LIME_SOURCE_FILES_CXX})
|
||||
set_target_properties(lime-static PROPERTIES OUTPUT_NAME lime)
|
||||
target_include_directories(lime-static PUBLIC ${BCTOOLBOX_INCLUDE_DIRS} ${SOCI_INCLUDE_DIRS} ${SOCI_INCLUDE_DIRS}/soci)
|
||||
target_link_libraries(lime-static INTERFACE ${BCTOOLBOX_CORE_LIBRARIES} ${BELLESIP_LIBRARIES} ${SOCI_LIBRARIES} ${SOCI_sqlite3_PLUGIN} ${SQLITE3_LIBRARIES})
|
||||
endif()
|
||||
if(ENABLE_SHARED)
|
||||
add_library(lime SHARED ${LIME_HEADER_FILES} ${LIME_SOURCE_FILES_C} ${LIME_SOURCE_FILES_CXX})
|
||||
if(APPLE)
|
||||
set_target_properties(lime PROPERTIES LINK_FLAGS "-stdlib=libc++")
|
||||
endif()
|
||||
set_target_properties(lime PROPERTIES VERSION ${LIME_SO_VERSION})
|
||||
target_include_directories(lime PUBLIC ${BCTOOLBOX_INCLUDE_DIRS} ${SOCI_INCLUDE_DIRS} ${SOCI_INCLUDE_DIRS}/soci)
|
||||
target_link_libraries(lime PRIVATE ${BCTOOLBOX_CORE_LIBRARIES} ${BELLESIP_LIBRARIES} ${SOCI_LIBRARIES} ${SOCI_sqlite3_PLUGIN} ${SQLITE3_LIBRARIES})
|
||||
if(MSVC)
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/lime.pdb
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_STATIC)
|
||||
install(TARGETS lime-static EXPORT ${EXPORT_TARGETS_NAME}Targets
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
|
||||
)
|
||||
endif()
|
||||
if(ENABLE_SHARED)
|
||||
install(TARGETS lime EXPORT ${EXPORT_TARGETS_NAME}Targets
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
|
||||
)
|
||||
endif()
|
||||
|
||||
install(FILES ${LIME_HEADER_FILES}
|
||||
DESTINATION include/lime
|
||||
PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
|
||||
)
|
367
src/lime.cpp
Normal file
367
src/lime.cpp
Normal file
|
@ -0,0 +1,367 @@
|
|||
/*
|
||||
lime.cpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define BCTBX_LOG_DOMAIN "lime"
|
||||
#include <bctoolbox/logging.h>
|
||||
|
||||
#include "lime/lime.hpp"
|
||||
|
||||
#include "lime_impl.hpp"
|
||||
#include "bctoolbox/exception.hh"
|
||||
#include "lime_double_ratchet.hpp"
|
||||
#include "lime_double_ratchet_protocol.hpp"
|
||||
|
||||
using namespace::std;
|
||||
|
||||
namespace lime {
|
||||
|
||||
|
||||
/****************************************************************************/
|
||||
/* */
|
||||
/* Members helpers functions (privates) */
|
||||
/* */
|
||||
/****************************************************************************/
|
||||
|
||||
/****************************************************************************/
|
||||
/* */
|
||||
/* Constructors */
|
||||
/* */
|
||||
/****************************************************************************/
|
||||
/**
|
||||
* @brief Load user constructor
|
||||
* before calling this constructor, user existence in DB is checked and its Uid retrieved
|
||||
* just load it into Lime class
|
||||
*
|
||||
* @param[in/out] localStorage pointer to DB accessor
|
||||
* @param[in] userId user Id(shall be GRUU), stored in the structure
|
||||
* @param[in] Uid the DB internal Id for this user, speed up DB operations by holding it in DB
|
||||
* @param[in] url URL of the X3DH key server used to publish our keys(retrieved from DB)
|
||||
*/
|
||||
template <typename Curve>
|
||||
Lime<Curve>::Lime(std::unique_ptr<lime::Db> &&localStorage, const std::string &userId, const std::string &url, belle_http_provider_t *http_provider, const long int Uid)
|
||||
: m_RNG{bctbx_rng_context_new()}, m_selfDeviceId{userId},
|
||||
m_Ik{}, m_Ik_loaded(false),
|
||||
m_localStorage(std::move(localStorage)), m_db_Uid{Uid},
|
||||
m_http_provider{http_provider}, m_X3DH_Server_URL{url},
|
||||
m_DR_sessions_cache{}, m_ongoing_encryption{nullptr}, m_encryption_queue{}
|
||||
{ }
|
||||
|
||||
|
||||
/**
|
||||
* @brief Create user constructor
|
||||
* Create a user in DB, if already existing, throw exception
|
||||
*
|
||||
* @param[in/out] localStorage pointer to DB accessor
|
||||
* @param[in] userId user Id(shall be GRUU), stored in the structure
|
||||
* @param[in] url URL of the X3DH key server used to publish our keys
|
||||
*/
|
||||
template <typename Curve>
|
||||
Lime<Curve>::Lime(std::unique_ptr<lime::Db> &&localStorage, const std::string &userId, const std::string &url, belle_http_provider_t *http_provider)
|
||||
: m_RNG{bctbx_rng_context_new()}, m_selfDeviceId{userId},
|
||||
m_Ik{}, m_Ik_loaded(false),
|
||||
m_localStorage(std::move(localStorage)), m_db_Uid{0},
|
||||
m_http_provider{http_provider}, m_X3DH_Server_URL{url},
|
||||
m_DR_sessions_cache{}, m_ongoing_encryption{nullptr}, m_encryption_queue{}
|
||||
{
|
||||
try {
|
||||
create_user();
|
||||
} catch (...) { // if createUser throw an exception we must clean ressource allocated C style by constructor
|
||||
bctbx_rng_context_free(m_RNG);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Curve>
|
||||
Lime<Curve>::~Lime() {
|
||||
bctbx_rng_context_free(m_RNG);
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* */
|
||||
/* Public API */
|
||||
/* */
|
||||
/****************************************************************************/
|
||||
/**
|
||||
* @brief Publish on X3DH server the user, it is performed just after creation in local storage
|
||||
* this will, on success, trigger generation and sending of SPk and OPks for our new user
|
||||
*
|
||||
* @param[in] callback call when completed
|
||||
*/
|
||||
template <typename Curve>
|
||||
void Lime<Curve>::publish_user(const limeCallback &callback) {
|
||||
callbackUserData<Curve> *userData = new callbackUserData<Curve>{this->shared_from_this(), callback, true};
|
||||
get_SelfIdentityKey(); // make sure our Ik is loaded in object
|
||||
std::vector<uint8_t> X3DHmessage{};
|
||||
x3dh_protocol::buildMessage_registerUser<Curve>(X3DHmessage, m_Ik.publicKey());
|
||||
postToX3DHServer(userData, X3DHmessage);
|
||||
}
|
||||
|
||||
template <typename Curve>
|
||||
void Lime<Curve>::delete_user(const limeCallback &callback) {
|
||||
// delete user from local Storage
|
||||
m_localStorage->delete_LimeUser(m_selfDeviceId);
|
||||
|
||||
// delete user from server
|
||||
callbackUserData<Curve> *userData = new callbackUserData<Curve>{this->shared_from_this(), callback};
|
||||
std::vector<uint8_t> X3DHmessage{};
|
||||
x3dh_protocol::buildMessage_deleteUser<Curve>(X3DHmessage);
|
||||
postToX3DHServer(userData, X3DHmessage);
|
||||
}
|
||||
|
||||
template <typename Curve>
|
||||
void Lime<Curve>::encrypt(std::shared_ptr<const std::string> recipientUserId, std::shared_ptr<std::vector<recipientData>> recipients, std::shared_ptr<const std::vector<uint8_t>> plainMessage, std::shared_ptr<std::vector<uint8_t>> cipherMessage, const limeCallback &callback) {
|
||||
bctbx_debug("encrypt from %s to %ld recipients", m_selfDeviceId.data(), recipients->size());
|
||||
/* Check if we have all the Double Ratcher sessions ready or shall we go for an X3DH */
|
||||
std::vector<std::string> missingPeers; /* vector of userId(GRUU) which are requested to perform X3DH before the encryption can occurs */
|
||||
|
||||
/* Create the appropriate recipient infos and fill it with sessions found in cache */
|
||||
std::vector<recipientInfos<Curve>> internal_recipients{};
|
||||
for (auto &recipient : *recipients) {
|
||||
auto sessionElem = m_DR_sessions_cache.find(recipient.deviceId);
|
||||
if (sessionElem != m_DR_sessions_cache.end()) { // session is in cache
|
||||
internal_recipients.emplace_back(recipient.deviceId, sessionElem->second);
|
||||
} else { // session is not in cache, just create it and the session ptr will be a nullptr
|
||||
internal_recipients.emplace_back(recipient.deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
/* try to load all the session that are not in cache */
|
||||
std::vector<std::string> missing_devices{};
|
||||
cache_DR_sessions(internal_recipients, missing_devices);
|
||||
|
||||
/* If we are still missing session we must ask the X3DH server for key bundles */
|
||||
if (missing_devices.size()>0) {
|
||||
// create a new callbackUserData, it shall be then deleted in callback, store in all shared_ptr to input/output values needed to call this encrypt function
|
||||
auto userData = make_shared<callbackUserData<Curve>>(this->shared_from_this(), callback, recipientUserId, recipients, plainMessage, cipherMessage);
|
||||
if (m_ongoing_encryption == nullptr) { // no ongoing asynchronous encryption process it
|
||||
m_ongoing_encryption = userData;
|
||||
} else { // some one else is expecting X3DH server response, enqueue this request
|
||||
m_encryption_queue.push(userData);
|
||||
return;
|
||||
}
|
||||
// retrieve bundles from X3DH server, when they arrive, it will run the X3DH initiation and create the DR sessions
|
||||
std::vector<uint8_t> X3DHmessage{};
|
||||
x3dh_protocol::buildMessage_getPeerBundles<Curve>(X3DHmessage, missing_devices);
|
||||
// use the raw pointer to userData as it is passed to belle-sip C callbacks but store it in current object so it is not destroyed
|
||||
postToX3DHServer(userData.get(), X3DHmessage);
|
||||
// use the raw pointer to userData as it is passed to belle-sip C callbacks but store it in current object so it is not destroyed
|
||||
//X3DH_get_peerBundles(userData.get(), missing_devices);
|
||||
} else { // got everyone, encrypt
|
||||
encryptMessage(internal_recipients, *plainMessage, *recipientUserId, m_selfDeviceId, *cipherMessage);
|
||||
// move cipher headers to the input/output structure
|
||||
for (size_t i=0; i<recipients->size(); i++) {
|
||||
(*recipients)[i].cipherHeader = std::move(internal_recipients[i].cipherHeader);
|
||||
}
|
||||
if (callback) callback(lime::callbackReturn::success, "");
|
||||
// is there no one in an asynchronous encryption process and do we have something in encryption queue to process
|
||||
if (m_ongoing_encryption == nullptr && !m_encryption_queue.empty()) { // may happend when an encryption was queued but session was created by a previously queued encryption request
|
||||
auto userData = m_encryption_queue.front();
|
||||
m_encryption_queue.pop(); // remove it from queue and do it
|
||||
encrypt(userData->recipientUserId, userData->recipients, userData->plainMessage, userData->cipherMessage, userData->callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Curve>
|
||||
bool Lime<Curve>::decrypt(const std::string &recipientUserId, const std::string &senderDeviceId, const std::vector<uint8_t> &cipherHeader, const std::vector<uint8_t> &cipherMessage, std::vector<uint8_t> &plainMessage) {
|
||||
bctbx_debug("decrypt from %s to %s", senderDeviceId.data(), recipientUserId.data());
|
||||
// do we have any session (loaded or not) matching that senderDeviceId ?
|
||||
auto sessionElem = m_DR_sessions_cache.find(senderDeviceId);
|
||||
auto db_sessionIdInCache = 0; // this would be the db_sessionId of the session stored in cache if there is one, no session has the Id 0
|
||||
if (sessionElem != m_DR_sessions_cache.end()) { // session is in cache, it is the active one, just give it a try
|
||||
db_sessionIdInCache = sessionElem->second->dbSessionId();
|
||||
std::vector<std::shared_ptr<DR<Curve>>> DRSessions{1, sessionElem->second}; // copy the session pointer into a vector as the decryot function ask for it
|
||||
if (decryptMessage<Curve>(senderDeviceId, m_selfDeviceId, recipientUserId, DRSessions, cipherHeader, cipherMessage, plainMessage) != nullptr) {
|
||||
return true; // we manage to decrypt the message with the current active session loaded in cache, nothing else to do
|
||||
} else { // remove session from cache
|
||||
// session in local storage is not modified, so it's still the active one, it will change status to stale when an other active session will be created
|
||||
m_DR_sessions_cache.erase(sessionElem);
|
||||
}
|
||||
}
|
||||
|
||||
// If we are still here, no session in cache or it didn't decrypt with it. Lookup in localStorage
|
||||
std::vector<std::shared_ptr<DR<Curve>>> DRSessions{};
|
||||
// load in DRSessions all the session found in cache for this peer device, except the one with id db_sessionIdInCache(is ignored if 0) as we already tried it
|
||||
get_DRSessions(senderDeviceId, db_sessionIdInCache, DRSessions);
|
||||
auto usedDRSession = decryptMessage<Curve>(senderDeviceId, m_selfDeviceId, recipientUserId, DRSessions, cipherHeader, cipherMessage, plainMessage);
|
||||
if (usedDRSession != nullptr) { // we manage to decrypt with a session
|
||||
m_DR_sessions_cache[senderDeviceId] = std::move(usedDRSession);
|
||||
return true;
|
||||
}
|
||||
|
||||
// No luck yet, is this message holds a X3DH header - if no we must give up
|
||||
std::vector<uint8_t> X3DH_initMessage{};
|
||||
if (!double_ratchet_protocol::parseMessage_get_X3DHinit<Curve>(cipherHeader, X3DH_initMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// parse the X3DH init message, get keys from localStorage, compute the shared secrets, create DR_Session and return a shared pointer to it
|
||||
std::shared_ptr<DR<Curve>> DRSession{X3DH_init_receiver_session(X3DH_initMessage, senderDeviceId)}; // would just throw an exception in case of failure, let it flow up
|
||||
DRSessions.clear();
|
||||
DRSessions.push_back(DRSession);
|
||||
|
||||
if (decryptMessage<Curve>(senderDeviceId, m_selfDeviceId, recipientUserId, DRSessions, cipherHeader, cipherMessage, plainMessage) != 0) {
|
||||
// we manage to decrypt the message with this session, set it in cache
|
||||
m_DR_sessions_cache[senderDeviceId] = std::move(DRSession);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* instantiate Lime for C255 and C448 */
|
||||
#ifdef EC25519_ENABLED
|
||||
template class Lime<C255>;
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
template class Lime<C448>;
|
||||
#endif
|
||||
|
||||
/****************************************************************************/
|
||||
/* */
|
||||
/* Factory functions and Delete user */
|
||||
/* */
|
||||
/****************************************************************************/
|
||||
/**
|
||||
* @brief : Insert user in database and return a pointer to the control class instanciating the appropriate Lime children class
|
||||
m* Once created a user cannot be modified, insertion of existing userId will raise an exception.
|
||||
*
|
||||
* @param[in] dbFilename Path to filename to use
|
||||
* @param[in] userId User to create in DB, userId shall be the GRUU
|
||||
* @param[in] url URL of X3DH key server to be used to publish our keys
|
||||
* @param[in] curve Which curve shall we use for this account, select the implemenation to instanciate when using this user
|
||||
* @param[in] http_provider An http provider used to communicate with x3dh key server
|
||||
*
|
||||
* @return a pointer to the LimeGeneric class allowing access to API declared in lime.hpp
|
||||
*/
|
||||
std::shared_ptr<LimeGeneric> insert_LimeUser(const std::string &dbFilename, const std::string &userId, const std::string &url, const lime::CurveId curve, belle_http_provider *http_provider,
|
||||
const limeCallback &callback) {
|
||||
bctbx_message("Create Lime user %s", userId.data());
|
||||
/* first check the requested curve is instanciable and return an exception if not */
|
||||
#ifndef EC25519_ENABLED
|
||||
if (curve == lime::CurveId::c25519) {
|
||||
throw BCTBX_EXCEPTION << "Lime User creation asking to use Curve 25519 but it's not supported - change lib lime compile option to enable it";
|
||||
}
|
||||
#endif
|
||||
#ifndef EC448_ENABLED
|
||||
if (curve == lime::CurveId::c448) {
|
||||
throw BCTBX_EXCEPTION << "Lime User creation asking to use Curve 448 but it's not supported - change lib lime compile option to enable it";
|
||||
}
|
||||
#endif
|
||||
|
||||
/* open DB */
|
||||
auto localStorage = std::make_unique<lime::Db>(dbFilename); // create as unique ptr, ownership is then passed to the Lime structure when instanciated
|
||||
|
||||
try {
|
||||
//instanciate the correct Lime object
|
||||
switch (curve) {
|
||||
case lime::CurveId::c25519 :
|
||||
#ifdef EC25519_ENABLED
|
||||
{
|
||||
/* constructor will insert user in Db, if already present, raise an exception*/
|
||||
auto lime_ptr = std::make_shared<Lime<C255>>(std::move(localStorage), userId, url, http_provider);
|
||||
lime_ptr->publish_user(callback);
|
||||
return lime_ptr;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
||||
case lime::CurveId::c448 :
|
||||
#ifdef EC448_ENABLED
|
||||
{
|
||||
auto lime_ptr = std::make_shared<Lime<C448>>(std::move(localStorage), userId, url, http_provider);
|
||||
lime_ptr->publish_user(callback);
|
||||
return lime_ptr;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
||||
case lime::CurveId::unset :
|
||||
default: // asking for an unsupported type
|
||||
throw BCTBX_EXCEPTION << "Cannot create lime user "<<userId;//<<". Unsupported curve (id <<"static_cast<uint8_t>(curve)") requested";
|
||||
break;
|
||||
}
|
||||
} catch (BctbxException &e) {
|
||||
throw; // just forward the exceptions raised by constructor
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief : Load user from database and return a pointer to the control class instanciating the appropriate Lime children class
|
||||
* Fail to find the user will raise an exception
|
||||
*
|
||||
* @param[in] dbFilename Path to filename to use
|
||||
* @param[in] userId User to create in DB, userId shall be the GRUU
|
||||
* @param[in] http_provider An http provider used to communicate with x3dh key server
|
||||
*
|
||||
* @return a pointer to the LimeGeneric class allowing access to API declared in lime.hpp
|
||||
*/
|
||||
std::shared_ptr<LimeGeneric> load_LimeUser(const std::string &dbFilename, const std::string &userId, belle_http_provider *http_provider) {
|
||||
|
||||
/* open DB and load user */
|
||||
auto localStorage = std::make_unique<lime::Db>(dbFilename); // create as unique ptr, ownership is then passed to the Lime structure when instanciated
|
||||
auto curve = CurveId::unset;
|
||||
long int Uid=0;
|
||||
std::string x3dh_server_url;
|
||||
|
||||
localStorage->load_LimeUser(userId, Uid, curve, x3dh_server_url); // this one will throw an exception if user is not found, just let it rise
|
||||
bctbx_message("Load Lime user %s", userId.data());
|
||||
|
||||
/* check the curve id retrieved from DB is instanciable and return an exception if not */
|
||||
#ifndef EC25519_ENABLED
|
||||
if (curve == lime::CurveId::c25519) {
|
||||
throw BCTBX_EXCEPTION << "Lime load User "<<userId<<" requests usage of Curve 25519 but it's not supported - change lib lime compile option to enable it";
|
||||
}
|
||||
#endif
|
||||
#ifndef EC448_ENABLED
|
||||
if (curve == lime::CurveId::c448) {
|
||||
throw BCTBX_EXCEPTION << "Lime load User "<<userId<<" requests usage of Curve 448 but it's not supported - change lib lime compile option to enable it";
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
try {
|
||||
switch (curve) {
|
||||
case lime::CurveId::c25519 :
|
||||
#ifdef EC25519_ENABLED
|
||||
return std::make_shared<Lime<C255>>(std::move(localStorage), userId, x3dh_server_url, http_provider, Uid);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case lime::CurveId::c448 :
|
||||
#ifdef EC448_ENABLED
|
||||
|
||||
return std::make_shared<Lime<C448>>(std::move(localStorage), userId, x3dh_server_url, http_provider, Uid);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case lime::CurveId::unset :
|
||||
default: // asking for an unsupported type
|
||||
throw BCTBX_EXCEPTION << "Cannot create load user "<<userId;//<<". Unsupported curve (id <<"static_cast<uint8_t>(curve)") requested";
|
||||
break;
|
||||
}
|
||||
} catch (BctbxException &e) {
|
||||
throw;
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
} //namespace lime
|
512
src/lime_double_ratchet.cpp
Normal file
512
src/lime_double_ratchet.cpp
Normal file
|
@ -0,0 +1,512 @@
|
|||
/*
|
||||
lime_double_ratchet.cpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define BCTBX_LOG_DOMAIN "lime"
|
||||
#include <bctoolbox/logging.h>
|
||||
|
||||
#include "lime_double_ratchet.hpp"
|
||||
#include "lime_double_ratchet_protocol.hpp"
|
||||
#include "lime_localStorage.hpp"
|
||||
|
||||
#include "bctoolbox/crypto.h"
|
||||
#include "bctoolbox/exception.hh"
|
||||
|
||||
#include <algorithm> //copy_n
|
||||
|
||||
|
||||
using namespace::std;
|
||||
using namespace::lime;
|
||||
|
||||
namespace lime {
|
||||
/* Set of constants used as input is several uses of HKDF like function */
|
||||
/* They MUST be different */
|
||||
const std::array<std::uint8_t,2> hkdf_rk_info{0x03, 0x01}; //it already includes the expansion index (0x01) used in kdf_rk
|
||||
const std::array<std::uint8_t,1> hkdf_ck_info{0x02};
|
||||
const std::array<std::uint8_t,1> hkdf_mk_info{0x01};
|
||||
|
||||
/****************************************************************************/
|
||||
/* Helpers functions not part of DR class */
|
||||
/****************************************************************************/
|
||||
/* Key derivation functions : KDF_RK (root key derivation function, for DH ratchet) and KDF_CK(chain key derivation function, for symmetric ratchet) */
|
||||
/**
|
||||
* @Brief Key Derivation Function used in Root key/Diffie-Hellman Ratchet chain.
|
||||
* HKDF impleted as described in RFC5869, using SHA512 as hash function according to recommendation in DR spec section 5.2
|
||||
* Note: Output length requested by DH ratchet is 64 bytes. Using SHA512 we got it in one round of
|
||||
* expansion (RFC5869 2.3), thus only one round is implemented here:
|
||||
* PRK = HMAC-SHA512(salt, input)
|
||||
* Output = HMAC-SHA512(PRK, info || 0x01)
|
||||
*
|
||||
* i.e: RK || CK = HMAC-SHA512(HMAC-SHA512(RK, dh_out), info || 0x01)
|
||||
* info being a constant string HKDF_RK_INFO_STRING used only for this implementation of HKDF
|
||||
*
|
||||
* @param[in/out] RK Input buffer used as salt also to store the 32 first byte of output key material
|
||||
* @param[out] CK Output buffer, last 32 bytes of output key material
|
||||
* @param[in] dh_out Buffer used as input key material, buffer is C style as it comes directly from another C API buffer(ECDH in bctoolbox)
|
||||
*/
|
||||
template <typename Curve>
|
||||
static void KDF_RK(DRChainKey &RK, DRChainKey &CK, const uint8_t *dh_out) noexcept {
|
||||
uint8_t PRK[64]; // PRK size is the one of hmacSha512 maximum output
|
||||
uint8_t tmp[2*lime::settings::DRChainKeySize]; // tmp will hold RK || CK
|
||||
bctbx_hmacSha512(RK.data(), RK.size(), dh_out, X<Curve>::keyLength(), sizeof(PRK), PRK);
|
||||
bctbx_hmacSha512(PRK, sizeof(PRK), hkdf_rk_info.data(), hkdf_rk_info.size(), sizeof(tmp), tmp);
|
||||
std::copy_n(tmp, lime::settings::DRChainKeySize, RK.begin());
|
||||
std::copy_n(tmp+lime::settings::DRChainKeySize, lime::settings::DRChainKeySize, CK.begin());
|
||||
bctbx_clean(PRK, 64);
|
||||
bctbx_clean(tmp, 2*lime::settings::DRChainKeySize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Brief Key Derivation Function used in Symmetric key ratchet chain.
|
||||
* Impleted according to DR spec section 5.2 using HMAC-SHA256 for CK derivation and 512 for MK and IV derivation
|
||||
* MK = HMAC-SHA512(CK, hkdf_mk_info) // get 48 bytes of it: first 32 to be key and last 16 to be IV
|
||||
* CK = HMAC-SHA256(CK, hkdf_ck_info)
|
||||
* hkdf_ck_info and hldf_mk_info being a distincts constant strings
|
||||
*
|
||||
* @param[in/out] CK Input/output buffer used as key to compute MK and then next CK
|
||||
* @param[out] MK Message Key(32 bytes) and IV(16 bytes) computed from HMAc_SHA512 keyed with CK
|
||||
*/
|
||||
static void KDF_CK(DRChainKey &CK, DRMKey &MK) noexcept {
|
||||
// derive MK and IV from CK and constant
|
||||
bctbx_hmacSha512(CK.data(), CK.size(), hkdf_mk_info.data(), hkdf_mk_info.size(), MK.size(), MK.data());
|
||||
|
||||
// use temporary buffer, not likely that output and key could be the same buffer
|
||||
uint8_t tmp[lime::settings::DRChainKeySize];
|
||||
bctbx_hmacSha512(CK.data(), CK.size(), hkdf_ck_info.data(), hkdf_ck_info.size(), CK.size(), tmp);
|
||||
std::copy_n(tmp, CK.size(), CK.begin());
|
||||
bctbx_clean(tmp, lime::settings::DRChainKeySize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decrypt as described is spec section 3.1
|
||||
*
|
||||
* @param[in] MK A buffer holding key<32 bytes> || IV<16 bytes>
|
||||
* @param[in] ciphertext buffer holding: header<size depends on DHKey type> || ciphertext || auth tag<16 bytes>
|
||||
* @param[in] headerSize Size of the header included in ciphertext
|
||||
* @param[in] AD Associated data
|
||||
* @param[out] plaintext the output message(may be a vector or an array of unsigned char)
|
||||
* this vector need resizing before calling actual decrypt
|
||||
*
|
||||
* @return false if authentication failed
|
||||
*
|
||||
*/
|
||||
static bool decrypt(const lime::DRMKey &MK, const std::vector<uint8_t> &ciphertext, const size_t headerSize, std::vector<uint8_t> &AD, std::array<uint8_t,48> &plaintext) {
|
||||
return (bctbx_aes_gcm_decrypt_and_auth(MK.data(), lime::settings::DRMessageKeySize, // MK buffer hold key<DRMessageKeySize bytes>||IV<DRMessageIVSize bytes>
|
||||
ciphertext.data()+headerSize, plaintext.size(), // cipher text starts after header, length is the one computed for plaintext
|
||||
AD.data(), AD.size(),
|
||||
MK.data()+lime::settings::DRMessageKeySize, lime::settings::DRMessageIVSize,
|
||||
ciphertext.data()+ciphertext.size() - lime::settings::DRMessageAuthTagSize, lime::settings::DRMessageAuthTagSize, // tag is in the last 16 bytes of buffer
|
||||
plaintext.data()) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Encrypt as described is spec section 3.1
|
||||
*
|
||||
* @param[in] MK A buffer holding key<32 bytes> || IV<16 bytes>
|
||||
* @param[in] plaintext the output message(may be a vector or an array of unsigned char)
|
||||
* @param[in] AD Associated data
|
||||
* @param[in] headerSize Size of the header included in ciphertext
|
||||
* @param[in/out] ciphertext buffer holding: header<size depends on DHKey type>, will append to it: ciphertext || auth tag<16 bytes>
|
||||
* this vector version need resizing before calling encrypt
|
||||
*
|
||||
* @return false if something goes wrong
|
||||
*
|
||||
*/
|
||||
static bool encrypt(const lime::DRMKey &MK, const std::array<uint8_t,48> &plaintext, const size_t headerSize, std::vector<uint8_t> &AD, std::vector<uint8_t> &ciphertext) {
|
||||
return (bctbx_aes_gcm_encrypt_and_tag(MK.data(), lime::settings::DRMessageKeySize, // MK buffer also hold the IV
|
||||
plaintext.data(), plaintext.size(),
|
||||
AD.data(), AD.size(),
|
||||
MK.data()+lime::settings::DRMessageKeySize, lime::settings::DRMessageIVSize, // IV is stored in the same buffer as key, after it
|
||||
ciphertext.data()+headerSize+plaintext.size(), lime::settings::DRMessageAuthTagSize, // directly store tag after cipher text in the output buffer
|
||||
ciphertext.data()+headerSize) == 0);
|
||||
}
|
||||
|
||||
/****************************************************************************/
|
||||
/* DR member functions */
|
||||
/****************************************************************************/
|
||||
|
||||
/****************************************************************************/
|
||||
/* DR class constructors: 3 cases */
|
||||
/* - sender's init */
|
||||
/* - receiver's init */
|
||||
/* - initialisation from session stored in local storage */
|
||||
/****************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Create a new DR session for sending message. Match pseudo code for RatchetInitAlice in DR spec section 3.3
|
||||
*
|
||||
* @param[in] localStorage Local storage accessor to save DR session and perform mkskipped lookup
|
||||
* @param[in] SK a 32 bytes shared secret established prior the session init (likely done using X3DH)
|
||||
* @param[in] peerPublicKey the public key of message recipient (also obtained through X3DH, shall be peer SPk)
|
||||
* @param[in] peerDid Id used in local storage for this peer Device this session shall be attached to
|
||||
* @param[in] X3DH_initMessage at session creation as sender we shall also store the X3DHInit message to be able to include it in all message until we got a response from peer
|
||||
*/
|
||||
template <typename Curve>
|
||||
DR<Curve>::DR(lime::Db *localStorage, const DRChainKey &SK, const SharedADBuffer &AD, const X<Curve> &peerPublicKey, long int peerDid, const std::vector<uint8_t> &X3DH_initMessage)
|
||||
:m_DHr{peerPublicKey},m_DHr_valid{true}, m_DHs{},m_RK{SK},m_CKs{0},m_CKr{0},m_Ns(0),m_Nr(0),m_PN(0),m_sharedAD{AD},m_mkskipped{},
|
||||
m_RNG{bctbx_rng_context_new()},m_dbSessionId{0},m_usedNr{0},m_usedDHid{0},m_localStorage{localStorage},m_dirty{DRSessionDbStatus::dirty},m_peerDid{peerDid},
|
||||
m_active_status{true}, m_X3DH_initMessage{X3DH_initMessage}
|
||||
{
|
||||
// generate a new self key pair
|
||||
// use specialized templates to init the bctoolbox ECDH context with the correct DH algo
|
||||
bctbx_ECDHContext_t *ECDH_Context = ECDHInit<Curve>();
|
||||
bctbx_ECDHCreateKeyPair(ECDH_Context, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
|
||||
|
||||
// copy the peer public key into ECDH context
|
||||
bctbx_ECDHSetPeerPublicKey(ECDH_Context, peerPublicKey.data(), peerPublicKey.size());
|
||||
// get self key pair from context
|
||||
m_DHs.publicKey() = X<Curve>{ECDH_Context->selfPublic};
|
||||
m_DHs.privateKey() = X<Curve>{ECDH_Context->secret};
|
||||
|
||||
// compute shared secret
|
||||
bctbx_ECDHComputeSecret(ECDH_Context, NULL, NULL);
|
||||
|
||||
// derive the root key
|
||||
KDF_RK<Curve>(m_RK, m_CKs, ECDH_Context->sharedSecret);
|
||||
|
||||
bctbx_DestroyECDHContext(ECDH_Context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a new DR session for message reception. Match pseudo code for RatchetInitBob in DR spec section 3.3
|
||||
*
|
||||
* @param[in] localStorage Local storage accessor to save DR session and perform mkskipped lookup
|
||||
* @param[in] SK a 32 bytes shared secret established prior the session init (likely done using X3DH)
|
||||
* @param[in] selfKeyPair the key pair used by sender to establish this DR session
|
||||
* @param[in] peerDid Id used in local storage for this peer Device this session shall be attached to
|
||||
*/
|
||||
template <typename Curve>
|
||||
DR<Curve>::DR(lime::Db *localStorage, const DRChainKey &SK, const SharedADBuffer &AD, const KeyPair<X<Curve>> &selfKeyPair, long int peerDid)
|
||||
:m_DHr{},m_DHr_valid{false},m_DHs{selfKeyPair},m_RK{SK},m_CKs{0},m_CKr{0},m_Ns(0),m_Nr(0),m_PN(0),m_sharedAD{AD},m_mkskipped{},
|
||||
m_RNG{bctbx_rng_context_new()},m_dbSessionId{0},m_usedNr{0},m_usedDHid{0},m_localStorage{localStorage},m_dirty{DRSessionDbStatus::dirty},m_peerDid{peerDid},
|
||||
m_active_status{true}, m_X3DH_initMessage{}
|
||||
{ }
|
||||
|
||||
/**
|
||||
* @brief Create a new DR session to be loaded from db,
|
||||
* m_dirty is already set to clean and DHR_valid to true as we won't save a session if no successfull sending or reception was performed
|
||||
* if loading fails, caller should destroy the session
|
||||
*
|
||||
* @param[in] localStorage Local storage accessor to save DR session and perform mkskipped lookup
|
||||
* @param[in] sessionId row id in the database identifying the session to be loaded
|
||||
*/
|
||||
template <typename Curve>
|
||||
DR<Curve>::DR(lime::Db *localStorage, long sessionId)
|
||||
:m_DHr{},m_DHr_valid{true},m_DHs{},m_RK{0},m_CKs{0},m_CKr{0},m_Ns(0),m_Nr(0),m_PN(0),m_sharedAD{0},m_mkskipped{},
|
||||
m_RNG{bctbx_rng_context_new()},m_dbSessionId{sessionId},m_usedNr{0},m_usedDHid{0},m_localStorage{localStorage},m_dirty{DRSessionDbStatus::clean},m_peerDid{0},
|
||||
m_active_status{false}, m_X3DH_initMessage{}
|
||||
{
|
||||
session_load();
|
||||
}
|
||||
|
||||
template <typename Curve>
|
||||
DR<Curve>::~DR() {
|
||||
bctbx_clean(m_DHs.privateKey().data(), m_DHs.privateKey().size());
|
||||
bctbx_clean(m_RK.data(), m_RK.size());
|
||||
bctbx_clean(m_CKs.data(), m_CKs.size());
|
||||
bctbx_clean(m_CKr.data(), m_CKr.size());
|
||||
bctbx_rng_context_free(m_RNG);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Derive chain keys until reaching the requested Id. Handling unordered messages
|
||||
* Store the derived but not used keys in a list indexed by peer DH and Nr
|
||||
*
|
||||
* @param[in] until index we must reach in that chain key
|
||||
* @param[in] limit maximum number of allowed derivations
|
||||
*
|
||||
* @throws when we try to overpass the maximum number of key derivation since last valid message
|
||||
*/
|
||||
template <typename Curve>
|
||||
void DR<Curve>::skipMessageKeys(const uint32_t until, const uint32_t limit) {
|
||||
if (m_Nr==until) return; // just to be sure we actually have MK to derive and store
|
||||
|
||||
// check if there are not too much message keys to derive in this chain
|
||||
if (m_Nr + limit < until) {
|
||||
throw BCTBX_EXCEPTION << "DR Session is too far behind this message to derive requested amount of keys: "<<(until-m_Nr);
|
||||
}
|
||||
|
||||
// each call to this function is made with a different DHr
|
||||
receiverKeyChain<Curve> newRChain{m_DHr};
|
||||
m_mkskipped.push_back(newRChain);
|
||||
auto rChain = &m_mkskipped.back();
|
||||
|
||||
DRMKey MK;
|
||||
while (m_Nr<until) {
|
||||
KDF_CK(m_CKr, MK);
|
||||
// insert the nessage key into the list of skipped ones
|
||||
rChain->messageKeys[m_Nr]=MK;
|
||||
|
||||
m_Nr++;
|
||||
}
|
||||
bctbx_clean(MK.data(), MK.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief perform a Diffie-Hellman Ratchet as described in DR spec section 3.5
|
||||
*
|
||||
* @param[in] headerDH The peer public key to use for the DH shared secret computation
|
||||
*/
|
||||
template <typename Curve>
|
||||
void DR<Curve>::DHRatchet(const X<Curve> &headerDH) {
|
||||
// reinit counters
|
||||
m_PN=m_Ns;
|
||||
m_Ns=0;
|
||||
m_Nr=0;
|
||||
|
||||
// this is our new DHr
|
||||
m_DHr = headerDH;
|
||||
|
||||
// use specialized templates to init the bctoolbox ECDH context with the correct DH algo
|
||||
bctbx_ECDHContext_t *ECDH_Context = ECDHInit<Curve>();
|
||||
// insert correct keys in the ECDH context
|
||||
bctbx_ECDHSetPeerPublicKey(ECDH_Context, m_DHr.data(), m_DHr.size()); // new Dhr
|
||||
bctbx_ECDHSetSelfPublicKey(ECDH_Context, m_DHs.publicKey().data(), m_DHs.publicKey().size()); // local key pair
|
||||
bctbx_ECDHSetSecretKey(ECDH_Context, m_DHs.privateKey().data(), m_DHs.privateKey().size());
|
||||
|
||||
// Derive the new receiving chain key
|
||||
bctbx_ECDHComputeSecret(ECDH_Context, NULL, NULL);
|
||||
KDF_RK<Curve>(m_RK, m_CKr, ECDH_Context->sharedSecret);
|
||||
|
||||
// generate a new self key pair
|
||||
bctbx_ECDHCreateKeyPair(ECDH_Context, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
|
||||
// Derive the new sending chain key
|
||||
bctbx_ECDHComputeSecret(ECDH_Context, NULL, NULL);
|
||||
KDF_RK<Curve>(m_RK, m_CKs, ECDH_Context->sharedSecret);
|
||||
|
||||
// retrieve new self pair from context
|
||||
m_DHs.publicKey() = X<Curve>{ECDH_Context->selfPublic};
|
||||
m_DHs.privateKey() = X<Curve>{ECDH_Context->secret};
|
||||
|
||||
// destroy context, it will erase the shared secret
|
||||
bctbx_DestroyECDHContext(ECDH_Context);
|
||||
|
||||
// modified the DR session, not in sync anymore with local storage
|
||||
m_dirty = DRSessionDbStatus::dirty_ratchet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Encrypt using the double-ratchet algorithm.
|
||||
*
|
||||
* @param[in] plaintext Shall actally be a 48 bytes buffer holding key+IV for a GCM encryption to the actual message
|
||||
* @param[in] AD Associated Data, this buffer shall hold: source GRUU<...> || recipient GRUU<...> || actual message AEAD auth tag
|
||||
* @param[out] ciphertext buffer holding the header, cipher text and auth tag, shall contain the key and IV used to cipher the actual message, auth tag applies on AD || header
|
||||
*/
|
||||
template <typename Curve>
|
||||
void DR<Curve>::ratchetEncrypt(const array<uint8_t, 48>& plaintext, std::vector<uint8_t> &&AD, std::vector<uint8_t> &ciphertext) {
|
||||
m_dirty = DRSessionDbStatus::dirty_encrypt; // we're about to modify this session, it won't be in sync anymore with local storage
|
||||
// chain key derivation(also compute message key)
|
||||
DRMKey MK;
|
||||
KDF_CK(m_CKs, MK);
|
||||
|
||||
// build header string in the ciphertext buffer
|
||||
double_ratchet_protocol::buildMessage_header(ciphertext, m_Ns, m_PN, m_DHs.publicKey(), m_X3DH_initMessage);
|
||||
auto headerSize = ciphertext.size(); // cipher text holds only the DR header for now
|
||||
|
||||
// increment current sending chain message index
|
||||
m_Ns++;
|
||||
|
||||
// build AD: given AD || sharedAD stored in session || header (see DR spec section 3.4)
|
||||
AD.insert(AD.end(), m_sharedAD.begin(), m_sharedAD.end());
|
||||
AD.insert(AD.end(), ciphertext.begin(), ciphertext.end()); // cipher text holds header only for now
|
||||
|
||||
// data will be written directly in the underlying structure by C library, so set size to the actual one
|
||||
// header size + cipher text size + auth tag size
|
||||
ciphertext.resize(ciphertext.size()+plaintext.size()+lime::settings::DRMessageAuthTagSize);
|
||||
|
||||
if (encrypt(MK, plaintext, headerSize, AD, ciphertext)) {
|
||||
if (session_save() == true) {
|
||||
m_dirty = DRSessionDbStatus::clean; // this session and local storage are back in sync
|
||||
}
|
||||
}
|
||||
bctbx_clean(MK.data(), MK.size());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Decrypt Double Ratchet message
|
||||
*
|
||||
*/
|
||||
template <typename Curve>
|
||||
bool DR<Curve>::ratchetDecrypt(const std::vector<uint8_t> &ciphertext,const std::vector<uint8_t> &AD, array<uint8_t,48> &plaintext) {
|
||||
// parse header
|
||||
double_ratchet_protocol::DRHeader<Curve> header{ciphertext};
|
||||
if (!header.valid()) { // check it is valid otherwise just stop
|
||||
throw BCTBX_EXCEPTION << "DR Session got an invalid message header";
|
||||
}
|
||||
|
||||
// build an Associated Data buffer: given AD || shared AD stored in session || header (as in DR spec section 3.4)
|
||||
std::vector<uint8_t> DRAD{AD}; // copy given AD
|
||||
DRAD.insert(DRAD.end(), m_sharedAD.begin(), m_sharedAD.end());
|
||||
DRAD.insert(DRAD.end(), ciphertext.begin(), ciphertext.begin()+header.size());
|
||||
|
||||
|
||||
DRMKey MK;
|
||||
int32_t maxAllowedDerivation = lime::settings::maxMessageSkip;
|
||||
m_dirty = DRSessionDbStatus::dirty_decrypt; // we're about to modify the DR session, it will not be in sync anymore with local storage
|
||||
if (!m_DHr_valid) { // it's the first message arriving after the initialisation of the chain in receiver mode, we have no existing history in this chain
|
||||
DHRatchet(header.DHs()); // just perform the DH ratchet step
|
||||
m_DHr_valid=true;
|
||||
} else {
|
||||
// check stored message keys
|
||||
if (trySkippedMessageKeys(header.Ns(), header.DHs(), MK)) {
|
||||
if (decrypt(MK, ciphertext, header.size(), DRAD, plaintext) == true) {
|
||||
//Decrypt went well, we must save the session to DB
|
||||
if (session_save() == true) {
|
||||
m_dirty = DRSessionDbStatus::clean; // this session and local storage are back in sync
|
||||
m_usedDHid=0; // reset variables used to tell the local storage to delete them
|
||||
m_usedNr=0;
|
||||
m_X3DH_initMessage.clear(); // just in case we had a valid X3DH init in session, erase it as it's not needed after the first message received from peer
|
||||
}
|
||||
} else {
|
||||
bctbx_clean(MK.data(), MK.size());
|
||||
return false;
|
||||
};
|
||||
bctbx_clean(MK.data(), MK.size());
|
||||
return true;
|
||||
}
|
||||
// if header DH public key != current stored peer public DH key: we must perform a DH ratchet
|
||||
if (m_DHr!=header.DHs()) {
|
||||
maxAllowedDerivation -= header.PN()-m_Nr; /* we must derive headerPN-Nr keys, remove this from the count of our allowedDerivation number */
|
||||
skipMessageKeys(header.PN(), lime::settings::maxMessageSkip-header.Ns()); // we must keep header.Ns derivations available for the next chain
|
||||
DHRatchet(header.DHs());
|
||||
}
|
||||
}
|
||||
|
||||
// in the derivation limit we remove the derivation done in the previous DH rachet key chain
|
||||
skipMessageKeys(header.Ns(), static_cast<uint32_t>(maxAllowedDerivation)); // maxAllowedDerivation cannot actually be negative or an exception is raised in previous call to skipMessageKeys
|
||||
|
||||
// generate key material for decryption(and derive Chain key)
|
||||
KDF_CK(m_CKr, MK);
|
||||
|
||||
m_Nr++;
|
||||
|
||||
//decrypt and save on succes
|
||||
if (decrypt(MK, ciphertext, header.size(), DRAD, plaintext) == true ) {
|
||||
if (session_save() == true) {
|
||||
m_dirty = DRSessionDbStatus::clean; // this session and local storage are back in sync
|
||||
m_mkskipped.clear(); // potential skipped message keys are now stored in DB, clear the local storage
|
||||
m_X3DH_initMessage.clear(); // just in case we had a valid X3DH init in session, erase it as it's not needed after the first message received from peer
|
||||
}
|
||||
bctbx_clean(MK.data(), MK.size());
|
||||
return true;
|
||||
} else {
|
||||
bctbx_clean(MK.data(), MK.size());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* template instanciations for DHKeyX25519 and DHKeyX448 */
|
||||
#ifdef EC25519_ENABLED
|
||||
template class DR<C255>;
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
template class DR<C448>;
|
||||
#endif
|
||||
|
||||
template <typename Curve>
|
||||
void encryptMessage(std::vector<recipientInfos<Curve>>& recipients, const std::vector<uint8_t>& plaintext, const std::string& recipientUserId, const std::string& sourceDeviceId, std::vector<uint8_t>& cipherMessage) {
|
||||
// First generate a key and IV, use it to encrypt the given message, Associated Data are : sourceDeviceId || recipientUserId
|
||||
// generate the random key : 32 bytes of key, 16 bytes of IV
|
||||
bctbx_rng_context_t *RNG = bctbx_rng_context_new();
|
||||
std::array<uint8_t,lime::settings::DRMessageKeySize+lime::settings::DRMessageIVSize> randomKey; // use the same size than the one used internally by Double Ratchet
|
||||
bctbx_rng_get(RNG, randomKey.data(), randomKey.size());
|
||||
bctbx_rng_context_free(RNG);
|
||||
|
||||
// resize cipherMessage vector as it is adressed directly by C library: same as plain message + room for the authentication tag
|
||||
cipherMessage.resize(plaintext.size()+lime::settings::DRMessageAuthTagSize);
|
||||
|
||||
// AD is source deviceId(gruu) || recipientUserId(sip uri)
|
||||
std::vector<uint8_t> AD{sourceDeviceId.begin(),sourceDeviceId.end()};
|
||||
AD.insert(AD.end(), recipientUserId.begin(), recipientUserId.end());
|
||||
|
||||
// encrypt to cipherMessage buffer
|
||||
if (bctbx_aes_gcm_encrypt_and_tag(randomKey.data(), lime::settings::DRMessageKeySize, // key buffer also hold the IV
|
||||
plaintext.data(), plaintext.size(),
|
||||
AD.data(), AD.size(),
|
||||
randomKey.data()+lime::settings::DRMessageKeySize, lime::settings::DRMessageIVSize, // IV is stored in the same buffer as key, after it
|
||||
cipherMessage.data()+plaintext.size(), lime::settings::DRMessageAuthTagSize, // directly store tag after cipher text in the output buffer
|
||||
cipherMessage.data()) != 0) {
|
||||
throw BCTBX_EXCEPTION << "DR Session low level encryption routine failed";
|
||||
}
|
||||
|
||||
// Loop on each session, given Associated Data to Double Ratchet encryption is: auth tag of cipherMessage AEAD || sourceDeviceId || recipient device Id(gruu)
|
||||
// build the common part to AD given to DR Session encryption
|
||||
AD.assign(cipherMessage.begin()+plaintext.size(), cipherMessage.end());
|
||||
AD.insert(AD.end(), sourceDeviceId.begin(), sourceDeviceId.end());
|
||||
|
||||
for(size_t i=0; i<recipients.size(); i++) {
|
||||
std::vector<uint8_t> recipientAD{AD}; // copy AD
|
||||
recipientAD.insert(recipientAD.end(), recipients[i].deviceId.begin(), recipients[i].deviceId.end()); //insert recipient device id(gruu)
|
||||
|
||||
recipients[i].DRSession->ratchetEncrypt(randomKey, std::move(recipientAD), recipients[i].cipherHeader);
|
||||
}
|
||||
bctbx_clean(randomKey.data(), randomKey.size());
|
||||
}
|
||||
|
||||
template <typename Curve>
|
||||
std::shared_ptr<DR<Curve>> decryptMessage(const std::string& sourceDeviceId, const std::string& recipientDeviceId, const std::string& recipientUserId, std::vector<std::shared_ptr<DR<Curve>>>& DRSessions, const std::vector<uint8_t>& cipherHeader, const std::vector<uint8_t>& cipherMessage, std::vector<uint8_t>& plaintext) {
|
||||
// prepare the AD given to ratchet decrypt: auth tag from cipherMessage || source Device Id || recipient Device Id
|
||||
std::vector<uint8_t> AD{cipherMessage.end()-lime::settings::DRMessageAuthTagSize, cipherMessage.end()};
|
||||
AD.insert(AD.end(), sourceDeviceId.begin(), sourceDeviceId.end());
|
||||
AD.insert(AD.end(), recipientDeviceId.begin(), recipientDeviceId.end());
|
||||
|
||||
// buffer to store the random key used to encrypt message
|
||||
std::array<uint8_t,lime::settings::DRMessageKeySize+lime::settings::DRMessageIVSize> randomKey; /* use the same size than the one used internally by Double Ratchet */
|
||||
|
||||
for (auto& DRSession : DRSessions) {
|
||||
if(DRSession->ratchetDecrypt(cipherHeader, AD, randomKey) == true) { // we got the random key correctly deciphered
|
||||
// recompute the AD used for this encryption: source Device Id || recipient User Id
|
||||
std::vector<uint8_t> localAD{sourceDeviceId.begin(), sourceDeviceId.end()};
|
||||
localAD.insert(localAD.end(), recipientUserId.begin(), recipientUserId.end());
|
||||
|
||||
// resize plaintext vector as it is adressed directly by C library: same as cipher message - authentication tag length
|
||||
plaintext.resize(cipherMessage.size()-lime::settings::DRMessageAuthTagSize);
|
||||
|
||||
// use it to decipher message
|
||||
if (bctbx_aes_gcm_decrypt_and_auth(randomKey.data(), lime::settings::DRMessageKeySize, // random key buffer hold key<DRMessageKeySize bytes> || IV<DRMessageIVSize bytes>
|
||||
cipherMessage.data(), cipherMessage.size()-lime::settings::DRMessageAuthTagSize, // cipherMessage is Message || auth tag
|
||||
localAD.data(), localAD.size(),
|
||||
randomKey.data()+lime::settings::DRMessageKeySize, lime::settings::DRMessageIVSize,
|
||||
cipherMessage.data()+cipherMessage.size()-lime::settings::DRMessageAuthTagSize, lime::settings::DRMessageAuthTagSize, // tag is in the last 16 bytes of buffer
|
||||
plaintext.data()) == 0) {
|
||||
|
||||
bctbx_clean(randomKey.data(), randomKey.size());
|
||||
return DRSession;
|
||||
} else {
|
||||
bctbx_clean(randomKey.data(), randomKey.size());
|
||||
throw BCTBX_EXCEPTION << "Message key correctly deciphered but then failed to decipher message itself";
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr; // no session correctly deciphered
|
||||
}
|
||||
|
||||
/* template instanciations for DHKeyX25519 and DHKeyX448 encryption/decryption functions */
|
||||
#ifdef EC25519_ENABLED
|
||||
template void encryptMessage<C255>(std::vector<recipientInfos<C255>>& recipients, const std::vector<uint8_t>& plaintext, const std::string& recipientUserId, const std::string& sourceDeviceId, std::vector<uint8_t>& cipherMessage);
|
||||
template std::shared_ptr<DR<C255>> decryptMessage<C255>(const std::string& sourceId, const std::string& recipientDeviceId, const std::string& recipientUserId, std::vector<std::shared_ptr<DR<C255>>>& DRSessions, const std::vector<uint8_t>& cipherHeader, const std::vector<uint8_t>& cipherMessage, std::vector<uint8_t>& plaintext);
|
||||
#endif
|
||||
#ifdef EC448_ENABLED
|
||||
template void encryptMessage<C448>(std::vector<recipientInfos<C448>>& recipients, const std::vector<uint8_t>& plaintext, const std::string& recipientUserId, const std::string& sourceDeviceId, std::vector<uint8_t>& cipherMessage);
|
||||
template std::shared_ptr<DR<C448>> decryptMessage<C448>(const std::string& sourceId, const std::string& recipientDeviceId, const std::string& recipientUserId, std::vector<std::shared_ptr<DR<C448>>>& DRSessions, const std::vector<uint8_t>& cipherHeader, const std::vector<uint8_t>& cipherMessage, std::vector<uint8_t>& plaintext);
|
||||
#endif
|
||||
}
|
180
src/lime_double_ratchet.hpp
Normal file
180
src/lime_double_ratchet.hpp
Normal file
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
lime_double_ratchet.hpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef lime_double_ratchet_hpp
|
||||
#define lime_double_ratchet_hpp
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include "bctoolbox/crypto.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "lime_utils.hpp"
|
||||
#include "lime_keys.hpp"
|
||||
|
||||
namespace lime {
|
||||
|
||||
class Db; // forward declaration of class Db used by DR<DHKey>, declared in lime_localStorage.hpp
|
||||
|
||||
// an enum to set the possible status of session regarding the Local Storage
|
||||
// used to pick a subset of session to be saved in DB
|
||||
enum class DRSessionDbStatus : uint8_t {clean, dirty_encrypt, dirty_decrypt, dirty_ratchet, dirty};
|
||||
|
||||
// Double Rachet chain keys: Root key, Sender and receiver keys are 32 bytes arrays
|
||||
using DRChainKey = std::array<uint8_t, lime::settings::DRChainKeySize>;
|
||||
// Double Ratchet Message keys : 32 bytes of encryption key followed by 16 bytes of IV
|
||||
using DRMKey = std::array<uint8_t, lime::settings::DRMessageKeySize+lime::settings::DRMessageIVSize>;
|
||||
// Shared Associated Data : stored at session initialisation, given by upper level(X3DH), shall be derived from Identity and Identity keys of sender and recipient, fixed size for storage convenience
|
||||
using SharedADBuffer = std::array<uint8_t, lime::settings::DRSessionSharedADSize>;
|
||||
// Chain storing the DH and MKs associated with Nr(uint32_t map index)
|
||||
template <typename Curve>
|
||||
struct receiverKeyChain {
|
||||
X<Curve> DHr;
|
||||
std::unordered_map<std::uint32_t, DRMKey> messageKeys;
|
||||
receiverKeyChain(X<Curve> key) :DHr{std::move(key)}, messageKeys{} {};
|
||||
};
|
||||
|
||||
/**
|
||||
* DR object store a Double Rachet session. 3 kinds of construction:
|
||||
* - from scratch for sender
|
||||
* - from scracth for receiver
|
||||
* - unserialised object from local storage version
|
||||
*/
|
||||
template <typename Curve>
|
||||
class DR {
|
||||
private:
|
||||
/* State variables for Double Ratchet, see Double Ratchet spec section 3.2 for details */
|
||||
X<Curve> m_DHr; // Remote public key
|
||||
bool m_DHr_valid; // do we have a valid remote public key, flag used to spot the first message arriving at session creation in receiver mode
|
||||
KeyPair<X<Curve>> m_DHs; // self Key pair
|
||||
DRChainKey m_RK; // 32 bytes root key
|
||||
DRChainKey m_CKs; // 32 bytes key chain for sending
|
||||
DRChainKey m_CKr; // 32 bytes key chain for receiving
|
||||
std::uint32_t m_Ns,m_Nr; // Message index in sending and receiving chain
|
||||
std::uint32_t m_PN; // Number of messages in previous sending chain
|
||||
SharedADBuffer m_sharedAD; // Associated Data derived from self and peer device Identity key, set once at session creation, given by X3DH
|
||||
std::vector<lime::receiverKeyChain<Curve>> m_mkskipped; // list of skipped message indexed by DH receiver public key and Nr, store MK generated during on-going decrypt, lookup is done directly in DB.
|
||||
|
||||
/* helpers variables */
|
||||
bctbx_rng_context_t *m_RNG; // Random Number Generator context
|
||||
long int m_dbSessionId; // used to store row id from Database Storage
|
||||
uint32_t m_usedNr; // store the index of message key used for decryption if it came from mkskipped db
|
||||
long m_usedDHid; // store the index of DHr message key used for decryption if it came from mkskipped db(not zero only if used)
|
||||
lime::Db *m_localStorage; // enable access to the database holding sessions and skipped message keys, no need to use smart pointers here, Db is not owned by DRsession, it must persist even if no session exists
|
||||
DRSessionDbStatus m_dirty; // status of the object regarding its instance in local storage, could be: clean, dirty_encrypt, dirty_decrypt or dirty
|
||||
long int m_peerDid; // used during session creation only to hold the peer device id in DB as we need it to insert the session in local Storage
|
||||
bool m_active_status; // current status of this session, true if it is the active one, false if it is stale
|
||||
std::vector<uint8_t> m_X3DH_initMessage; // store the X3DH init message to be able to prepend it to any message until we got a first response from peer so we're sure he was able to init the session on his side
|
||||
|
||||
/*helpers functions */
|
||||
void skipMessageKeys(const uint32_t until, const uint32_t limit); /* check if we skipped some messages in current receiving chain, generate and store in session intermediate message keys */
|
||||
void DHRatchet(const X<Curve> &headerDH); /* perform a Diffie-Hellman ratchet using the given peer public key */
|
||||
/* local storage related implemented in lime_localStorage.cpp */
|
||||
bool session_save(); /* save/update session in database : updated component depends m_dirty value */
|
||||
bool session_load(); /* load session in database */
|
||||
bool trySkippedMessageKeys(const uint32_t Nr, const X<Curve> &DHr, DRMKey &MK); /* check in DB if we have a message key matching public DH and Ns */
|
||||
|
||||
public:
|
||||
DR() = delete; // make sure the Double Ratchet is not initialised without parameters
|
||||
DR(lime::Db *localStorage, const DRChainKey &SK, const SharedADBuffer &AD, const X<Curve> &peerPublicKey, long int peerDeviceId, const std::vector<uint8_t> &X3DH_initMessage); // call to initialise a session for sender: we have Shared Key and peer Public key
|
||||
DR(lime::Db *localStorage, const DRChainKey &SK, const SharedADBuffer &AD, const KeyPair<X<Curve>> &selfKeyPair, long int peerDeviceId); // call at initialisation of a session for receiver: we have Share Key and self key pair
|
||||
DR(lime::Db *localStorage, long sessionId); // load session from DB
|
||||
DR(DR<Curve> &a) = delete; // can't copy a session, force usage of shared pointers
|
||||
DR<Curve> &operator=(DR<Curve> &a) = delete; // can't copy a session
|
||||
~DR();
|
||||
|
||||
void ratchetEncrypt(const std::array<uint8_t, 48> &plaintext, std::vector<uint8_t> &&AD, std::vector<uint8_t> &ciphertext);
|
||||
bool ratchetDecrypt(const std::vector<uint8_t> &cipherText, const std::vector<uint8_t> &AD, std::array<uint8_t, 48> &plaintext);
|
||||
auto dbSessionId(void) const {return m_dbSessionId;}; // retrieve the session's local storage id
|
||||
};
|
||||
|
||||
|
||||
template <typename Curve>
|
||||
struct recipientInfos {
|
||||
std::shared_ptr<DR<Curve>> DRSession; // Session to reach recipient
|
||||
std::string deviceId; // recipient device Id(gruu)
|
||||
std::vector<uint8_t> cipherHeader; // will hold the header targeted to this recipient after encryption
|
||||
recipientInfos() : DRSession{nullptr}, deviceId{}, cipherHeader{} {};
|
||||
recipientInfos(std::string deviceId) : DRSession{nullptr}, deviceId{deviceId}, cipherHeader{} {};
|
||||
recipientInfos(std::string deviceId, std::shared_ptr<DR<Curve>> session) : DRSession{session}, deviceId{deviceId}, cipherHeader{} {};
|
||||
};
|
||||
|
||||
// helpers function wich are the one to be used to encrypt/decrypt messages
|
||||
/**
|
||||
* @brief Encrypt a message to all recipients, identified by their device id
|
||||
* The plaintext is first encrypted by one randomly generated key using aes-gcm
|
||||
* The key and IV are then encrypted with DR Session specific to each device
|
||||
*
|
||||
* @param[in/out] recipients vector of recipients device id(gruu) and linked DR Session, DR Session are modified by the encryption
|
||||
* The recipients struct also hold after encryption the encrypted message header targeted to that recipient only
|
||||
* @param[in] plaintext data to be encrypted
|
||||
* @param[in] recipientUserId the recipient ID, not specific to a device(could be a sip-uri) or a user(could be a group sip-uri)
|
||||
* @param[in] sourceDeviceId the Id of sender device(gruu)
|
||||
* @param[out] cipherMessage message encrypted with a random generated key(and IV)
|
||||
*/
|
||||
template <typename Curve>
|
||||
void encryptMessage(std::vector<recipientInfos<Curve>>& recipients, const std::vector<uint8_t>& plaintext, const std::string& recipientUserId, const std::string& sourceDeviceId, std::vector<uint8_t>& cipherMessage);
|
||||
|
||||
/**
|
||||
* @brief Decrypt a message
|
||||
* First try to decrypt the header using the DR Sessions given in parameter, then decrypt the message itself with key retrieved from header part
|
||||
*
|
||||
* @param[in] sourceDeviceId the device Id of sender(gruu)
|
||||
* @param[in] recipientDeviceId the recipient ID, specific to current device(gruu)
|
||||
* @param[in] recipientUserId the recipient ID, not specific to a device(could be a sip-uri) or a user(could be a group sip-uri)
|
||||
* @param[int/out] DRSessions list of DR Sessions linked to sender device, first one shall be the one registered as active
|
||||
* @param[out] cipherHeader message holding the random decryption key encrypted by the DR session
|
||||
* @param[out] cipherMessage message encrypted with a random generated key(and IV)
|
||||
* @param[out] plaintext decrypted message
|
||||
*
|
||||
* @return a shared pointer towards the session used to decrypt, nullptr if we couldn't find one to do it
|
||||
*/
|
||||
template <typename Curve>
|
||||
std::shared_ptr<DR<Curve>> decryptMessage(const std::string& sourceDeviceId, const std::string& recipientDeviceId, const std::string& recipientUserId, std::vector<std::shared_ptr<DR<Curve>>>& DRSessions, const std::vector<uint8_t>& cipherHeader, const std::vector<uint8_t>& cipherMessage, std::vector<uint8_t>& plaintext);
|
||||
|
||||
/**
|
||||
* @brief check the message for presence of X3DH init in the header, parse it if there is one
|
||||
*
|
||||
* @param[in] header A buffer holding the message, it shall be DR header | DR message. If there is a X3DH init message it is in the DR header
|
||||
* @param[out] x3dhInitMessage A buffer holding the X3DH input message
|
||||
*
|
||||
* @return true if a X3DH init message was found, false otherwise
|
||||
*
|
||||
* This one is implemented here as is deals with parsing the DR packet header but is not really related to DR session
|
||||
*/
|
||||
template <typename Curve>
|
||||
bool get_X3DH_initMessage(const std::vector<uint8_t> &header, std::vector<uint8_t> &X3DH_initMessage);
|
||||
|
||||
/* this templates are instanciated once in the lime_double_ratchet.cpp file, explicitly tell anyone including this header that there is no need to re-instanciate them */
|
||||
#ifdef EC25519_ENABLED
|
||||
extern template class DR<C255>;
|
||||
extern template void encryptMessage<C255>(std::vector<recipientInfos<C255>>& recipients, const std::vector<uint8_t>& plaintext, const std::string& recipientUserId, const std::string& sourceDeviceId, std::vector<uint8_t>& cipherMessage);
|
||||
extern template std::shared_ptr<DR<C255>> decryptMessage<C255>(const std::string& sourceDeviceId, const std::string& recipientDeviceId, const std::string& recipientUserId, std::vector<std::shared_ptr<DR<C255>>>& DRSessions, const std::vector<uint8_t>& cipherHeader, const std::vector<uint8_t>& cipherMessage, std::vector<uint8_t>& plaintext);
|
||||
extern template bool get_X3DH_initMessage<C255>(const std::vector<uint8_t> &header, std::vector<uint8_t> &X3DH_initMessage);
|
||||
#endif
|
||||
#ifdef EC448_ENABLED
|
||||
extern template class DR<C448>;
|
||||
extern template void encryptMessage<C448>(std::vector<recipientInfos<C448>>& recipients, const std::vector<uint8_t>& plaintext, const std::string& recipientUserId, const std::string& sourceDeviceId, std::vector<uint8_t>& cipherMessage);
|
||||
extern template std::shared_ptr<DR<C448>> decryptMessage<C448>(const std::string& sourceDeviceId, const std::string& recipientDeviceId, const std::string& recipientUserId, std::vector<std::shared_ptr<DR<C448>>>& DRSessions, const std::vector<uint8_t>& cipherHeader, const std::vector<uint8_t>& cipherMessage, std::vector<uint8_t>& plaintext);
|
||||
extern template bool get_X3DH_initMessage<C448>(const std::vector<uint8_t> &header, std::vector<uint8_t> &X3DH_initMessage);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif /* lime_double_ratchet_hpp */
|
298
src/lime_double_ratchet_protocol.cpp
Normal file
298
src/lime_double_ratchet_protocol.cpp
Normal file
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
lime_double_ratchet_protocol.cpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define BCTBX_LOG_DOMAIN "lime"
|
||||
#include <bctoolbox/logging.h>
|
||||
|
||||
#include "lime/lime.hpp"
|
||||
#include "lime_double_ratchet_protocol.hpp"
|
||||
|
||||
#include "bctoolbox/exception.hh"
|
||||
|
||||
using namespace::std;
|
||||
using namespace::lime;
|
||||
|
||||
namespace lime {
|
||||
// Group in this namespace all the functions related to building or parsing double ratchet packets
|
||||
namespace double_ratchet_protocol {
|
||||
/* Implemented version of the DR session protocol (provide a way to handle future/alternative packets formats/crypto algorithm)
|
||||
* Supported version description :
|
||||
*
|
||||
* Version 0x01:
|
||||
* DRHeader is: Protocol Version Number<1 byte> || Packet Type <1 byte> || curveId <1 byte> || [X3DH Init message <variable>] || Ns<4 bytes> || PN<4 bytes> || DHs<...>
|
||||
* Message is : DRheaer<...> || cipherMessageKeyK<48 bytes> || Key auth tag<16 bytes> || cipherText<...> || Message auth tag<16 bytes>
|
||||
*
|
||||
* Associated Data are transmitted separately: ADk for the Key auth tag, and ADm for the Message auth tag
|
||||
* Message AEAD on : (ADm, message plain text) keyed by message Key(include IV)
|
||||
* Key AEAD on : (ADk || Message auth tag || header, Message Key) keyed by Double Ratchet generated key/IV
|
||||
*
|
||||
* ADm is : source GRUU<...> || recipient sip-uri(can be a group uri)<...>
|
||||
* ADk is : source GRUU<...> || recipient GRUU<...>
|
||||
* Note: ADk is used with session stored AD provided by X3DH at session creation which is HKDF(initiator Ik || receiver Ik || initiator device Id || receiver device Id)
|
||||
*
|
||||
* Diffie-Hellman support: X25519 or X448 (not mixed, specified by X3DH server and client setting which must match)
|
||||
*
|
||||
* Packets types are : regular or x3dhinit
|
||||
* - regular packet does not contain x3dh init message
|
||||
* - x3dh init packet includes x3dh init message in the header as follow:
|
||||
* haveOPk <flag 1 byte> || self Ik < ED<Curve>::keyLength() bytes > || Ek < X<Curve>::keyLenght() bytes> || peer SPk id < 4 bytes > || [peer OPk id(if flag is set)<4bytes>]
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief build an X3DH init message to insert in DR header
|
||||
* haveOPk <flag 1 byte> || self Ik < ED<Curve>::keyLength() bytes > || Ek < X<Curve>::keyLenght() bytes> || peer SPk id < 4 bytes > || [peer OPk id(if flag is set)<4bytes>]
|
||||
*
|
||||
* @param[out] message the X3DH init message
|
||||
* @param[in] Ik self public identity key
|
||||
* @param[in] Ek self public ephemeral key
|
||||
* @param[in] SPk_id id of peer signed prekey used
|
||||
* @param[in] OPk_id id of peer OneTime prekey used(if any)
|
||||
* @param[in] OPk_flag do we used an OPk?
|
||||
*
|
||||
*/
|
||||
template <typename Curve>
|
||||
void buildMessage_X3DHinit(std::vector<uint8_t> &message, const ED<Curve> &Ik, const X<Curve> &Ek, const uint32_t SPk_id, const uint32_t OPk_id, const bool OPk_flag) noexcept {
|
||||
// make sure message is cleared and set its first byte to OPk flag
|
||||
message.assign(1, static_cast<uint8_t>(OPk_flag?DR_X3DH_OPk_flag::withOPk:DR_X3DH_OPk_flag::withoutOPk));
|
||||
message.reserve(1+Ik.size()+Ek.size()+4+(OPk_flag?4:0));
|
||||
|
||||
message.insert(message.end(), Ik.begin(), Ik.end());
|
||||
message.insert(message.end(), Ek.begin(), Ek.end());
|
||||
message.push_back((SPk_id>>24)&0xFF);
|
||||
message.push_back((SPk_id>>16)&0xFF);
|
||||
message.push_back((SPk_id>>8)&0xFF);
|
||||
message.push_back((SPk_id)&0xFF);
|
||||
if (OPk_flag) {
|
||||
message.push_back((OPk_id>>24)&0xFF);
|
||||
message.push_back((OPk_id>>16)&0xFF);
|
||||
message.push_back((OPk_id>>8)&0xFF);
|
||||
message.push_back((OPk_id)&0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse the X3DH init message and extract peer Ik, peer Ek, self SPk id and seld OPk id if present
|
||||
* usedOPk <flag on one byte> || peer Ik || peer Ek || self SPk id || self OPk id(if flag is set)
|
||||
*
|
||||
* When this function is called, we already parsed the DR message to extract the X3DH_initMessage
|
||||
* all checks were already performed by the Double Ratchet packet parser, just grab the data
|
||||
*
|
||||
* @param[in] message the message to parse
|
||||
* @param[out] Ik peer public Identity key
|
||||
* @param[out] Ek peer public Ephemeral key
|
||||
* @param[out] SPk_id self Signed prekey id
|
||||
* @param[out] OPk_id self One Time prekey id(if used, 0 otherwise)
|
||||
* @param[out] OPk_flag true if an OPk flag was present in the message
|
||||
*/
|
||||
template <typename Curve>
|
||||
void parseMessage_X3DHinit(const std::vector<uint8_t>message, ED<Curve> &Ik, X<Curve> &Ek, uint32_t &SPk_id, uint32_t &OPk_id, bool &OPk_flag) noexcept {
|
||||
OPk_flag = (message[0] == static_cast<uint8_t>(DR_X3DH_OPk_flag::withOPk))?true:false;
|
||||
size_t index = 1;
|
||||
|
||||
Ik.assign(message.begin()+index);
|
||||
index += ED<Curve>::keyLength();
|
||||
|
||||
Ek.assign(message.begin()+index);
|
||||
index += X<Curve>::keyLength();
|
||||
|
||||
SPk_id = static_cast<uint32_t>(message[index])<<24 |
|
||||
static_cast<uint32_t>(message[index+1])<<16 |
|
||||
static_cast<uint32_t>(message[index+2])<<8 |
|
||||
static_cast<uint32_t>(message[index+3]);
|
||||
|
||||
if (OPk_flag) { // there is an OPk id
|
||||
index+=4;
|
||||
OPk_id = static_cast<uint32_t>(message[index])<<24 |
|
||||
static_cast<uint32_t>(message[index+1])<<16 |
|
||||
static_cast<uint32_t>(message[index+2])<<8 |
|
||||
static_cast<uint32_t>(message[index+3]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief check the message for presence of X3DH init in the header, extract it if there is one
|
||||
*
|
||||
* @param[in] message A buffer holding the message, it shall be DR header || DR message. If there is a X3DH init message it is in the DR header
|
||||
* @param[out] x3dhInitMessage A buffer holding the X3DH input message
|
||||
*
|
||||
* @return true if a X3DH init message was found, false otherwise (also in case of invalid packet)
|
||||
*/
|
||||
template <typename Curve>
|
||||
bool parseMessage_get_X3DHinit(const std::vector<uint8_t> &message, std::vector<uint8_t> &X3DH_initMessage) noexcept {
|
||||
// we need to parse the first 4 bytes of the packet to determine if we have a valid one and an X3DH init in it
|
||||
if (message.size()<4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (message[0]) { // header[0] contains DR protocol version
|
||||
case double_ratchet_protocol::DR_v01: // version 0x01 of protocol
|
||||
{
|
||||
// if curveId is not matching or message type is not x3dhinit, return false
|
||||
if (message[2] != static_cast<uint8_t>(Curve::curveId()) || message[1]!=static_cast<uint8_t>(DR_message_type::x3dhinit)) {
|
||||
return false;
|
||||
}
|
||||
// check length
|
||||
size_t x3dh_initMessageSize = 1 + ED<Curve>::keyLength() + X<Curve>::keyLength() + 4; // size of X3DH init message without OPk
|
||||
if (message[3] == 1) { // there is an OPk
|
||||
x3dh_initMessageSize += 4;
|
||||
}
|
||||
// header is: Protocol Version Number<1 byte> || Packet Type <1 byte> || curveId <1 byte> || [X3DH Init message <variable>] || Ns<4 bytes> || PN<4 bytes> || DHs<...>
|
||||
// regular packet header length is 11 + X<Curve>::keuLength(), check our buffer holds at least a complete header
|
||||
if (message.size() < x3dh_initMessageSize + 11 + X<Curve>::keyLength()) { //header shall be actually longer because buffer passed is the whole message
|
||||
return false;
|
||||
}
|
||||
// copy the message in the output buffer
|
||||
X3DH_initMessage.assign(message.begin()+3, message.begin()+3+x3dh_initMessageSize);
|
||||
}
|
||||
return true;
|
||||
|
||||
default :
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build a header string from needed info
|
||||
* header is: Protocol Version Number<1 byte> || Packet Type <1 byte> || curveId <1 byte> || [X3DH Init message <variable>] || Ns<4 bytes> || PN<4 bytes> || DHs<...>
|
||||
*
|
||||
* @param[out] header output buffer
|
||||
* @param[in] Ns Index of sending chain
|
||||
* @param[in] PN Index of previous sending chain
|
||||
* @param[in] DHs Current DH public key
|
||||
* @param[in] X3DH_initMessage A buffer holding an X3DH init message to be inserted in header, if empty packet type is set to regular
|
||||
*/
|
||||
template <typename Curve>
|
||||
void buildMessage_header(std::vector<uint8_t> &header, const uint32_t Ns, const uint32_t PN, const X<Curve> &DHs, const std::vector<uint8_t> X3DH_initMessage) noexcept {
|
||||
// Header is one buffer composed of:
|
||||
// Version Number<1 byte> || packet Type <1 byte> || curve Id <1 byte> || [<x3d init <variable>] || Ns <4 bytes> || PN <4 bytes> || Key type byte Id(1 byte) || self public key<DHKey::size bytes>
|
||||
header.assign(1, static_cast<uint8_t>(double_ratchet_protocol::DR_v01));
|
||||
if (X3DH_initMessage.size()>0) { // we do have an X3DH init message to insert in the header
|
||||
header.push_back(static_cast<uint8_t>(lime::double_ratchet_protocol::DR_message_type::x3dhinit));
|
||||
header.push_back(static_cast<uint8_t>(Curve::curveId()));
|
||||
header.insert(header.end(), X3DH_initMessage.begin(), X3DH_initMessage.end());
|
||||
} else {
|
||||
header.push_back(static_cast<uint8_t>(lime::double_ratchet_protocol::DR_message_type::regular));
|
||||
header.push_back(static_cast<uint8_t>(Curve::curveId()));
|
||||
}
|
||||
header.push_back((uint8_t)((Ns>>24)&0xFF));
|
||||
header.push_back((uint8_t)((Ns>>16)&0xFF));
|
||||
header.push_back((uint8_t)((Ns>>8)&0xFF));
|
||||
header.push_back((uint8_t)(Ns&0xFF));
|
||||
header.push_back((uint8_t)((PN>>24)&0xFF));
|
||||
header.push_back((uint8_t)((PN>>16)&0xFF));
|
||||
header.push_back((uint8_t)((PN>>8)&0xFF));
|
||||
header.push_back((uint8_t)(PN&0xFF));
|
||||
header.insert(header.end(), DHs.begin(), DHs.end());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief DRHeader ctor parse a buffer to find a header at the begining of it
|
||||
* it perform some check on DR version byte and key id byte
|
||||
* The _valid flag is set if a valid header is found in input buffer
|
||||
*/
|
||||
template <typename Curve>
|
||||
DRHeader<Curve>::DRHeader(const std::vector<uint8_t> header) : m_Ns{0},m_PN{0},m_DHs{},m_valid{false},m_size{0}{ // init valid to false and check during parsing if all is ok
|
||||
// if we don't have a least a minimal size, just return, valid is set to false
|
||||
// make sure we have at least enough data to parse version<1 byte> || message type<1 byte> || curve Id<1 byte> || [x3dh init] || OPk flag without any ulterior checks on size
|
||||
// getting these 4 bytes allow us to compute the expected size of header
|
||||
if (header.size()<4) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (header[0]) { // header[0] contains DR protocol version
|
||||
case lime::double_ratchet_protocol::DR_v01: // version 0x01 of protocol, see in lime_utils for details
|
||||
if (header[2] != static_cast<uint8_t>(Curve::curveId())) return; // wrong curve in use, return with valid flag false
|
||||
|
||||
switch (header[1]) { // packet types
|
||||
case static_cast<uint8_t>(lime::double_ratchet_protocol::DR_message_type::regular) :
|
||||
// header is : Version<1 byte> ||
|
||||
// packet type <1 byte> ||
|
||||
// curve id <1 byte> ||
|
||||
// Ns<4 bytes> ||
|
||||
// PN <4 bytes> ||
|
||||
// DHs < X<Curve>::keyLength() >
|
||||
m_size = 11 + X<Curve>::keyLength();
|
||||
if (header.size() >= m_size) { //header shall be actually longer because buffer pass is the whole message
|
||||
m_Ns = header[3]<<24|header[4]<<16|header[5]<<8|header[6];
|
||||
m_PN = header[7]<<24|header[8]<<16|header[9]<<8|header[10];
|
||||
m_DHs = X<Curve>{header.data()+11}; // DH key start after header other infos
|
||||
m_valid = true;
|
||||
}
|
||||
break;
|
||||
case static_cast<uint8_t>(lime::double_ratchet_protocol::DR_message_type::x3dhinit) :
|
||||
{
|
||||
// header is : Version<1 byte> ||
|
||||
// packet type <1 byte> ||
|
||||
// curve id <1 byte> ||
|
||||
// x3dh init message <variable> ||
|
||||
// Ns<4 bytes> || PN <4 bytes> ||
|
||||
// DHs < X<Curve>::keyLength() >
|
||||
//
|
||||
// x3dh init is : haveOPk <flag 1 byte : 0 no OPk, 1 OPk > ||
|
||||
// self Ik < ED<Curve>::keyLength() bytes > ||
|
||||
// Ek < X<Curve>::keyLenght() bytes > ||
|
||||
// peer SPk id < 4 bytes > ||
|
||||
// [peer OPk id < 4 bytes >] {0,1} according to haveOPk flag
|
||||
size_t x3dh_initMessageSize = 1 + ED<Curve>::keyLength() + X<Curve>::keyLength() + 4; // add size of X3DH init message without OPk
|
||||
if (header[3] == 1) { // there is an OPk
|
||||
x3dh_initMessageSize += 4;
|
||||
}
|
||||
m_size = 11 + X<Curve>::keyLength() + x3dh_initMessageSize;
|
||||
|
||||
// X3DH init message is processed separatly, just take care of the DR header values
|
||||
if (header.size() >= m_size) { //header shall be actually longer because buffer pass is the whole message
|
||||
m_Ns = header[3+x3dh_initMessageSize]<<24|header[4+x3dh_initMessageSize]<<16|header[5+x3dh_initMessageSize]<<8|header[6+x3dh_initMessageSize];
|
||||
m_PN = header[7+x3dh_initMessageSize]<<24|header[8+x3dh_initMessageSize]<<16|header[9+x3dh_initMessageSize]<<8|header[10+x3dh_initMessageSize];
|
||||
m_DHs = X<Curve>{header.data()+11+x3dh_initMessageSize}; // DH key start after header other infos
|
||||
m_valid = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: // unknown packet type, just return with valid set to false
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
default: // just do nothing, we do not know this version of header, don't parse anything and leave its valid flag to false
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Instanciate templated functions */
|
||||
#ifdef EC25519_ENABLED
|
||||
template void buildMessage_X3DHinit<C255>(std::vector<uint8_t> &message, const ED<C255> &Ik, const X<C255> &Ek, const uint32_t SPk_id, const uint32_t OPk_id, const bool OPk_flag) noexcept;
|
||||
template void parseMessage_X3DHinit<C255>(const std::vector<uint8_t>message, ED<C255> &Ik, X<C255> &Ek, uint32_t &SPk_id, uint32_t &OPk_id, bool &OPk_flag) noexcept;
|
||||
template bool parseMessage_get_X3DHinit<C255>(const std::vector<uint8_t> &message, std::vector<uint8_t> &X3DH_initMessage) noexcept;
|
||||
template void buildMessage_header<C255>(std::vector<uint8_t> &header, const uint32_t Ns, const uint32_t PN, const X<C255> &DHs, const std::vector<uint8_t> X3DH_initMessage) noexcept;
|
||||
template class DRHeader<C255>;
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
template void buildMessage_X3DHinit<C448>(std::vector<uint8_t> &message, const ED<C448> &Ik, const X<C448> &Ek, const uint32_t SPk_id, const uint32_t OPk_id, const bool OPk_flag) noexcept;
|
||||
template void parseMessage_X3DHinit<C448>(const std::vector<uint8_t>message, ED<C448> &Ik, X<C448> &Ek, uint32_t &SPk_id, uint32_t &OPk_id, bool &OPk_flag) noexcept;
|
||||
template bool parseMessage_get_X3DHinit<C448>(const std::vector<uint8_t> &message, std::vector<uint8_t> &X3DH_initMessage) noexcept;
|
||||
template void buildMessage_header<C448>(std::vector<uint8_t> &header, const uint32_t Ns, const uint32_t PN, const X<C448> &DHs, const std::vector<uint8_t> X3DH_initMessage) noexcept;
|
||||
template class DRHeader<C448>;
|
||||
#endif
|
||||
|
||||
} // namespace double_ratchet_protocol
|
||||
} //namespace lime
|
131
src/lime_double_ratchet_protocol.hpp
Normal file
131
src/lime_double_ratchet_protocol.hpp
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
lime_x3dh_protocol.hpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef lime_double_ratchet_protocol_hpp
|
||||
#define lime_double_ratchet_protocol_hpp
|
||||
|
||||
#include "lime_keys.hpp"
|
||||
|
||||
namespace lime {
|
||||
namespace double_ratchet_protocol {
|
||||
/**
|
||||
* @brief build an X3DH init message to insert in DR header
|
||||
* haveOPk <flag 1 byte> || self Ik < ED<Curve>::keyLength() bytes > || Ek < X<Curve>::keyLenght() bytes> || peer SPk id < 4 bytes > || [peer OPk id(if flag is set)<4bytes>]
|
||||
*
|
||||
* @param[out] message the X3DH init message
|
||||
* @param[in] Ik self public identity key
|
||||
* @param[in] Ek self public ephemeral key
|
||||
* @param[in] SPk_id id of peer signed prekey used
|
||||
* @param[in] OPk_id id of peer OneTime prekey used(if any)
|
||||
* @param[in] OPk_flag do we used an OPk?
|
||||
*
|
||||
*/
|
||||
template <typename Curve>
|
||||
void buildMessage_X3DHinit(std::vector<uint8_t> &message, const ED<Curve> &Ik, const X<Curve> &Ek, const uint32_t SPk_id, const uint32_t OPk_id, const bool OPk_flag) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Parse the X3DH init message and extract peer Ik, peer Ek, self SPk id and seld OPk id if present
|
||||
* usedOPk <flag on one byte> || peer Ik || peer Ek || self SPk id || self OPk id(if flag is set)
|
||||
*
|
||||
* When this function is called, we already parsed the DR message to extract the X3DH_initMessage
|
||||
* all checks were already performed by the Double Ratchet packet parser, just grab the data
|
||||
*
|
||||
* @param[in] message the message to parse
|
||||
* @param[out] Ik peer public Identity key
|
||||
* @param[out] Ek peer public Ephemeral key
|
||||
* @param[out] SPk_id self Signed prekey id
|
||||
* @param[out] OPk_id self One Time prekey id(if used, 0 otherwise)
|
||||
* @param[out] OPk_flag true if an OPk flag was present in the message
|
||||
*/
|
||||
template <typename Curve>
|
||||
void parseMessage_X3DHinit(const std::vector<uint8_t>message, ED<Curve> &Ik, X<Curve> &Ek, uint32_t &SPk_id, uint32_t &OPk_id, bool &OPk_flag) noexcept;
|
||||
|
||||
/**
|
||||
* @brief check the message for presence of X3DH init in the header, extract it if there is one
|
||||
*
|
||||
* @param[in] message A buffer holding the message, it shall be DR header || DR message. If there is a X3DH init message it is in the DR header
|
||||
* @param[out] x3dhInitMessage A buffer holding the X3DH input message
|
||||
*
|
||||
* @return true if a X3DH init message was found, false otherwise (also in case of invalid packet)
|
||||
*/
|
||||
template <typename Curve>
|
||||
bool parseMessage_get_X3DHinit(const std::vector<uint8_t> &message, std::vector<uint8_t> &X3DH_initMessage) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Build a header string from needed info
|
||||
* header is: Protocol Version Number<1 byte> || Packet Type <1 byte> || curveId <1 byte> || [X3DH Init message <variable>] || Ns<4 bytes> || PN<4 bytes> || DHs<...>
|
||||
*
|
||||
* @param[out] header the buffer containing header to be sent to recipient
|
||||
* @param[in] Ns Index of sending chain
|
||||
* @param[in] PN Index of previous sending chain
|
||||
* @param[in] DHs Current DH public key
|
||||
* @param[in] X3DH_initMessage A buffer holding an X3DH init message to be inserted in header, if empty packet type is set to regular
|
||||
*/
|
||||
template <typename Curve>
|
||||
void buildMessage_header(std::vector<uint8_t> &header, const uint32_t Ns, const uint32_t PN, const X<Curve> &DHs, const std::vector<uint8_t> X3DH_initMessage) noexcept;
|
||||
|
||||
/**
|
||||
* DR message header: helper class and functions to parse message header and access its components
|
||||
*
|
||||
*/
|
||||
template <typename Curve>
|
||||
class DRHeader {
|
||||
private:
|
||||
uint32_t m_Ns,m_PN; // Sender chain and Previous Sender chain indexes.
|
||||
X<Curve> m_DHs; // Public key
|
||||
bool m_valid; // is this header valid?
|
||||
size_t m_size; // store the size of parsed header
|
||||
|
||||
public:
|
||||
/* data member accessors (read only) */
|
||||
auto Ns(void) const {return m_Ns;}
|
||||
auto PN(void) const {return m_PN;}
|
||||
const X<Curve> &DHs(void) const {return m_DHs;}
|
||||
auto valid(void) const {return m_valid;}
|
||||
size_t size(void) {return m_size;}
|
||||
|
||||
/* ctor/dtor */
|
||||
DRHeader() = delete;
|
||||
DRHeader(const std::vector<uint8_t> header);
|
||||
~DRHeader() = default;
|
||||
};
|
||||
|
||||
/* this templates are intanciated in lime_double_ratchet_procotocol.cpp, do not re-instanciate it anywhere else */
|
||||
#ifdef EC25519_ENABLED
|
||||
extern template void buildMessage_X3DHinit<C255>(std::vector<uint8_t> &message, const ED<C255> &Ik, const X<C255> &Ek, const uint32_t SPk_id, const uint32_t OPk_id, const bool OPk_flag) noexcept;
|
||||
extern template void parseMessage_X3DHinit<C255>(const std::vector<uint8_t>message, ED<C255> &Ik, X<C255> &Ek, uint32_t &SPk_id, uint32_t &OPk_id, bool &OPk_flag) noexcept;
|
||||
extern template bool parseMessage_get_X3DHinit<C255>(const std::vector<uint8_t> &message, std::vector<uint8_t> &X3DH_initMessage) noexcept;
|
||||
extern template void buildMessage_header<C255>(std::vector<uint8_t> &header, const uint32_t Ns, const uint32_t PN, const X<C255> &DHs, const std::vector<uint8_t> X3DH_initMessage) noexcept;
|
||||
extern template class DRHeader<C255>;
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
extern template void buildMessage_X3DHinit<C448>(std::vector<uint8_t> &message, const ED<C448> &Ik, const X<C448> &Ek, const uint32_t SPk_id, const uint32_t OPk_id, const bool OPk_flag) noexcept;
|
||||
extern template void parseMessage_X3DHinit<C448>(const std::vector<uint8_t>message, ED<C448> &Ik, X<C448> &Ek, uint32_t &SPk_id, uint32_t &OPk_id, bool &OPk_flag) noexcept;
|
||||
extern template bool parseMessage_get_X3DHinit<C448>(const std::vector<uint8_t> &message, std::vector<uint8_t> &X3DH_initMessage) noexcept;
|
||||
extern template void buildMessage_header<C448>(std::vector<uint8_t> &header, const uint32_t Ns, const uint32_t PN, const X<C448> &DHs, const std::vector<uint8_t> X3DH_initMessage) noexcept;
|
||||
extern template class DRHeader<C448>;
|
||||
#endif
|
||||
/* These constants are needed only for tests purpose, otherwise their usage is internal only */
|
||||
constexpr std::uint8_t DR_v01=0x01;
|
||||
enum class DR_message_type : uint8_t{unset_type=0x00, regular=0x01, x3dhinit=0x02};
|
||||
enum class DR_X3DH_OPk_flag : uint8_t{withoutOPk=0x00, withOPk=0x01};
|
||||
|
||||
} // namespace double_ratchet_protocol
|
||||
}// namespace lime
|
||||
#endif // lime_double_ratchet_protocol_hpp
|
151
src/lime_impl.hpp
Normal file
151
src/lime_impl.hpp
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
lime_impl.hpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef lime_impl_hpp
|
||||
#define lime_impl_hpp
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
|
||||
#include "lime/lime.hpp"
|
||||
#include "lime_lime.hpp"
|
||||
#include "lime_keys.hpp"
|
||||
#include "lime_localStorage.hpp"
|
||||
#include "lime_double_ratchet.hpp"
|
||||
#include "lime_x3dh_protocol.hpp"
|
||||
|
||||
namespace lime {
|
||||
// an enum used by network state engine to manage sequence packet sending(at user creation)
|
||||
enum class network_state : uint8_t {done=0x00, sendSPk=0x01, sendOPk=0x02};
|
||||
|
||||
template <typename Curve>
|
||||
struct callbackUserData;
|
||||
|
||||
/* templated declaration of Lime can be specialised using C255 or C448 according to the elliptic curve we want to use */
|
||||
template <typename Curve>
|
||||
class Lime : public LimeGeneric, public std::enable_shared_from_this<Lime<Curve>> {
|
||||
private:
|
||||
/*** data members ***/
|
||||
/* general purpose */
|
||||
bctbx_rng_context_t *m_RNG; // Random Number Generator context
|
||||
std::string m_selfDeviceId; // self device Id, shall be the GRUU
|
||||
|
||||
/* X3DH keys */
|
||||
KeyPair<ED<Curve>> m_Ik; // our identity key pair, is loaded from DB only if requested(to sign a SPK or to perform X3DH init)
|
||||
bool m_Ik_loaded; // did we load the Ik yet?
|
||||
|
||||
/* local storage related */
|
||||
std::shared_ptr<lime::Db> m_localStorage; // shared pointer would be used/stored in Double Ratchet Sessions
|
||||
long int m_db_Uid; // the Uid in database, retrieved at creation/load, used for faster access
|
||||
|
||||
/* network related */
|
||||
belle_http_provider_t *m_http_provider; // externally provided http stack used to communicate with x3dh server
|
||||
std::string m_X3DH_Server_URL; // url of x3dh key server
|
||||
|
||||
/* Double ratchet related */
|
||||
std::unordered_map<std::string, std::shared_ptr<DR<Curve>>> m_DR_sessions_cache; // store already loaded DR session
|
||||
|
||||
/* encryption queue: encryption requesting asynchronous operation(connection to X3DH server) are queued to avoid repeating a request to server */
|
||||
std::shared_ptr<callbackUserData<Curve>> m_ongoing_encryption;
|
||||
std::queue<std::shared_ptr<callbackUserData<Curve>>> m_encryption_queue;
|
||||
|
||||
/*** Private functions ***/
|
||||
/* database related functions, implementation is in lime_localStorage.cpp */
|
||||
// create user in DB, throw an exception if already there or something went wrong
|
||||
bool create_user();
|
||||
// user load from DB is implemented directly as a Db member function, output of it is passed to Lime<> ctor
|
||||
void get_SelfIdentityKey(); // check our Identity key pair is loaded in Lime object, retrieve it from DB if it isn't
|
||||
void cache_DR_sessions(std::vector<recipientInfos<Curve>> &internal_recipients, std::vector<std::string> &missing_devices); // loop on internal recipient an try to load in DR session cache the one which have no session attached
|
||||
void get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDRSessionId, std::vector<std::shared_ptr<DR<Curve>>> &DRSessions); // load from local storage in DRSessions all DR session matching the peerDeviceId, ignore the one picked by id in 2nd arg
|
||||
long int store_peerDevice(const std::string &peerDeviceId, const ED<Curve> &Ik); // store given peer Device Id and public identity key, return the Id used in table to store it
|
||||
|
||||
/* X3DH related - part related to exchange with server or localStorage - implemented in lime_x3dh_protocol.cpp or lime_localStorage.cpp */
|
||||
void X3DH_generate_SPk(X<Curve> &publicSPk, Signature<Curve> &SPk_sig, uint32_t &SPk_id); // generate a new Signed Pre-Key key pair, store it in DB and set its public key, signature and Id in given params
|
||||
void X3DH_generate_OPks(std::vector<X<Curve>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number); // generate a new batch of OPks, store them in base and fill the vector with information to be sent to X3DH server
|
||||
void X3DH_get_SPk(uint32_t SPk_id, KeyPair<X<Curve>> &SPk); // retrieve matching SPk from localStorage, throw an exception if not found
|
||||
void X3DH_get_OPk(uint32_t OPk_id, KeyPair<X<Curve>> &SPk); // retrieve matching OPk from localStorage, throw an exception if not found
|
||||
/* X3DH related - part related to X3DH DR session initiation, implemented in lime_x3dh.cpp */
|
||||
void X3DH_init_sender_session(const std::vector<X3DH_peerBundle<Curve>> &peersBundle); // compute a sender X3DH using the data from peer bundle, then create and load the DR_Session
|
||||
std::shared_ptr<DR<Curve>> X3DH_init_receiver_session(const std::vector<uint8_t> X3DH_initMessage, const std::string &senderDeviceId); // from received X3DH init packet, try to compute the shared secrets, then create the DR_Session
|
||||
|
||||
/* network related */
|
||||
void postToX3DHServer(callbackUserData<Curve> *userData, const std::vector<uint8_t> &message); // send a request to X3DH server
|
||||
static void process_response(void *data, const belle_http_response_event_t *event) noexcept; // callback on server response (hence the static)
|
||||
void cleanUserData(callbackUserData<Curve> *userData); // clean user data
|
||||
|
||||
public: /* Implement API defined in lime.hpp in factory abstract class */
|
||||
/**
|
||||
* @brief Constructors:
|
||||
* - one would create a new user in localStorage and assign it a server and curve id
|
||||
* - one to load the user from db based on provided user Id(which shall be GRUU)
|
||||
* Note: ownership of localStorage pointer is transfered to a shared pointer, private menber of Lime class
|
||||
*/
|
||||
Lime(std::unique_ptr<lime::Db> &&localStorage, const std::string &userId, const std::string &url, belle_http_provider_t *http_provider);
|
||||
Lime(std::unique_ptr<lime::Db> &&localStorage, const std::string &userId, const std::string &url, belle_http_provider_t *http_provider, const long Uid);
|
||||
~Lime();
|
||||
Lime(Lime<Curve> &a) = delete; // can't copy a session, force usage of shared pointers
|
||||
Lime<Curve> &operator=(Lime<Curve> &a) = delete; // can't copy a session
|
||||
|
||||
void publish_user(const limeCallback &callback);
|
||||
void delete_user(const limeCallback &callback);
|
||||
|
||||
void encrypt(std::shared_ptr<const std::string> recipientUserId, std::shared_ptr<std::vector<recipientData>> recipients, std::shared_ptr<const std::vector<uint8_t>> plainMessage, std::shared_ptr<std::vector<uint8_t>> cipherMessage, const limeCallback &callback) override;
|
||||
bool decrypt(const std::string &recipientUserId, const std::string &senderDeviceId, const std::vector<uint8_t> &cipherHeader, const std::vector<uint8_t> &cipherMessage, std::vector<uint8_t> &plainMessage) override;
|
||||
};
|
||||
|
||||
// structure holding user data during callback
|
||||
template <typename Curve>
|
||||
struct callbackUserData {
|
||||
// always needed
|
||||
std::weak_ptr<Lime<Curve>> limeObj; // Lime is owned by the LimeManager, it shall no be destructed, do not own this with a shared_ptr as Lime obj may own the callbackUserData obj thus creating circular reference
|
||||
const limeCallback callback; // is a lambda closure, not real idea of what is its lifetime but it seems ok to hold it this way
|
||||
// needed for encryption: get a shared ref to keep params alive
|
||||
std::shared_ptr<const std::string> recipientUserId;
|
||||
std::shared_ptr<std::vector<recipientData>> recipients;
|
||||
std::shared_ptr<const std::vector<uint8_t>> plainMessage;
|
||||
std::shared_ptr<std::vector<uint8_t>> cipherMessage;
|
||||
lime::network_state network_state_machine; /* used to run a simple state machine at user creation to perform sequence of packet sending: registerUser, postSPk, postOPks */
|
||||
|
||||
// when created at user_create/delete
|
||||
callbackUserData(std::weak_ptr<Lime<Curve>> thiz, const limeCallback &callbackRef, bool startRegisterUserSequence=false)
|
||||
: limeObj{thiz}, callback{callbackRef},
|
||||
recipientUserId{nullptr}, recipients{nullptr}, plainMessage{nullptr}, cipherMessage{nullptr}, network_state_machine{startRegisterUserSequence?lime::network_state::sendSPk:lime::network_state::done} {};
|
||||
// when created at encrypt
|
||||
callbackUserData(std::weak_ptr<Lime<Curve>> thiz, const limeCallback &callbackRef,
|
||||
std::shared_ptr<const std::string> recipientUserId, std::shared_ptr<std::vector<recipientData>> recipients,
|
||||
std::shared_ptr<const std::vector<uint8_t>> plainMessage, std::shared_ptr<std::vector<uint8_t>> cipherMessage)
|
||||
: limeObj{thiz}, callback{callbackRef},
|
||||
recipientUserId{recipientUserId}, recipients{recipients}, plainMessage{plainMessage}, cipherMessage{cipherMessage}, network_state_machine {lime::network_state::done} {};// copy construct all shared_ptr
|
||||
// do not copy callback data, force passing the pointer around after creation
|
||||
callbackUserData(callbackUserData &a) = delete;
|
||||
callbackUserData operator=(callbackUserData &a) = delete;
|
||||
};
|
||||
|
||||
|
||||
/* this template is intanciated in lime.cpp, do not re-instanciate it anywhere else */
|
||||
#ifdef EC25519_ENABLED
|
||||
extern template class Lime<C255>;
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
extern template class Lime<C448>;
|
||||
#endif
|
||||
|
||||
}
|
||||
#endif /* lime_impl_hpp */
|
97
src/lime_keys.cpp
Normal file
97
src/lime_keys.cpp
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
lime_keys.cpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define BCTBX_LOG_DOMAIN "lime"
|
||||
#include <bctoolbox/logging.h>
|
||||
|
||||
#include "lime_keys.hpp"
|
||||
|
||||
namespace lime {
|
||||
|
||||
/****************************************************************/
|
||||
/* ECDH keys */
|
||||
/****************************************************************/
|
||||
/* function to create a ECDH context */
|
||||
template <typename Curve>
|
||||
bctbx_ECDHContext_t *ECDHInit(void) {
|
||||
/* if this template is instanciated the static_assert will fail but will give us an error message with faulty Curve type */
|
||||
static_assert(sizeof(Curve) != sizeof(Curve), "You must specialize sessionsInit<> for your type: correctly initialise the ECDH context");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef EC25519_ENABLED
|
||||
/* specialise ECDH context creation */
|
||||
template <> bctbx_ECDHContext_t *ECDHInit<C255>(void) {
|
||||
return bctbx_CreateECDHContext(BCTBX_ECDH_X25519);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
/* specialise ECDH context creation */
|
||||
template <> bctbx_ECDHContext_t *ECDHInit<C448>(void) {
|
||||
return bctbx_CreateECDHContext(BCTBX_ECDH_X448);
|
||||
}
|
||||
#endif
|
||||
|
||||
/****************************************************************/
|
||||
/* EdDSA keys */
|
||||
/****************************************************************/
|
||||
/* function to create a EDDSA context */
|
||||
template <typename Curve>
|
||||
bctbx_EDDSAContext_t *EDDSAInit(void) {
|
||||
/* if this template is instanciated the static_assert will fail but will give us an error message with DRType */
|
||||
static_assert(sizeof(Curve) != sizeof(Curve), "You must specialize sessionsInit<> for your type: correctly initialise the ECDH context");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef EC25519_ENABLED
|
||||
/* specialise EDDSA context creation */
|
||||
template <> bctbx_EDDSAContext_t *EDDSAInit<C255>(void) {
|
||||
return bctbx_CreateEDDSAContext(BCTBX_EDDSA_25519);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
/* specialise EDDSA context creation */
|
||||
template <> bctbx_EDDSAContext_t *EDDSAInit<C448>(void) {
|
||||
return bctbx_CreateEDDSAContext(BCTBX_EDDSA_448);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* instanciate the template specialisation */
|
||||
#ifdef EC255_ENABLED
|
||||
template bctbx_ECDHContext_t *ECDHInit<C255>(void);
|
||||
template bctbx_EDDSAContext_t *EDDSAInit<C255>(void);
|
||||
template class X<C255>;
|
||||
template class ED<C255>;
|
||||
template class KeyPair<X<C255>>;
|
||||
template class KeyPair<ED<C255>>;
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
template bctbx_ECDHContext_t *ECDHInit<C448>(void);
|
||||
template bctbx_EDDSAContext_t *EDDSAInit<C448>(void);
|
||||
template class X<C448>;
|
||||
template class ED<C448>;
|
||||
template class KeyPair<X<C448>>;
|
||||
template class KeyPair<ED<C448>>;
|
||||
#endif
|
||||
}
|
153
src/lime_keys.hpp
Normal file
153
src/lime_keys.hpp
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
lime_keys.hpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef lime_keys_hpp
|
||||
#define lime_keys_hpp
|
||||
|
||||
#include <algorithm> //std::copy_n
|
||||
#include <array>
|
||||
#include <iterator>
|
||||
#include "bctoolbox/crypto.h"
|
||||
#include "lime/lime.hpp"
|
||||
|
||||
namespace lime {
|
||||
// all keys types have an ID byte prepended to it before sending it in any message
|
||||
// as recommended in X3DH spec section 2.1
|
||||
enum class keyByteId : uint8_t {x25519=0x01, x448=0x02, ed25519=0x81, ed448=0x82};
|
||||
// also define in an enum their key size
|
||||
enum class keySize : size_t {x25519=BCTBX_ECDH_X25519_PUBLIC_SIZE, x448=BCTBX_ECDH_X448_PUBLIC_SIZE, ed25519=BCTBX_EDDSA_25519_PUBLIC_SIZE, ed448=BCTBX_EDDSA_448_PUBLIC_SIZE};
|
||||
enum class sigSize : size_t {ed25519=BCTBX_EDDSA_25519_SIGNATURE_SIZE, ed448=BCTBX_EDDSA_448_SIGNATURE_SIZE};
|
||||
|
||||
/* define needed constant for the curves: self identificatio(used in DB and as parameter from lib users, keys sizes)*/
|
||||
/* These structure are used as template argument to enable support for different EC. Some templates specialisation MUST be define in lime_keys.cpp to be able to use them */
|
||||
struct C255 { // curve 25519, use a 4 chars to identify it to improve code readability
|
||||
static constexpr lime::CurveId curveId() {return lime::CurveId::c25519;};
|
||||
static constexpr size_t XkeySize() {return static_cast<size_t>(keySize::x25519);};
|
||||
static constexpr uint8_t XkeyByteId() {return static_cast<uint8_t>(keyByteId::x25519);};
|
||||
static constexpr size_t EDkeySize() {return static_cast<size_t>(keySize::ed25519);};
|
||||
static constexpr size_t EDSigSize() {return static_cast<size_t>(sigSize::ed25519);};
|
||||
static constexpr uint8_t EDkeyByteId() {return static_cast<uint8_t>(keyByteId::ed25519);};
|
||||
};
|
||||
|
||||
struct C448 { // curve 448-goldilocks
|
||||
static constexpr lime::CurveId curveId() {return lime::CurveId::c448;};
|
||||
static constexpr size_t XkeySize() {return static_cast<size_t>(keySize::x448);};
|
||||
static constexpr uint8_t XkeyByteId() {return static_cast<uint8_t>(keyByteId::x448);};
|
||||
static constexpr size_t EDkeySize() {return static_cast<size_t>(keySize::ed448);};
|
||||
static constexpr size_t EDSigSize() {return static_cast<size_t>(sigSize::ed448);};
|
||||
static constexpr uint8_t EDkeyByteId() {return static_cast<uint8_t>(keyByteId::ed448);};
|
||||
};
|
||||
|
||||
|
||||
/****************************************************************/
|
||||
/* ECDH keys */
|
||||
/****************************************************************/
|
||||
/* function to create a ECDH context */
|
||||
template <typename Curve>
|
||||
bctbx_ECDHContext_t *ECDHInit(void);
|
||||
|
||||
template <typename Curve>
|
||||
class X : public std::array<uint8_t, static_cast<size_t>(Curve::XkeySize())>{
|
||||
public :
|
||||
constexpr static size_t keyLength(void) {return Curve::XkeySize();}; // provide a static size function to be able to call the function not on an object
|
||||
constexpr static uint8_t byteId(void) {return Curve::XkeyByteId();};
|
||||
X(const uint8_t *buffer) {std::copy_n(buffer, Curve::XkeySize(), this->data());} // construct from a C style buffer
|
||||
X(std::vector<uint8_t>::const_iterator buffer) {std::copy_n(buffer, Curve::XkeySize(), this->begin());} // construct from a std::vector<uint8_t>
|
||||
X() {};
|
||||
void assign(std::vector<uint8_t>::const_iterator buffer) {std::copy_n(buffer, Curve::XkeySize(), this->begin());} // copy from a std::vector<uint8_t>
|
||||
};
|
||||
|
||||
/****************************************************************/
|
||||
/* EdDSA keys and signature */
|
||||
/****************************************************************/
|
||||
/* function to create a ECDH context */
|
||||
template <typename Curve>
|
||||
bctbx_EDDSAContext_t *EDDSAInit(void);
|
||||
|
||||
template <typename Curve>
|
||||
class ED : public std::array<uint8_t, static_cast<size_t>(Curve::EDkeySize())>{
|
||||
public :
|
||||
constexpr static size_t keyLength(void) {return Curve::EDkeySize();}; // provide a static size function to be able to call the function not on an object
|
||||
constexpr static uint8_t byteId(void) {return Curve::EDkeyByteId();};
|
||||
ED(const uint8_t *buffer) {std::copy_n(buffer, Curve::EDkeySize(), this->data());} // construct from a C style buffer
|
||||
ED(std::vector<uint8_t>::const_iterator buffer) {std::copy_n(buffer, Curve::EDkeySize(), this->begin());} // contruct from a std::vector<uint8_t>
|
||||
ED() {};
|
||||
void assign(std::vector<uint8_t>::const_iterator buffer) {std::copy_n(buffer, Curve::EDkeySize(), this->begin());} // copy from a std::vector<uint8_t>
|
||||
};
|
||||
|
||||
template <typename Curve>
|
||||
class Signature : public std::array<uint8_t, static_cast<size_t>(Curve::EDSigSize())>{
|
||||
public :
|
||||
constexpr static size_t signatureLength(void) {return Curve::EDSigSize();}; // provide a static size function to be able to call the function not on an object
|
||||
Signature(const uint8_t *buffer) {std::copy_n(buffer, Curve::EDSigSize(), this->data());}
|
||||
Signature() {};
|
||||
};
|
||||
|
||||
|
||||
/****************************************************************/
|
||||
/* Common structure: key pair */
|
||||
/****************************************************************/
|
||||
template <typename Key>
|
||||
class KeyPair {
|
||||
private:
|
||||
/* for all supported algo(X25519, X448, ED25519, ED448), private and public keys have the same size, so we use the same type to declare them in a pair */
|
||||
Key _pubKey;
|
||||
Key _privKey;
|
||||
public:
|
||||
size_t size(void) {return _pubKey.size();}; // by construction private and public are of the same type so have same size so we can return any size fron this two keys.
|
||||
Key &privateKey(void) {return _privKey;};
|
||||
Key &publicKey(void) {return _pubKey;};
|
||||
KeyPair(Key pub, Key priv):_pubKey(pub),_privKey(priv) {};
|
||||
KeyPair(uint8_t *pub, uint8_t *priv):_pubKey{pub},_privKey{priv} {};
|
||||
KeyPair() :_pubKey{},_privKey{}{};
|
||||
bool operator==(KeyPair<Key> b) const {return (_privKey==b.privateKey() && _pubKey==b.publicKey());};
|
||||
};
|
||||
|
||||
/****************************************************************/
|
||||
/* Template are instanciated in lime_keys.cpp */
|
||||
/* Do not re-instiate them elsewhere */
|
||||
/****************************************************************/
|
||||
#ifdef EC25519_ENABLED
|
||||
/* declare specialisation for C255 */
|
||||
template <> bctbx_ECDHContext_t *ECDHInit<C255>(void);
|
||||
template <> bctbx_EDDSAContext_t *EDDSAInit<C255>(void);
|
||||
/* ask any file including lime_keys.hpp to not instantiate the follownings as it is done in lime_keys.cpp*/
|
||||
extern template bctbx_ECDHContext_t *ECDHInit<C255>(void);
|
||||
extern template bctbx_EDDSAContext_t *EDDSAInit<C255>(void);
|
||||
extern template class X<C255>;
|
||||
extern template class ED<C255>;
|
||||
extern template class KeyPair<X<C255>>;
|
||||
extern template class KeyPair<ED<C255>>;
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
/* declare specialisation for C488 */
|
||||
template <> bctbx_ECDHContext_t *ECDHInit<C448>(void);
|
||||
template <> bctbx_EDDSAContext_t *EDDSAInit<C448>(void);
|
||||
/* ask any file including lime_keys.hpp to not instantiate the follownings as it is done in lime_keys.cpp*/
|
||||
extern template bctbx_ECDHContext_t *ECDHInit<C448>(void);
|
||||
extern template bctbx_EDDSAContext_t *EDDSAInit<C448>(void);
|
||||
extern template class X<C448>;
|
||||
extern template class ED<C448>;
|
||||
extern template class KeyPair<X<C448>>;
|
||||
extern template class KeyPair<ED<C448>>;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif /* lime_keys_hpp */
|
67
src/lime_lime.hpp
Normal file
67
src/lime_lime.hpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
lime_lime.hpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef lime_lime_hpp
|
||||
#define lime_lime_hpp
|
||||
|
||||
#include <memory> // unique_ptr
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "belle-sip/belle-sip.h"
|
||||
|
||||
namespace lime {
|
||||
|
||||
/* A pure abstract class, implementation used is set by curveId parameter given to insert/load_limeUser function */
|
||||
/* NOTE: never instanciate directly a Lime object, always use the Lime Factory function as Lime object MUST be held by a shared pointer */
|
||||
class LimeGeneric {
|
||||
|
||||
public:
|
||||
/* Encrypt/Decrypt */
|
||||
virtual void encrypt(std::shared_ptr<const std::string> recipientUserId, std::shared_ptr<std::vector<recipientData>> recipients, std::shared_ptr<const std::vector<uint8_t>> plainMessage, std::shared_ptr<std::vector<uint8_t>> cipherMessage, const limeCallback &callback) = 0;
|
||||
virtual bool decrypt(const std::string &recipientUserId, const std::string &senderDeviceId, const std::vector<uint8_t> &cipherHeader, const std::vector<uint8_t> &cipherMessage, std::vector<uint8_t> &plainMessage) = 0;
|
||||
virtual void delete_user(const limeCallback &callback) = 0;
|
||||
virtual ~LimeGeneric() {};
|
||||
};
|
||||
|
||||
/* Lime Factory functions : return a pointer to the implementation using the specified elliptic curve. Two functions: one for creation, one for loading from local storage */
|
||||
|
||||
/**
|
||||
* @brief Create a local lime user and insert all its needed data in a DB, it will trigger identity key creation and communication of it, SPKs and OPKs to key server
|
||||
*
|
||||
* @param[in] dbFilename path to the database to be used
|
||||
* @param[in] userId a unique identifier to a local user, if not already present in base it will be inserted. Recommended value: device's GRUU
|
||||
* @param[in] keyServer URL of X3DH key server(WARNING : matching between elliptic curve usage of all clients on the same server is responsability of clients)
|
||||
* @param[in] curve select which Elliptic curve to base X3DH and Double ratchet on: Curve25519 or Curve448,
|
||||
* this is set once at user creation and can't be modified, it must reflect key server preference.
|
||||
* @return a unique pointer to the object to be used by this user for any Lime operations
|
||||
*/
|
||||
std::shared_ptr<LimeGeneric> insert_LimeUser(const std::string &dbFilename, const std::string &userId, const std::string &url, const lime::CurveId curve,
|
||||
belle_http_provider_t *http_provider, const limeCallback &callback);
|
||||
|
||||
/**
|
||||
* @brief Load a local user from database
|
||||
*
|
||||
* @param[in] dbFilename path to the database to be used
|
||||
* @param[in] userId a unique identifier to a local user, if not already present in base it will be inserted. Recommended value: device's GRUU
|
||||
*
|
||||
* @return a unique pointer to the object to be used by this user for any Lime operations
|
||||
*/
|
||||
std::shared_ptr<LimeGeneric> load_LimeUser(const std::string &dbFilename, const std::string &userId, belle_http_provider_t *http_provider);
|
||||
|
||||
}
|
||||
#endif // lime_lime_hpp
|
767
src/lime_localStorage.cpp
Normal file
767
src/lime_localStorage.cpp
Normal file
|
@ -0,0 +1,767 @@
|
|||
/*
|
||||
lime_x3dh.cpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define BCTBX_LOG_DOMAIN "lime"
|
||||
#include <bctoolbox/logging.h>
|
||||
|
||||
#include "lime/lime.hpp"
|
||||
#include "lime_localStorage.hpp"
|
||||
#include "lime_double_ratchet.hpp"
|
||||
#include "lime_impl.hpp"
|
||||
#include "bctoolbox/exception.hh"
|
||||
|
||||
#include "soci/sqlite3/soci-sqlite3.h"
|
||||
using namespace::std;
|
||||
using namespace::soci;
|
||||
using namespace::lime;
|
||||
|
||||
namespace lime {
|
||||
|
||||
Db::Db(std::string filename) : sql{sqlite3, filename}{
|
||||
int userVersion=-1;
|
||||
sql<<"PRAGMA user_version;",into(userVersion);
|
||||
sql<<"PRAGMA foreign_keys = ON;"; // make sure this connection enable foreign keys
|
||||
if (userVersion != lime::settings::DBuserVersion) {
|
||||
if (userVersion > lime::settings::DBuserVersion) { /* nothing to do if we encounter a superior version number than expected, just hope it is compatible */
|
||||
//TODO: Log this event, throw an execption?
|
||||
//TODO: use a table for versionning not the user_version pragma
|
||||
} else { /* Perform update if needed */
|
||||
// update the schema version in DB metadata, the above line shall work but if fails at runtime in soci lib on syntax error
|
||||
//sql<<"PRAGMA user_version = :versionNumber;", use(lime::settings::DBuserVersion);
|
||||
sql<<"PRAGMA user_version = "<<lime::settings::DBuserVersion<<";"; // This one does the job. This pragma is valid in sqlite3 but might not be with other soci backends
|
||||
|
||||
// whole DB creation:
|
||||
transaction tr(sql); // begin a transaction which will hold all the create table queries (no sense in allowing the DB to be partially created)
|
||||
|
||||
/*** Double Ratchet tables ***/
|
||||
/* DR Session:
|
||||
* - DId : Device Id is a foreign key from lime_PeerDevices table: peer device Id, allow to retrieve sessions linking to a peer device and a local account
|
||||
* - SessionId(primary key)
|
||||
* - Ns, Nr, PN : index for sending, receivind and previous sending chain
|
||||
* - DHr : peer current public ECDH key
|
||||
* - DHs public, DHs private : self current ECDH key
|
||||
* - RK, CKs, CKr : Root key, sender and receiver chain keys
|
||||
* - AD : Associated data(provided once at session creation by X3DH, shall be initiator public Ik||receiver public Ik)
|
||||
* - Status : 0 is for stale and 1 is for active, only one session shall be active for a peer device, by default created as active
|
||||
* - timeStamp : is updated when session change status and is used to remove stale session after determined time in cleaning operation
|
||||
* - X3DHInit : when we are initiator, store the generated X3DH init message and keep sending it until we've got at least a reply from peer
|
||||
*/
|
||||
sql<<"CREATE TABLE DR_sessions( \
|
||||
Did INTEGER NOT NULL DEFAULT 0, \
|
||||
sessionId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \
|
||||
Ns UNSIGNED INTEGER NOT NULL, \
|
||||
Nr UNSIGNED INTEGER NOT NULL, \
|
||||
PN UNSIGNED INTEGER NOT NULL, \
|
||||
DHr BLOB NOT NULL, \
|
||||
DHs_pub BLOB NOT NULL, \
|
||||
DHs_priv BLOB NOT NULL, \
|
||||
RK BLOB NOT NULL, \
|
||||
CKs BLOB NOT NULL, \
|
||||
CKr BLOB NOT NULL, \
|
||||
AD BLOB NOT NULL, \
|
||||
Status INTEGER NOT NULL DEFAULT 1, \
|
||||
timeStamp DATETIME DEFAULT CURRENT_TIMESTAMP, \
|
||||
X3DHInit BLOB DEFAULT NULL, \
|
||||
FOREIGN KEY(Did) REFERENCES lime_PeerDevices(Did) ON UPDATE CASCADE ON DELETE CASCADE)";
|
||||
|
||||
/* DR Message Skipped DH : DHid(primary key), SessionId, DHr */
|
||||
sql<<"CREATE TABLE DR_MSk_DHr( \
|
||||
DHid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \
|
||||
sessionId INTEGER NOT NULL DEFAULT 0, \
|
||||
DHr BLOB NOT NULL);";
|
||||
|
||||
/* DR Message Skipped MK : [DHid,NR](primary key), MK */
|
||||
sql<<"CREATE TABLE DR_MSk_MK( \
|
||||
DHid INTEGER NOT NULL, \
|
||||
Nr INTEGER NOT NULL, \
|
||||
MK BLOB NOT NULL, \
|
||||
PRIMARY KEY( DHid , Nr ));";
|
||||
|
||||
/*** Lime tables : local user identities, peer devices identities ***/
|
||||
/* List each self account enable on device :
|
||||
- Uid : primary key, used to make link with Peer Devices, SPk and OPk tables
|
||||
- User Id : shall be the GRUU
|
||||
- Ik : public||private indentity key
|
||||
- server : the URL of key Server
|
||||
- curveId : identifies the curve used by this user - MUST be in sync with server
|
||||
*/
|
||||
sql<<"CREATE TABLE lime_LocalUsers( \
|
||||
Uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \
|
||||
UserId TEXT NOT NULL, \
|
||||
Ik BLOB NOT NULL, \
|
||||
server TEXT NOT NULL, \
|
||||
curveId INTEGER NOT NULL DEFAULT 0);"; // default the curveId value to 0 which is not one of the possible values(defined in lime.hpp)
|
||||
|
||||
/* Peer Devices :
|
||||
* - Did : primary key, used to make link with DR_sessions table.
|
||||
* - DeviceId: peer device id (shall be its GRUU)
|
||||
* - Uid: link to LocalUsers table, identify which localUser registered this peer Device
|
||||
* - Ik : Peer device Identity public key, got it from X3DH server or X3DH init message
|
||||
*/
|
||||
sql<<"CREATE TABLE lime_PeerDevices( \
|
||||
Did INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, \
|
||||
DeviceId TEXT NOT NULL, \
|
||||
Uid INTEGER NOT NULL, \
|
||||
Ik BLOB NOT NULL, \
|
||||
FOREIGN KEY(Uid) REFERENCES lime_LocalUsers(Uid) ON UPDATE CASCADE ON DELETE CASCADE);";
|
||||
|
||||
/*** X3DH tables ***/
|
||||
/* Signed pre-key :
|
||||
* - SPKid : the primary key must be a random number as it is public, so avoid leaking information on number of key used
|
||||
* - SPK : Public key||Private Key (ECDH keys)
|
||||
* - timeStamp : Application shall renew SPK regurlarly(SPK_LifeTime). Old key are disactivated and deleted after a period(SPK_LimboTime))
|
||||
* - Status : a boolean: can be active(1) or stale(0), by default any newly inserted key is set to active
|
||||
* - Uid : User Id from lime_LocalUsers table: who's key is this
|
||||
*/
|
||||
sql<<"CREATE TABLE X3DH_SPK( \
|
||||
SPKid UNSIGNED INTEGER PRIMARY KEY NOT NULL, \
|
||||
SPK BLOB NOT NULL, \
|
||||
timeStamp DATETIME DEFAULT CURRENT_TIMESTAMP, \
|
||||
Status INTEGER NOT NULL DEFAULT 1, \
|
||||
Uid INTEGER NOT NULL, \
|
||||
FOREIGN KEY(Uid) REFERENCES lime_LocalUsers(Uid) ON UPDATE CASCADE ON DELETE CASCADE);";
|
||||
|
||||
/* One time pre-key : deleted after usage, generated at user creation and on X3DH server request
|
||||
* - OPKid : the primary key must be a random number as it is public, so avoid leaking information on number of key used
|
||||
* - OPK : Public key||Private Key (ECDH keys)
|
||||
* - Uid : User Id from lime_LocalUsers table: who's key is this
|
||||
*/
|
||||
sql<<"CREATE TABLE X3DH_OPK( \
|
||||
OPKid UNSIGNED INTEGER PRIMARY KEY NOT NULL, \
|
||||
OPK BLOB NOT NULL, \
|
||||
Uid INTEGER NOT NULL, \
|
||||
FOREIGN KEY(Uid) REFERENCES lime_LocalUsers(Uid) ON UPDATE CASCADE ON DELETE CASCADE);";
|
||||
|
||||
tr.commit(); // commit all the previous queries
|
||||
}
|
||||
}
|
||||
};
|
||||
/******************************************************************************/
|
||||
/* */
|
||||
/* Db public API */
|
||||
/* */
|
||||
/******************************************************************************/
|
||||
/**
|
||||
* @brief Check for existence, retrieve Uid for local user based on its userId(GRUU) and curve from table lime_LocalUsers
|
||||
*
|
||||
* @param[in] userId a string holding the user to look for in DB, shall be its GRUU
|
||||
* @param[out] Uid the DB internal Id matching given userId(if find in DB, 0 otherwise)
|
||||
* @param[out] curveId the curve selected at user creation
|
||||
*
|
||||
*/
|
||||
void Db::load_LimeUser(const std::string &userId, long int &Uid, lime::CurveId &curveId, std::string &url)
|
||||
{
|
||||
int curve=0;
|
||||
sql<<"SELECT Uid,CurveId,server FROM lime_LocalUsers WHERE UserId = :userId LIMIT 1;", into(Uid), into(curve), into(url), use(userId);
|
||||
|
||||
if (sql.got_data()) { // we found someone
|
||||
// turn back integer value retrieved from DB into a lime::CurveId
|
||||
switch (curve) {
|
||||
case static_cast<uint8_t>(lime::CurveId::c25519):
|
||||
curveId=lime::CurveId::c25519;
|
||||
break;
|
||||
case static_cast<uint8_t>(lime::CurveId::c448):
|
||||
curveId=lime::CurveId::c448;
|
||||
break;
|
||||
case static_cast<uint8_t>(lime::CurveId::unset):
|
||||
default: // we got an unknow or unset curve Id, DB is either corrupted or a future version
|
||||
curveId=lime::CurveId::unset;
|
||||
Uid=0;
|
||||
throw BCTBX_EXCEPTION << "Lime DB either corrupted or back from the future. User "<<userId<<" claim to run with unknown or unset Curve Id "<< curve;
|
||||
}
|
||||
} else { // no match: throw an execption
|
||||
Uid = 0; // be sure to reset the db_Uid to 0
|
||||
throw BCTBX_EXCEPTION << "Cannot find Lime User "<<userId<<" in DB";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief if exists, delete user
|
||||
*
|
||||
* @param[in] userId a string holding the user to look for in DB, shall be its GRUU
|
||||
*
|
||||
*/
|
||||
void Db::delete_LimeUser(const std::string &userId)
|
||||
{
|
||||
sql<<"DELETE FROM lime_LocalUsers WHERE UserId = :userId;", use(userId);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
/* */
|
||||
/* Double ratchet member functions */
|
||||
/* */
|
||||
/******************************************************************************/
|
||||
template <typename DHKey>
|
||||
bool DR<DHKey>::session_save() {
|
||||
|
||||
// open transaction
|
||||
transaction tr(m_localStorage->sql);
|
||||
|
||||
// shall we try to insert or update?
|
||||
bool MSk_DHr_Clean = false; // flag use to signal the need for late cleaning in DR_MSk_DHr table
|
||||
if (m_dbSessionId==0) { // We have no id for this session row, we shall insert a new one
|
||||
// Build blobs from DR session
|
||||
blob DHr(m_localStorage->sql);
|
||||
DHr.write(0, (char *)(m_DHr.data()), m_DHr.size());
|
||||
blob DHs_pub(m_localStorage->sql);
|
||||
DHs_pub.write(0, (char *)(m_DHs.publicKey().data()), m_DHs.publicKey().size());
|
||||
blob DHs_priv(m_localStorage->sql);
|
||||
DHs_priv.write(0, (char *)(m_DHs.privateKey().data()), m_DHs.privateKey().size());
|
||||
blob RK(m_localStorage->sql);
|
||||
RK.write(0, (char *)(m_RK.data()), m_RK.size());
|
||||
blob CKs(m_localStorage->sql);
|
||||
CKs.write(0, (char *)(m_CKs.data()), m_CKs.size());
|
||||
blob CKr(m_localStorage->sql);
|
||||
CKr.write(0, (char *)(m_CKr.data()), m_CKr.size());
|
||||
/* this one is written in base only at creation and never updated again */
|
||||
blob AD(m_localStorage->sql);
|
||||
AD.write(0, (char *)(m_sharedAD.data()), m_sharedAD.size());
|
||||
|
||||
// make sure we have no other session active with this peer DiD
|
||||
m_localStorage->sql<<"UPDATE DR_sessions SET Status = 0, timeStamp = CURRENT_TIMESTAMP WHERE Did = :Did", use(m_peerDid);
|
||||
|
||||
if (m_X3DH_initMessage.size()>0) {
|
||||
blob X3DH_initMessage(m_localStorage->sql);
|
||||
X3DH_initMessage.write(0, (char *)(m_X3DH_initMessage.data()), m_X3DH_initMessage.size());
|
||||
m_localStorage->sql<<"INSERT INTO DR_sessions(Ns,Nr,PN,DHr,DHs_pub,DHs_priv,RK,CKs,CKr,AD,Did,X3DHInit) VALUES(:Ns,:Nr,:PN,:DHr,:DHs_pub,:DHs_priv,:RK,:CKs,:CKr,:AD,:Did,:X3DHinit);", use(m_Ns), use(m_Nr), use(m_PN), use(DHr), use(DHs_pub), use(DHs_priv), use(RK), use(CKs), use(CKr), use(AD), use(m_peerDid), use(X3DH_initMessage);
|
||||
} else {
|
||||
m_localStorage->sql<<"INSERT INTO DR_sessions(Ns,Nr,PN,DHr,DHs_pub,DHs_priv,RK,CKs,CKr,AD,Did) VALUES(:Ns,:Nr,:PN,:DHr,:DHs_pub,:DHs_priv,:RK,:CKs,:CKr,:AD,:Did);", use(m_Ns), use(m_Nr), use(m_PN), use(DHr), use(DHs_pub), use(DHs_priv), use(RK), use(CKs), use(CKr), use(AD), use(m_peerDid);
|
||||
}
|
||||
// if insert went well we shall be able to retrieve the last insert id to save it in the Session object
|
||||
/*** WARNING: unportable section of code, works only with sqlite3 backend ***/
|
||||
m_localStorage->sql<<"select last_insert_rowid()",into(m_dbSessionId);
|
||||
/*** above could should work but it doesn't, consistently return false from .get_last_insert_id... ***/
|
||||
/*if (!(sql.get_last_insert_id("DR_sessions", m_dbSessionId))) {
|
||||
throw;
|
||||
} */
|
||||
} else { // we have an id, it shall already be in the db
|
||||
// Try to update an existing row
|
||||
try{ //TODO: make sure the update was a success, or we shall signal it
|
||||
switch (m_dirty) {
|
||||
case DRSessionDbStatus::dirty: // dirty case shall actually never occurs as a dirty is set only at creation not loading, first save is processed above
|
||||
case DRSessionDbStatus::dirty_ratchet: // ratchet&decrypt modifies all but also request to delete X3DHInit from storage
|
||||
{
|
||||
// make sure we have no other session active with this peer DiD
|
||||
if (m_active_status == false) {
|
||||
m_localStorage->sql<<"UPDATE DR_sessions SET Status = 0, timeStamp = CURRENT_TIMESTAMP WHERE Did = :Did", use(m_peerDid);
|
||||
m_active_status = true;
|
||||
}
|
||||
|
||||
// Build blobs from DR session
|
||||
blob DHr(m_localStorage->sql);
|
||||
DHr.write(0, (char *)(m_DHr.data()), m_DHr.size());
|
||||
blob DHs_pub(m_localStorage->sql);
|
||||
DHs_pub.write(0, (char *)(m_DHs.publicKey().data()), m_DHs.publicKey().size());
|
||||
blob DHs_priv(m_localStorage->sql);
|
||||
DHs_priv.write(0, (char *)(m_DHs.privateKey().data()), m_DHs.privateKey().size());
|
||||
blob RK(m_localStorage->sql);
|
||||
RK.write(0, (char *)(m_RK.data()), m_RK.size());
|
||||
blob CKs(m_localStorage->sql);
|
||||
CKs.write(0, (char *)(m_CKs.data()), m_CKs.size());
|
||||
blob CKr(m_localStorage->sql);
|
||||
CKr.write(0, (char *)(m_CKr.data()), m_CKr.size());
|
||||
|
||||
m_localStorage->sql<<"UPDATE DR_sessions SET Ns= :Ns, Nr= :Nr, PN= :PN, DHr= :DHr,DHs_pub= :DHs_pub, DHs_priv= :DHs_priv,RK= :RK, CKs= :CKs, CKr= :CKr, Status = 1, X3DHInit = NULL WHERE sessionId = :sessionId;", use(m_Ns), use(m_Nr), use(m_PN), use(DHr), use(DHs_pub), use(DHs_priv), use(RK), use(CKs), use(CKr), use(m_dbSessionId);
|
||||
}
|
||||
break;
|
||||
case DRSessionDbStatus::dirty_decrypt: // decrypt modifies: CKr and Nr. Also set Status to active and clear X3DH init message if there is one(it is actually useless as our first reply from peer shall trigger a ratchet&decrypt)
|
||||
{
|
||||
// make sure we have no other session active with this peer DiD
|
||||
if (m_active_status == false) {
|
||||
m_localStorage->sql<<"UPDATE DR_sessions SET Status = 0, timeStamp = CURRENT_TIMESTAMP WHERE Did = :Did", use(m_peerDid);
|
||||
m_active_status = true;
|
||||
}
|
||||
|
||||
blob CKr(m_localStorage->sql);
|
||||
CKr.write(0, (char *)(m_CKr.data()), m_CKr.size());
|
||||
m_localStorage->sql<<"UPDATE DR_sessions SET Nr= :Nr, CKr= :CKr, Status = 1, X3DHInit = NULL WHERE sessionId = :sessionId;", use(m_Nr), use(CKr), use(m_dbSessionId);
|
||||
}
|
||||
break;
|
||||
case DRSessionDbStatus::dirty_encrypt: // encrypt modifies: CKs and Ns
|
||||
case DRSessionDbStatus::clean: // encrypt modifies: CKs and Ns
|
||||
{
|
||||
blob CKs(m_localStorage->sql);
|
||||
CKs.write(0, (char *)(m_CKs.data()), m_CKs.size());
|
||||
m_localStorage->sql<<"UPDATE DR_sessions SET Ns= :Ns, CKs= :CKs WHERE sessionId = :sessionId;", use(m_Ns), use(CKs), use(m_dbSessionId);
|
||||
}
|
||||
break;
|
||||
default: // Session is clean? So why have we been called?
|
||||
bctbx_error("Double ratchet session saved call on sessionId %ld but sessions appears to be clean", m_dbSessionId);
|
||||
break;
|
||||
}
|
||||
} catch (...) {
|
||||
throw;
|
||||
}
|
||||
// updatesert went well, do we have any mkskipped row to modify
|
||||
if (m_usedDHid !=0 ) { // ok, we consumed a key, remove it from db
|
||||
m_localStorage->sql<<"DELETE from DR_MSk_MK WHERE DHid = :DHid AND Nr = :Nr;", use(m_usedDHid), use(m_usedNr);
|
||||
MSk_DHr_Clean = true; // flag the cleaning needed in DR_MSk_DH table, we may have to remove a row in it if no more row are linked to it in DR_MSk_MK
|
||||
}
|
||||
}
|
||||
|
||||
// Shall we insert some skipped Message keys?
|
||||
for ( auto rChain : m_mkskipped) { // loop all chains of message keys, each one is a DHr associated to an unordered map of MK indexed by Nr to be saved
|
||||
blob DHr(m_localStorage->sql);
|
||||
DHr.write(0, (char *)(rChain.DHr.data()), rChain.DHr.size());
|
||||
long DHid=0;
|
||||
m_localStorage->sql<<"SELECT DHid FROM DR_MSk_DHr WHERE sessionId = :sessionId AND DHr = :DHr LIMIT 1",into(DHid), use(m_dbSessionId), use(DHr);
|
||||
if (!m_localStorage->sql.got_data()) { // There is no row in DR_MSk_DHr matching this key, we must add it
|
||||
m_localStorage->sql<<"INSERT INTO DR_MSk_DHr(sessionId, DHr) VALUES(:sessionId, :DHr)", use(m_dbSessionId), use(DHr);
|
||||
m_localStorage->sql<<"select last_insert_rowid()",into(DHid); // WARNING: unportable code, sqlite3 only, see above for more details on similar issue
|
||||
}
|
||||
// insert all the skipped key in the chain
|
||||
uint32_t Nr;
|
||||
blob MK(m_localStorage->sql);
|
||||
statement st = (m_localStorage->sql.prepare << "INSERT INTO DR_MSk_MK(DHid,Nr,MK) VALUES(:DHid,:Nr,:Mk)", use(DHid), use(Nr), use(MK));
|
||||
|
||||
for (const auto &kv : rChain.messageKeys) { // messageKeys is an unordered map of MK indexed by Nr.
|
||||
Nr=kv.first;
|
||||
MK.write(0, (char *)kv.second.data(), kv.second.size());
|
||||
st.execute(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Now do the cleaning(remove unused row from DR_MKs_DHr table) if needed
|
||||
if (MSk_DHr_Clean == true) {
|
||||
uint32_t Nr;
|
||||
m_localStorage->sql<<"SELECT Nr from DR_MSk_MK WHERE DHid = :DHid LIMIT 1;", into(Nr), use(m_usedDHid);
|
||||
if (!m_localStorage->sql.got_data()) { // no more MK with this DHid, remove it
|
||||
m_localStorage->sql<<"DELETE from DR_MSk_DHr WHERE DHid = :DHid;", use(m_usedDHid);
|
||||
}
|
||||
}
|
||||
|
||||
tr.commit();
|
||||
return true;
|
||||
};
|
||||
|
||||
template <typename DHKey>
|
||||
bool DR<DHKey>::session_load() {
|
||||
// blobs to store DR session data
|
||||
blob DHr(m_localStorage->sql);
|
||||
blob DHs_pub(m_localStorage->sql);
|
||||
blob DHs_priv(m_localStorage->sql);
|
||||
blob RK(m_localStorage->sql);
|
||||
blob CKs(m_localStorage->sql);
|
||||
blob CKr(m_localStorage->sql);
|
||||
blob AD(m_localStorage->sql);
|
||||
blob X3DH_initMessage(m_localStorage->sql);
|
||||
|
||||
// create an empty DR session
|
||||
indicator ind;
|
||||
int status; // retrieve an int from DB, turn it into a bool to store in object
|
||||
m_localStorage->sql<<"SELECT Did,Ns,Nr,PN,DHr,DHs_pub,DHs_priv,RK,CKs,CKr,AD,Status,X3DHInit FROM DR_sessions WHERE sessionId = :sessionId LIMIT 1", into(m_peerDid), into(m_Ns), into(m_Nr), into(m_PN), into(DHr), into(DHs_pub), into(DHs_priv), into(RK), into(CKs), into(CKr), into(AD), into(status), into(X3DH_initMessage,ind), use(m_dbSessionId);
|
||||
|
||||
if (m_localStorage->sql.got_data()) { // TODO : some more specific checks on length of retrieved data?
|
||||
DHr.read(0, (char *)(m_DHr.data()), m_DHr.size());
|
||||
DHs_pub.read(0, (char *)(m_DHs.publicKey().data()), m_DHs.publicKey().size());
|
||||
DHs_priv.read(0, (char *)(m_DHs.privateKey().data()), m_DHs.privateKey().size());
|
||||
RK.read(0, (char *)(m_RK.data()), m_RK.size());
|
||||
CKs.read(0, (char *)(m_CKs.data()), m_CKs.size());
|
||||
CKr.read(0, (char *)(m_CKr.data()), m_CKr.size());
|
||||
AD.read(0, (char *)(m_sharedAD.data()), m_sharedAD.size());
|
||||
if (ind == i_ok && X3DH_initMessage.get_len()>0) {
|
||||
m_X3DH_initMessage.resize(X3DH_initMessage.get_len());
|
||||
X3DH_initMessage.read(0, (char *)(m_X3DH_initMessage.data()), m_X3DH_initMessage.size());
|
||||
}
|
||||
if (status==1) {
|
||||
m_active_status = true;
|
||||
} else {
|
||||
m_active_status = false;
|
||||
}
|
||||
return true;
|
||||
} else { // something went wrong with the DB, we cannot retrieve the session
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Curve>
|
||||
bool DR<Curve>::trySkippedMessageKeys(const uint32_t Nr, const X<Curve> &DHr, DRMKey &MK) {
|
||||
blob MK_blob(m_localStorage->sql);
|
||||
blob DHr_blob(m_localStorage->sql);
|
||||
DHr_blob.write(0, (char *)(DHr.data()), DHr.size());
|
||||
|
||||
indicator ind;
|
||||
m_localStorage->sql<<"SELECT m.MK, m.DHid FROM DR_MSk_MK as m INNER JOIN DR_MSk_DHr as d ON d.DHid=m.DHid WHERE d.sessionId = :sessionId AND d.DHr = :DHr AND m.Nr = :Nr LIMIT 1", into(MK_blob,ind), into(m_usedDHid), use(m_dbSessionId), use(DHr_blob), use(Nr);
|
||||
// we didn't find anything
|
||||
if (!m_localStorage->sql.got_data() || ind != i_ok || MK_blob.get_len()!=MK.size()) {
|
||||
m_usedDHid=0; // make sure the DHid is not set when we didn't find anything as it is later used to remove confirmed used key from DB
|
||||
return false;
|
||||
}
|
||||
// record the Nr of extracted to be able to delete it fron base later(if decrypt ends well)
|
||||
m_usedNr=Nr;
|
||||
|
||||
MK_blob.read(0, (char *)(MK.data()), MK.size());
|
||||
return true;
|
||||
};
|
||||
/* template instanciations for Curves 25519 and 448 */
|
||||
#ifdef EC25519_ENABLED
|
||||
template bool DR<C255>::session_load();
|
||||
template bool DR<C255>::session_save();
|
||||
template bool DR<C255>::trySkippedMessageKeys(const uint32_t Nr, const X<C255> &DHr, DRMKey &MK);
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
template bool DR<C448>::session_load();
|
||||
template bool DR<C448>::session_save();
|
||||
template bool DR<C448>::trySkippedMessageKeys(const uint32_t Nr, const X<C448> &DHr, DRMKey &MK);
|
||||
#endif
|
||||
|
||||
/******************************************************************************/
|
||||
/* */
|
||||
/* Lime members functions */
|
||||
/* */
|
||||
/******************************************************************************/
|
||||
/**
|
||||
* @brief Create a new local user based on its userId(GRUU) from table lime_LocalUsers
|
||||
*
|
||||
* use m_selfDeviceId as input, use m_X3DH_Server as input
|
||||
* populate m_db_Uid
|
||||
*
|
||||
* @return true if user was created successfully, exception is thrown otherwise.
|
||||
*
|
||||
* @exception BCTBX_EXCEPTION thrown if user already exists in the database
|
||||
*/
|
||||
template <typename Curve>
|
||||
bool Lime<Curve>::create_user()
|
||||
{
|
||||
// check if the user is not already in the DB
|
||||
int dummy_Uid;
|
||||
m_localStorage->sql<<"SELECT Uid FROM lime_LocalUsers WHERE UserId = :userId LIMIT 1;", into(dummy_Uid), use(m_selfDeviceId);
|
||||
if (m_localStorage->sql.got_data()) {
|
||||
throw BCTBX_EXCEPTION << "Lime user "<<m_selfDeviceId<<" cannot be created: it is already in Database - delete it before if you really want to replace it";
|
||||
}
|
||||
|
||||
// generate an identity EDDSA key pair
|
||||
auto EDDSAContext = EDDSAInit<Curve>();
|
||||
bctbx_EDDSACreateKeyPair(EDDSAContext, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
|
||||
// store it in a blob : Public||Private
|
||||
blob Ik(m_localStorage->sql);
|
||||
Ik.write(0, (const char *)EDDSAContext->publicKey, EDDSAContext->pointCoordinateLength);
|
||||
Ik.write(EDDSAContext->pointCoordinateLength, (const char *)EDDSAContext->secretKey, EDDSAContext->secretLength);
|
||||
/* set the Ik in Lime object */
|
||||
//m_Ik = std::move(KeyPair<ED<Curve>>{EDDSAContext->publicKey, EDDSAContext->secretKey});
|
||||
bctbx_DestroyEDDSAContext(EDDSAContext);
|
||||
|
||||
|
||||
// insert in DB
|
||||
try {
|
||||
m_localStorage->sql<<"INSERT INTO lime_LocalUsers(UserId,Ik,server,curveId) VALUES (:userId,:Ik,:server,:curveId) ", use(m_selfDeviceId), use(Ik), use(m_X3DH_Server_URL), use(static_cast<uint8_t>(Curve::curveId()));
|
||||
} catch (exception const &e) {
|
||||
throw BCTBX_EXCEPTION << "Lime user insertion failed. DB backend says : "<<e.what();
|
||||
}
|
||||
// get the Id of inserted row
|
||||
m_localStorage->sql<<"select last_insert_rowid()",into(m_db_Uid);
|
||||
/* WARNING: previous line break portability of DB backend, specific to sqlite3.
|
||||
Following code shall work but consistently returns false and do not set m_db_Uid...*/
|
||||
/*
|
||||
if (!(m_localStorage->sql.get_last_insert_id("lime_LocalUsers", m_db_Uid)))
|
||||
throw BCTBX_EXCEPTION << "Lime user insertion failed. Couldn't retrieve last insert DB";
|
||||
}
|
||||
*/
|
||||
/* all went fine set the Ik loaded flag */
|
||||
//m_Ik_loaded = true;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Curve>
|
||||
void Lime<Curve>::get_SelfIdentityKey() {
|
||||
if (m_Ik_loaded == false) {
|
||||
blob Ik_blob(m_localStorage->sql);
|
||||
m_localStorage->sql<<"SELECT Ik FROM Lime_LocalUsers WHERE Uid = :UserId LIMIT 1;", into(Ik_blob), use(m_db_Uid);
|
||||
if (m_localStorage->sql.got_data()) { // Found it, it is stored in one buffer Public || Private
|
||||
Ik_blob.read(0, (char *)(m_Ik.publicKey().data()), m_Ik.publicKey().size()); // Read the public key
|
||||
Ik_blob.read(m_Ik.publicKey().size(), (char *)(m_Ik.privateKey().data()), m_Ik.privateKey().size()); // Read the private key
|
||||
m_Ik_loaded = true; // set the flag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename Curve>
|
||||
void Lime<Curve>::X3DH_generate_SPk(X<Curve> &publicSPk, Signature<Curve> &SPk_sig, uint32_t &SPk_id) {
|
||||
// check Identity key is loaded in Lime object context
|
||||
get_SelfIdentityKey();
|
||||
|
||||
// Generate a new ECDH Key pair
|
||||
auto ECDH_Context = ECDHInit<Curve>();
|
||||
bctbx_ECDHCreateKeyPair(ECDH_Context, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
|
||||
|
||||
// Sign the public key with our identity key
|
||||
auto EDDSA_Context = EDDSAInit<Curve>();
|
||||
bctbx_EDDSA_setPublicKey(EDDSA_Context, m_Ik.publicKey().data(), m_Ik.publicKey().size());
|
||||
bctbx_EDDSA_setSecretKey(EDDSA_Context, m_Ik.privateKey().data(), m_Ik.privateKey().size());
|
||||
auto sig_size=SPk_sig.size();
|
||||
bctbx_EDDSA_sign(EDDSA_Context, ECDH_Context->selfPublic, ECDH_Context->pointCoordinateLength, nullptr, 0, SPk_sig.data(), &sig_size);
|
||||
|
||||
// Generate a random SPk Id
|
||||
std::array<uint8_t,4> randomId;
|
||||
bctbx_rng_get(m_RNG, randomId.data(), randomId.size());
|
||||
SPk_id = static_cast<uint32_t>(randomId[0])<<24 | static_cast<uint32_t>(randomId[1])<<16 | static_cast<uint32_t>(randomId[2])<<8 | static_cast<uint32_t>(randomId[3]);
|
||||
|
||||
// insert all this in DB
|
||||
try {
|
||||
// open a transaction as both modification shall be done or none
|
||||
transaction tr(m_localStorage->sql);
|
||||
|
||||
// We must first update potential existing SPK in base from active to stale status
|
||||
m_localStorage->sql<<"UPDATE X3DH_SPK SET Status = 0, timeStamp = CURRENT_TIMESTAMP WHERE Uid = :Uid", use(m_db_Uid);
|
||||
|
||||
blob SPk_blob(m_localStorage->sql);
|
||||
SPk_blob.write(0, (const char *)ECDH_Context->selfPublic, ECDH_Context->pointCoordinateLength);
|
||||
SPk_blob.write(ECDH_Context->pointCoordinateLength, (const char *)ECDH_Context->secret, ECDH_Context->secretLength);
|
||||
m_localStorage->sql<<"INSERT INTO X3DH_SPK(SPKid,SPK,Uid) VALUES (:SPKid,:SPK,:Uid) ", use(SPk_id), use(SPk_blob), use(m_db_Uid);
|
||||
|
||||
tr.commit();
|
||||
} catch (exception const &e) {
|
||||
bctbx_DestroyECDHContext(ECDH_Context);
|
||||
bctbx_DestroyEDDSAContext(EDDSA_Context);
|
||||
throw BCTBX_EXCEPTION << "SPK insertion in DB failed. DB backend says : "<<e.what();
|
||||
}
|
||||
|
||||
// get SPk public key in output param
|
||||
publicSPk = std::move(X<Curve>{ECDH_Context->selfPublic});
|
||||
|
||||
// destroy contexts
|
||||
bctbx_DestroyECDHContext(ECDH_Context);
|
||||
bctbx_DestroyEDDSAContext(EDDSA_Context);
|
||||
}
|
||||
|
||||
template <typename Curve>
|
||||
void Lime<Curve>::X3DH_generate_OPks(std::vector<X<Curve>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number) {
|
||||
|
||||
// make room for OPk and OPk ids
|
||||
publicOPks.reserve(OPk_number);
|
||||
OPk_ids.reserve(OPk_number);
|
||||
|
||||
// Prepare DB statement
|
||||
transaction tr(m_localStorage->sql);
|
||||
blob OPk(m_localStorage->sql);
|
||||
uint32_t OPk_id;
|
||||
statement st = (m_localStorage->sql.prepare << "INSERT INTO X3DH_OPK(OPKid, OPK,Uid) VALUES(:OPKid,:OPK,:Uid)", use(OPk_id), use(OPk), use(m_db_Uid));
|
||||
|
||||
// Create an ECDH context to create key pairs
|
||||
auto ECDH_Context = ECDHInit<Curve>();
|
||||
|
||||
try {
|
||||
for (uint16_t i=0; i<OPk_number; i++) {
|
||||
// Generate a new ECDH Key pair
|
||||
bctbx_ECDHCreateKeyPair(ECDH_Context, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
|
||||
|
||||
// Generate a random SPk Id (uint32_t)
|
||||
std::array<uint8_t,4> randomId;
|
||||
bctbx_rng_get(m_RNG, randomId.data(), randomId.size());
|
||||
OPk_id = static_cast<uint32_t>(randomId[0])<<24 | static_cast<uint32_t>(randomId[1])<<16 | static_cast<uint32_t>(randomId[2])<<8 | static_cast<uint32_t>(randomId[3]);
|
||||
|
||||
// Insert in DB: store Public Key || Private Key
|
||||
OPk.write(0, (char *)(ECDH_Context->selfPublic), ECDH_Context->pointCoordinateLength);
|
||||
OPk.write(ECDH_Context->pointCoordinateLength, (char *)(ECDH_Context->secret), ECDH_Context->secretLength);
|
||||
st.execute(true);
|
||||
|
||||
// set in output vectors
|
||||
publicOPks.emplace_back(ECDH_Context->selfPublic);
|
||||
OPk_ids.push_back(OPk_id);
|
||||
}
|
||||
} catch (exception &e) {
|
||||
bctbx_DestroyECDHContext(ECDH_Context);
|
||||
throw BCTBX_EXCEPTION << "OPK insertion in DB failed. DB backend says : "<<e.what();
|
||||
}
|
||||
// commit changes to DB
|
||||
tr.commit();
|
||||
|
||||
bctbx_DestroyECDHContext(ECDH_Context);
|
||||
}
|
||||
|
||||
template <typename Curve>
|
||||
void Lime<Curve>::cache_DR_sessions(std::vector<recipientInfos<Curve>> &internal_recipients, std::vector<std::string> &missing_devices) {
|
||||
/* build a user list of missing ones : produce a list ready to be sent to SQL query: 'user','user','user',... also build a map to store shared_ptr to sessions */
|
||||
std::string sqlString_requestedDevices{""};
|
||||
std::unordered_map<std::string, std::shared_ptr<DR<Curve>>> requestedDevices; // found session will be loaded and temp stored in this
|
||||
|
||||
size_t requestedDevicesCount = 0;
|
||||
for (auto &recipient : internal_recipients) {
|
||||
if (recipient.DRSession == nullptr) {
|
||||
sqlString_requestedDevices.append("'").append(recipient.deviceId).append("',");
|
||||
requestedDevicesCount++;
|
||||
}
|
||||
}
|
||||
if (requestedDevicesCount==0) return; // we already got them all
|
||||
|
||||
sqlString_requestedDevices.pop_back(); // remove the last ','
|
||||
|
||||
/* fetch them from DB */
|
||||
std::vector<long int>sessionId(requestedDevicesCount);
|
||||
std::vector<std::string>peerId(requestedDevicesCount);
|
||||
|
||||
statement st = (m_localStorage->sql.prepare << "SELECT s.sessionId, d.DeviceId FROM DR_sessions as s INNER JOIN lime_PeerDevices as d ON s.Did=d.Did WHERE d.Uid= :Uid AND s.Status=1 AND d.DeviceId IN ("<<sqlString_requestedDevices<<");", into(sessionId), into(peerId), use(m_db_Uid));
|
||||
|
||||
st.execute();
|
||||
while (st.fetch()) { // we shall do it only once as we probably won't get more devices than requested, could happend if DB is in chaos and we have several sessions actives for one pair
|
||||
/* load session in cache for them */
|
||||
for (size_t i=0; i<sessionId.size(); i++ ) {
|
||||
requestedDevices[peerId[i]] = std::make_shared<DR<Curve>>(m_localStorage.get(), sessionId[i]); // load session from cache
|
||||
}
|
||||
|
||||
/* loop on found sessions and store them in cache */
|
||||
for (auto &recipient : requestedDevices) {
|
||||
m_DR_sessions_cache[recipient.first] = recipient.second;
|
||||
}
|
||||
|
||||
// useless but recommended by SOCI spec
|
||||
sessionId.resize(requestedDevicesCount);
|
||||
peerId.resize(requestedDevicesCount);
|
||||
}
|
||||
|
||||
/* loop on internal recipient and fill it with the found ones, store the missing ones in the missing_devices vector */
|
||||
for (auto &recipient : internal_recipients) {
|
||||
if (recipient.DRSession == nullptr) { // they are missing
|
||||
auto retrievedElem = requestedDevices.find(recipient.deviceId);
|
||||
if (retrievedElem == requestedDevices.end()) { // we didn't found this one
|
||||
missing_devices.push_back(recipient.deviceId);
|
||||
} else { // we got this one
|
||||
recipient.DRSession = std::move(retrievedElem->second); // don't need this pointer in map anymore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Store peer device information(DeviceId - GRUU -, public Ik, Uid to link it to a user) in local storage
|
||||
*
|
||||
* @param[in] peerDeviceId The device id to insert
|
||||
* @param[in] Ik The public EDDSA identity key of this device
|
||||
*
|
||||
* @return the id internally used by db to store this row
|
||||
*/
|
||||
template <typename Curve>
|
||||
long int Lime<Curve>::store_peerDevice(const std::string &peerDeviceId, const ED<Curve> &Ik) {
|
||||
blob Ik_blob(m_localStorage->sql);
|
||||
|
||||
try {
|
||||
long int Did=0;
|
||||
// make sure this device wasn't already here, if it was, check they have the same Ik
|
||||
m_localStorage->sql<<"SELECT Ik,Did FROM lime_peerDevices WHERE DeviceId = :DeviceId AND Uid = :Uid LIMIT 1;", into(Ik_blob), into(Did), use(peerDeviceId), use(m_db_Uid);
|
||||
if (m_localStorage->sql.got_data()) { // Found one
|
||||
ED<Curve> stored_Ik;
|
||||
Ik_blob.read(0, (char *)(stored_Ik.data()), stored_Ik.size()); // Read it to compare it to the given one
|
||||
if (stored_Ik == Ik) { // they match, so we just return the Did
|
||||
return Did;
|
||||
} else { // Ik are not matching, peer device changed its Ik!?! Reject
|
||||
bctbx_error("It appears that peer device %s was known with an identity key but is trying to use another one now", peerDeviceId.data());
|
||||
throw BCTBX_EXCEPTION << "Peer device "<<peerDeviceId<<" changed its Ik";
|
||||
}
|
||||
} else { // not found in local Storage
|
||||
transaction tr(m_localStorage->sql);
|
||||
Ik_blob.write(0, (char *)(Ik.data()), Ik.size());
|
||||
m_localStorage->sql<<"INSERT INTO lime_PeerDevices(DeviceId,Uid,Ik) VALUES (:deviceId,:Uid,:Ik) ", use(peerDeviceId), use(m_db_Uid), use(Ik_blob);
|
||||
m_localStorage->sql<<"select last_insert_rowid()",into(Did);
|
||||
tr.commit();
|
||||
bctbx_debug("user %s store peerDevice %s with device id %x", m_selfDeviceId.data(), peerDeviceId.data(), Did);
|
||||
return Did;
|
||||
}
|
||||
} catch (exception const &e) {
|
||||
throw BCTBX_EXCEPTION << "Peer device "<<peerDeviceId<<" insertion failed. DB backend says : "<<e.what();
|
||||
}
|
||||
}
|
||||
|
||||
// load from local storage in DRSessions all DR session matching the peerDeviceId, ignore the one picked by id in 2nd arg
|
||||
template <typename Curve>
|
||||
void Lime<Curve>::get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDRSessionId, std::vector<std::shared_ptr<DR<Curve>>> &DRSessions) {
|
||||
std::vector<long int> sessionIds(10); // get sessions 10 by 10, one fetch shall be enough anyway
|
||||
statement st = (m_localStorage->sql.prepare << "SELECT s.sessionId FROM DR_sessions as s INNER JOIN lime_PeerDevices as d ON s.Did=d.Did WHERE d.DeviceId = :senderDeviceId AND s.sessionId <> :ignoreThisDRSessionId ORDER BY s.Status DESC, timeStamp ASC;", into(sessionIds), use(senderDeviceId), use(ignoreThisDRSessionId));
|
||||
st.execute();
|
||||
|
||||
while (st.fetch()) {
|
||||
for (auto sessionId : sessionIds) {
|
||||
/* load session in cache DRSessions */
|
||||
DRSessions.push_back(make_shared<DR<Curve>>(m_localStorage.get(), sessionId)); // load session from cache
|
||||
}
|
||||
sessionIds.resize(10);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief retrieve matching SPk from localStorage, throw an exception if not found
|
||||
*
|
||||
* @param[in] SPk_id Id of the SPk we're trying to fetch
|
||||
* @param[out] SPk The SPk if found
|
||||
*/
|
||||
template <typename Curve>
|
||||
void Lime<Curve>::X3DH_get_SPk(uint32_t SPk_id, KeyPair<X<Curve>> &SPk) {
|
||||
blob SPk_blob(m_localStorage->sql);
|
||||
m_localStorage->sql<<"SELECT SPk FROM X3DH_SPk WHERE Uid = :Uid AND SPKid = :SPk_id LIMIT 1;", into(SPk_blob), use(m_db_Uid), use(SPk_id);
|
||||
if (m_localStorage->sql.got_data()) { // Found it, it is stored in one buffer Public || Private
|
||||
SPk_blob.read(0, (char *)(SPk.publicKey().data()), SPk.publicKey().size()); // Read the public key
|
||||
SPk_blob.read(SPk.publicKey().size(), (char *)(SPk.privateKey().data()), SPk.privateKey().size()); // Read the private key
|
||||
} else {
|
||||
throw BCTBX_EXCEPTION << "X3DH "<<m_selfDeviceId<<"look up for SPk id "<<SPk_id<<" failed";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief retrieve matching OPk from localStorage, throw an exception if not found
|
||||
* Note: once fetch, the OPk is deleted from localStorage
|
||||
*
|
||||
* @param[in] OPk_id Id of the OPk we're trying to fetch
|
||||
* @param[out] OPk The OPk if found
|
||||
*/
|
||||
template <typename Curve>
|
||||
void Lime<Curve>::X3DH_get_OPk(uint32_t OPk_id, KeyPair<X<Curve>> &OPk) {
|
||||
blob OPk_blob(m_localStorage->sql);
|
||||
m_localStorage->sql<<"SELECT OPk FROM X3DH_OPk WHERE Uid = :Uid AND OPKid = :OPk_id LIMIT 1;", into(OPk_blob), use(m_db_Uid), use(OPk_id);
|
||||
if (m_localStorage->sql.got_data()) { // Found it, it is stored in one buffer Public || Private
|
||||
OPk_blob.read(0, (char *)(OPk.publicKey().data()), OPk.publicKey().size()); // Read the public key
|
||||
OPk_blob.read(OPk.publicKey().size(), (char *)(OPk.privateKey().data()), OPk.privateKey().size()); // Read the private key
|
||||
m_localStorage->sql<<"DELETE FROM X3DH_OPk WHERE Uid = :Uid AND OPKid = :OPk_id;", use(m_db_Uid), use(OPk_id); // And remove it from local Storage
|
||||
} else {
|
||||
throw BCTBX_EXCEPTION << "X3DH "<<m_selfDeviceId<<"look up for OPk id "<<OPk_id<<" failed";
|
||||
}
|
||||
}
|
||||
|
||||
/* template instanciations for Curves 25519 and 448 */
|
||||
#ifdef EC25519_ENABLED
|
||||
template bool Lime<C255>::create_user();
|
||||
template void Lime<C255>::get_SelfIdentityKey();
|
||||
template void Lime<C255>::X3DH_generate_SPk(X<C255> &publicSPk, Signature<C255> &SPk_sig, uint32_t &SPk_id);
|
||||
template void Lime<C255>::X3DH_generate_OPks(std::vector<X<C255>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number);
|
||||
template void Lime<C255>::cache_DR_sessions(std::vector<recipientInfos<C255>> &internal_recipients, std::vector<std::string> &missing_devices);
|
||||
template long int Lime<C255>::store_peerDevice(const std::string &peerDeviceId, const ED<C255> &Ik);
|
||||
template void Lime<C255>::get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDBSessionId, std::vector<std::shared_ptr<DR<C255>>> &DRSessions);
|
||||
template void Lime<C255>::X3DH_get_SPk(uint32_t SPk_id, KeyPair<X<C255>> &SPk);
|
||||
template void Lime<C255>::X3DH_get_OPk(uint32_t OPk_id, KeyPair<X<C255>> &SPk);
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
template bool Lime<C448>::create_user();
|
||||
template void Lime<C448>::get_SelfIdentityKey();
|
||||
template void Lime<C448>::X3DH_generate_SPk(X<C448> &publicSPk, Signature<C448> &SPk_sig, uint32_t &SPk_id);
|
||||
template void Lime<C448>::X3DH_generate_OPks(std::vector<X<C448>> &publicOPks, std::vector<uint32_t> &OPk_ids, const uint16_t OPk_number);
|
||||
template void Lime<C448>::cache_DR_sessions(std::vector<recipientInfos<C448>> &internal_recipients, std::vector<std::string> &missing_devices);
|
||||
template long int Lime<C448>::store_peerDevice(const std::string &peerDeviceId, const ED<C448> &Ik);
|
||||
template void Lime<C448>::get_DRSessions(const std::string &senderDeviceId, const long int ignoreThisDBSessionId, std::vector<std::shared_ptr<DR<C448>>> &DRSessions);
|
||||
template void Lime<C448>::X3DH_get_SPk(uint32_t SPk_id, KeyPair<X<C448>> &SPk);
|
||||
template void Lime<C448>::X3DH_get_OPk(uint32_t OPk_id, KeyPair<X<C448>> &SPk);
|
||||
#endif
|
||||
|
||||
|
||||
} // namespace lime
|
47
src/lime_localStorage.hpp
Normal file
47
src/lime_localStorage.hpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
lime_localStorage.hpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef lime_localStorage_hpp
|
||||
#define lime_localStorage_hpp
|
||||
|
||||
#include "soci/soci.h"
|
||||
|
||||
namespace lime {
|
||||
|
||||
class Db {
|
||||
public:
|
||||
// soci connexion to DB
|
||||
soci::session sql;
|
||||
|
||||
Db()=delete; // we can't create a new DB holder without DB filename
|
||||
Db(std::string filename);
|
||||
~Db(){sql.close();};
|
||||
|
||||
void load_LimeUser(const std::string &userId, long int &Uid, lime::CurveId &curveId, std::string &url);
|
||||
void delete_LimeUser(const std::string &userId);
|
||||
};
|
||||
|
||||
#ifdef EC25519_ENABLED
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif /* lime_localStorage_hpp */
|
105
src/lime_manager.cpp
Normal file
105
src/lime_manager.cpp
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
lime_manager.cpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define BCTBX_LOG_DOMAIN "lime"
|
||||
#include <bctoolbox/logging.h>
|
||||
|
||||
#include "lime/lime.hpp"
|
||||
#include "lime_lime.hpp"
|
||||
|
||||
using namespace::std;
|
||||
|
||||
namespace lime {
|
||||
/****************************************************************************/
|
||||
/* */
|
||||
/* Lime Manager API */
|
||||
/* */
|
||||
/****************************************************************************/
|
||||
void LimeManager::create_user(const std::string &userId, const std::string &x3dhServerUrl, const lime::CurveId curve, const limeCallback &callback) {
|
||||
auto thiz = this;
|
||||
limeCallback managerCreateCallback([thiz, userId, callback](lime::callbackReturn returnCode, std::string errorMessage) {
|
||||
// first forward the callback
|
||||
callback(returnCode, errorMessage);
|
||||
|
||||
// then check if it went well, if not remove the user from cache(it will trigger destruction of the lime generic object so do it last
|
||||
// as it will also destroy the instance of this callback)
|
||||
if (returnCode != lime::callbackReturn::success) {
|
||||
thiz->m_users_cache.erase(userId);
|
||||
}
|
||||
});
|
||||
|
||||
m_users_cache.insert({userId, std::move(insert_LimeUser(m_db_access, userId, x3dhServerUrl, curve, m_http_provider, managerCreateCallback))});
|
||||
}
|
||||
|
||||
void LimeManager::delete_user(const std::string &userId, const limeCallback &callback) {
|
||||
auto thiz = this;
|
||||
limeCallback managerDeleteCallback([thiz, userId, callback](lime::callbackReturn returnCode, std::string errorMessage) {
|
||||
// first forward the callback
|
||||
callback(returnCode, errorMessage);
|
||||
|
||||
// then remove the user from cache(it will trigger destruction of the lime generic object so do it last
|
||||
// as it will also destroy the instance of this callback)
|
||||
thiz->m_users_cache.erase(userId);
|
||||
});
|
||||
|
||||
// is the user load? if no we must load it to be able to delete it(generate an exception if user doesn't exists)
|
||||
auto userElem = m_users_cache.find(userId);
|
||||
std::shared_ptr<LimeGeneric> user;
|
||||
if (userElem == m_users_cache.end()) {
|
||||
user = load_LimeUser(m_db_access, userId, m_http_provider);
|
||||
m_users_cache[userId]=user; // we must load it in cache otherwise object will be destroyed before getting into callback
|
||||
} else {
|
||||
user = userElem->second;
|
||||
}
|
||||
|
||||
user->delete_user(managerDeleteCallback);
|
||||
}
|
||||
|
||||
void LimeManager::encrypt(const std::string &localUserId, std::shared_ptr<const std::string> recipientUserId, std::shared_ptr<std::vector<recipientData>> recipients, std::shared_ptr<const std::vector<uint8_t>> plainMessage, std::shared_ptr<std::vector<uint8_t>> cipherMessage, const limeCallback &callback) {
|
||||
// Load user object
|
||||
auto userElem = m_users_cache.find(localUserId);
|
||||
std::shared_ptr<LimeGeneric> user;
|
||||
if (userElem == m_users_cache.end()) { // not in cache, load it from DB
|
||||
user = load_LimeUser(m_db_access, localUserId, m_http_provider);
|
||||
m_users_cache[localUserId]=user;
|
||||
} else {
|
||||
user = userElem->second;
|
||||
}
|
||||
|
||||
// call the encryption function
|
||||
user->encrypt(recipientUserId, recipients, plainMessage, cipherMessage, callback);
|
||||
}
|
||||
|
||||
bool LimeManager::decrypt(const std::string &localUserId, const std::string &recipientUserId, const std::string &senderDeviceId, const std::vector<uint8_t> &cipherHeader, const std::vector<uint8_t> &cipherMessage, std::vector<uint8_t> &plainMessage) {
|
||||
// Load user object
|
||||
auto userElem = m_users_cache.find(localUserId);
|
||||
std::shared_ptr<LimeGeneric> user;
|
||||
if (userElem == m_users_cache.end()) { // not in cache, load it from DB
|
||||
user = load_LimeUser(m_db_access, localUserId, m_http_provider);
|
||||
m_users_cache[localUserId]=user;
|
||||
} else {
|
||||
user = userElem->second;
|
||||
}
|
||||
|
||||
// call the decryption function
|
||||
return user->decrypt(recipientUserId, senderDeviceId, cipherHeader, cipherMessage, plainMessage);
|
||||
}
|
||||
} // namespace lime
|
75
src/lime_utils.hpp
Normal file
75
src/lime_utils.hpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
lime_utils.hpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef lime_utils_hpp
|
||||
#define lime_utils_hpp
|
||||
|
||||
namespace lime {
|
||||
// this namespace hold constants definition used as settings in all components of the lime library
|
||||
namespace settings {
|
||||
/******************************************************************************/
|
||||
/* */
|
||||
/* Generic settings: number of OPks to generate, Life time of SPks, ecc.. */
|
||||
/* */
|
||||
/******************************************************************************/
|
||||
constexpr uint16_t OPk_batch_number = 5; //TODO: 5 is ok for testing purpose what to set on real deployment
|
||||
|
||||
/******************************************************************************/
|
||||
/* */
|
||||
/* Double Ratchet related definitions */
|
||||
/* */
|
||||
/******************************************************************************/
|
||||
|
||||
// Sending, Receiving and Root key chain use 32 bytes keys (spec 3.2)
|
||||
constexpr size_t DRChainKeySize=32;
|
||||
|
||||
// Message Key are composed of a 32 bytes key and 16 bytes of IV
|
||||
constexpr size_t DRMessageKeySize=32;
|
||||
constexpr size_t DRMessageIVSize=16;
|
||||
|
||||
// AEAD generates tag 16 bytes long
|
||||
constexpr size_t DRMessageAuthTagSize=16;
|
||||
|
||||
// Each session stores a shared AD given at built and derived from Identity keys of sender and receiver
|
||||
// SharedAD is computed by X3DH as SHA256(Identity Key Sender|Identity Key Receiver)
|
||||
constexpr size_t DRSessionSharedADSize=32;
|
||||
|
||||
// Maximum number of Message we can skip(and store their keys) at reception of one message
|
||||
constexpr std::uint32_t maxMessageSkip=1024;
|
||||
|
||||
/******************************************************************************/
|
||||
/* */
|
||||
/* Local Storage related definitions */
|
||||
/* */
|
||||
/******************************************************************************/
|
||||
/* define a version number for the DB schema as an integer 0xMMmmpp */
|
||||
/* current version is 0.0.1 */
|
||||
constexpr int DBuserVersion=0x000001;
|
||||
|
||||
/******************************************************************************/
|
||||
/* */
|
||||
/* X3DH related definitions */
|
||||
/* */
|
||||
/******************************************************************************/
|
||||
const std::string X3DH_SK_info{"Lime"}; // shall be an ASCII string identifying the application (X3DH spec section 2.1)
|
||||
const std::string X3DH_AD_info{"X3DH Authenticated Data"}; // used to generate a shared AD based on Ik and deviceID
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* lime_utils_hpp */
|
267
src/lime_x3dh.cpp
Normal file
267
src/lime_x3dh.cpp
Normal file
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
lime_x3dh.cpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define BCTBX_LOG_DOMAIN "lime"
|
||||
#include <bctoolbox/logging.h>
|
||||
|
||||
#include "lime/lime.hpp"
|
||||
#include "lime_impl.hpp"
|
||||
#include "lime_double_ratchet_protocol.hpp"
|
||||
#include "bctoolbox/crypto.h"
|
||||
#include "bctoolbox/exception.hh"
|
||||
|
||||
using namespace::std;
|
||||
using namespace::lime;
|
||||
|
||||
namespace lime {
|
||||
|
||||
/**
|
||||
* @Brief Key Derivation Function. Used to derive SK(DRChainKey) from DH computation and AD from initiator and receiver ids and key
|
||||
* HKDF impleted as described in RFC5869, using SHA512 as hash function according to recommendation in X3DH spec section 2.2
|
||||
* Note: Output length requested by X3DH is 32 bytes. Using SHA512 we got it in one round of
|
||||
* expansion (RFC5869 2.3), thus only one round is implemented here:
|
||||
* PRK = HMAC-SHA512(salt, input)
|
||||
* Output = HMAC-SHA512(PRK, info || 0x01)
|
||||
*
|
||||
* with salt being a 0 filled buffer of SHA256 output length(32 bytes)
|
||||
*
|
||||
* @param[in] input Input buffer holding F || DH1 || DH2 || DH3 [|| DH4] or Ik initiator || Ik receiver || Initiator device Id || Receiver device Id
|
||||
* @param[in] info The string used as info
|
||||
* @param[out] output Output buffer, shall not be longer than 64 bits as we used SHA512 to compute and implement one round only. Templated as we need DRChainKey or SharedADBuffer typed output
|
||||
*/
|
||||
template <typename T>
|
||||
static void X3DH_HKDF(std::vector<uint8_t> &input, const std::string &info, T &output) noexcept {
|
||||
std::array<uint8_t,64> prk; // hold the output of pre-computation, as we use SHA512 gets a 64 bytes
|
||||
// expansion round input shall be info || 0x01
|
||||
std::vector<uint8_t> expansionRoundInput{info.begin(), info.end()};
|
||||
expansionRoundInput.push_back(0x01);
|
||||
std::array<uint8_t,32> zeroFilledSalt; zeroFilledSalt.fill(0);
|
||||
bctbx_hmacSha512(zeroFilledSalt.data(), zeroFilledSalt.size(), input.data(), input.size(), prk.size(), prk.data());
|
||||
bctbx_hmacSha512(prk.data(), prk.size(), expansionRoundInput.data(), expansionRoundInput.size(), output.size(), output.data());
|
||||
bctbx_clean(prk.data(), prk.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get a vector of peer bundle and initiate a DR Session with it. Created sessions are stored in lime cache and db along the X3DH init packet
|
||||
* as decribed in X3DH reference section 3.3
|
||||
*/
|
||||
template <typename Curve>
|
||||
void Lime<Curve>::X3DH_init_sender_session(const std::vector<X3DH_peerBundle<Curve>> &peersBundle) {
|
||||
for (auto &peerBundle : peersBundle) {
|
||||
// Verifify SPk_signature, throw an exception if it fails
|
||||
auto EDDSAContext = EDDSAInit<Curve>();
|
||||
bctbx_EDDSA_setPublicKey(EDDSAContext, peerBundle.Ik.data(), peerBundle.Ik.size());
|
||||
auto verifyIk = bctbx_EDDSA_verify(EDDSAContext, peerBundle.SPk.data(), peerBundle.SPk.size(), nullptr, 0, peerBundle.SPk_sig.data(), peerBundle.SPk_sig.size());
|
||||
if (verifyIk != BCTBX_VERIFY_SUCCESS) {
|
||||
bctbx_DestroyEDDSAContext(EDDSAContext);
|
||||
bctbx_error("X3DH: SPk signature verification failed for device %s", peerBundle.deviceId.data());
|
||||
throw BCTBX_EXCEPTION << "Verify signature on SPk failed for deviceId "<<peerBundle.deviceId;
|
||||
}
|
||||
|
||||
// insert the new peer device Id in Storage, keep the Id used in table to give it to DR_Session which will need it to save itself into DB.
|
||||
long int peerDid=0;
|
||||
try {
|
||||
peerDid = store_peerDevice(peerBundle.deviceId, peerBundle.Ik);
|
||||
} catch (BctbxException &e) {
|
||||
bctbx_DestroyEDDSAContext(EDDSAContext);
|
||||
throw;
|
||||
}
|
||||
|
||||
// Generate Ephemeral ECDH key pair: Ek
|
||||
auto Ek = ECDHInit<Curve>();
|
||||
bctbx_ECDHCreateKeyPair(Ek, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
|
||||
|
||||
// Convert self Ik and peer Ik ED keys to X keys : Ek context to hold (Ek / peerIk) - will be then reused with other peer public keys, selfIk context to hold (self Ik / <no peer public key for now>)
|
||||
auto selfIk = ECDHInit<Curve>();
|
||||
// Start by peer as it is already stored in EDDSAContext, convert it directly to Ek context as peer public
|
||||
bctbx_EDDSA_ECDH_publicKeyConversion(EDDSAContext, Ek, BCTBX_ECDH_ISPEER);
|
||||
// Set self Ik public and private to EDDSAContext key and convert them
|
||||
get_SelfIdentityKey(); // make sure it is in context
|
||||
bctbx_EDDSA_setPublicKey(EDDSAContext, m_Ik.publicKey().data(), m_Ik.publicKey().size());
|
||||
bctbx_EDDSA_setSecretKey(EDDSAContext, m_Ik.privateKey().data(), m_Ik.privateKey().size());
|
||||
bctbx_EDDSA_ECDH_publicKeyConversion(EDDSAContext, selfIk, BCTBX_ECDH_ISSELF);
|
||||
bctbx_EDDSA_ECDH_privateKeyConversion(EDDSAContext, selfIk);
|
||||
bctbx_DestroyEDDSAContext(EDDSAContext); // don't need the EDDSA anymore, all ECDH from now
|
||||
|
||||
// Initiate HKDF input : We will compute HKDF with a concat of F and all DH computed, see X3DH spec section 2.2 for what is F
|
||||
std::vector<uint8_t> HKDF_input(X<Curve>::keyLength(), 0xFF);
|
||||
|
||||
// Compute DH1 = DH(self Ik, peer SPk) - selfIk context already holds selfIk.
|
||||
bctbx_ECDHSetPeerPublicKey(selfIk, peerBundle.SPk.data(), peerBundle.SPk.size());
|
||||
bctbx_ECDHComputeSecret(selfIk, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
|
||||
HKDF_input.insert(HKDF_input.end(), selfIk->sharedSecret, selfIk->sharedSecret+selfIk->pointCoordinateLength); // HKDF_input holds F || DH1
|
||||
bctbx_DestroyECDHContext(selfIk);
|
||||
|
||||
// Compute DH2 = DH(Ek, peer Ik) - Ek context already contains all needed material
|
||||
bctbx_ECDHComputeSecret(Ek, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
|
||||
HKDF_input.insert(HKDF_input.end(), Ek->sharedSecret, Ek->sharedSecret+Ek->pointCoordinateLength); // HKDF_input holds F || DH1 || DH2
|
||||
|
||||
// Compute DH3 = DH(Ek, peer SPk) - Set peer SPk as peer Public, Ek already in place
|
||||
bctbx_ECDHSetPeerPublicKey(Ek, peerBundle.SPk.data(), peerBundle.SPk.size());
|
||||
bctbx_ECDHComputeSecret(Ek, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
|
||||
HKDF_input.insert(HKDF_input.end(), Ek->sharedSecret, Ek->sharedSecret+Ek->pointCoordinateLength); // HKDF_input holds F || DH1 || DH2 || DH3
|
||||
|
||||
// Compute DH4 = DH(Ek, peer OPk) (if any OPk in bundle)
|
||||
if (peerBundle.haveOPk) {
|
||||
bctbx_ECDHSetPeerPublicKey(Ek, peerBundle.OPk.data(), peerBundle.OPk.size());
|
||||
bctbx_ECDHComputeSecret(Ek, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
|
||||
HKDF_input.insert(HKDF_input.end(), Ek->sharedSecret, Ek->sharedSecret+Ek->pointCoordinateLength); // HKDF_input holds F || DH1 || DH2 || DH3 || DH4
|
||||
}
|
||||
|
||||
// Compute SK = HKDF(F || DH1 || DH2 || DH3 || DH4)
|
||||
DRChainKey SK;
|
||||
X3DH_HKDF<DRChainKey>(HKDF_input, lime::settings::X3DH_SK_info, SK);
|
||||
bctbx_clean(HKDF_input.data(), HKDF_input.size());
|
||||
|
||||
// Generate X3DH init message: as in X3DH spec section 3.3:
|
||||
std::vector<uint8_t> X3DH_initMessage{};
|
||||
double_ratchet_protocol::buildMessage_X3DHinit(X3DH_initMessage, m_Ik.publicKey(), X<Curve>{Ek->selfPublic}, peerBundle.SPk_id, peerBundle.haveOPk?peerBundle.OPk_id:0, peerBundle.haveOPk);
|
||||
|
||||
// Delete Ek
|
||||
bctbx_DestroyECDHContext(Ek);
|
||||
|
||||
// Generate the shared AD used in DR session
|
||||
SharedADBuffer AD; // AD is HKDF(session Initiator Ik || session receiver Ik || session Initiator device Id || session receiver device Id)
|
||||
std::vector<uint8_t>AD_input{m_Ik.publicKey().begin(), m_Ik.publicKey().end()};
|
||||
AD_input.insert(AD_input.end(), peerBundle.Ik.begin(), peerBundle.Ik.end());
|
||||
AD_input.insert(AD_input.end(), m_selfDeviceId.begin(), m_selfDeviceId.end());
|
||||
AD_input.insert(AD_input.end(), peerBundle.deviceId.begin(), peerBundle.deviceId.end());
|
||||
X3DH_HKDF<SharedADBuffer>(AD_input, lime::settings::X3DH_AD_info, AD);
|
||||
|
||||
// Generate DR_Session and put it in cache(but not in localStorage yet, that would be done when first message generation will be complete)
|
||||
// it could happend that we eventually already have a session for this peer device if we received an initial message from it while fetching its key bundle(very unlikely but...)
|
||||
// in that case just keep on building our new session so the peer device knows it must get rid of the OPk, sessions will eventually converge into only one when messages
|
||||
// stop crossing themselves on the network.
|
||||
// If the fetch bundle doesn't hold OPk, just ignore our newly built session, and use existing one
|
||||
if (peerBundle.haveOPk) {
|
||||
m_DR_sessions_cache.erase(peerBundle.deviceId); // will just do nothing if this peerDeviceId is not in cache
|
||||
}
|
||||
m_DR_sessions_cache.emplace(peerBundle.deviceId, make_shared<DR<Curve>>(m_localStorage.get(), SK, AD, peerBundle.SPk, peerDid, X3DH_initMessage)); // will just do nothing if this peerDeviceId is already in cache
|
||||
|
||||
bctbx_message("X3DH created session with device %s", peerBundle.deviceId.data());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Curve>
|
||||
std::shared_ptr<DR<Curve>> Lime<Curve>::X3DH_init_receiver_session(const std::vector<uint8_t> X3DH_initMessage, const std::string &senderDeviceId) {
|
||||
ED<Curve> peerIk{};
|
||||
X<Curve> Ek{};
|
||||
bool OPk_flag = false;
|
||||
uint32_t SPk_id=0, OPk_id=0;
|
||||
|
||||
double_ratchet_protocol::parseMessage_X3DHinit(X3DH_initMessage, peerIk, Ek, SPk_id, OPk_id, OPk_flag);
|
||||
|
||||
KeyPair<X<Curve>> SPk{};
|
||||
X3DH_get_SPk(SPk_id, SPk); // this one will throw an exception if the SPk is not found in local storage, let it flow up
|
||||
|
||||
KeyPair<X<Curve>> OPk{};
|
||||
if (OPk_flag) { // there is an OPk id
|
||||
X3DH_get_OPk(OPk_id, OPk); // this one will throw an exception if the OPk is not found in local storage, let it flow up
|
||||
}
|
||||
|
||||
// Compute DH1 = DH(SPk, peer Ik)
|
||||
// DH2 = DH(self Ik, Ek)
|
||||
// DH3 = DH(SPk, Ek)
|
||||
// DH4 = DH(OPk, Ek) if peer used an OPk
|
||||
|
||||
// Initiate HKDF input : We will compute HKDF with a concat of F and all DH computed, see X3DH spec section 2.2 for what is F: keyLength bytes set to 0xFF
|
||||
std::vector<uint8_t> HKDF_input(X<Curve>::keyLength(), 0xFF);
|
||||
HKDF_input.reserve(X<Curve>::keyLength()*5); // reserve memory for DH4 anyway, each DH has the same size the key has
|
||||
|
||||
// DH1 first
|
||||
// Convert peer Ik ED keys to X keys:: TODO what if peer directly send his X key instead of ED one as he got it in X form anyway?
|
||||
auto EDDSAContext = EDDSAInit<Curve>();
|
||||
bctbx_EDDSA_setPublicKey(EDDSAContext, peerIk.data(), peerIk.size());
|
||||
auto ECDHContext = ECDHInit<Curve>();
|
||||
bctbx_EDDSA_ECDH_publicKeyConversion(EDDSAContext, ECDHContext, BCTBX_ECDH_ISPEER);
|
||||
// set SPk in self key pair
|
||||
bctbx_ECDHSetSelfPublicKey(ECDHContext, SPk.publicKey().data(), SPk.publicKey().size());
|
||||
bctbx_ECDHSetSecretKey(ECDHContext, SPk.privateKey().data(), SPk.privateKey().size());
|
||||
// compute DH1 = DH(SPk, peerIk) and append it to HKDF_input buffer
|
||||
bctbx_ECDHComputeSecret(ECDHContext, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
|
||||
HKDF_input.insert(HKDF_input.end(), ECDHContext->sharedSecret, ECDHContext->sharedSecret+ECDHContext->pointCoordinateLength); // HKDF_input holds F || DH1
|
||||
|
||||
// Then DH3 = DH(SPk, Ek) as we already have SPk in the ECDH context, we will go back for DH2 after this one
|
||||
bctbx_ECDHSetPeerPublicKey(ECDHContext, Ek.data(), Ek.size());
|
||||
bctbx_ECDHComputeSecret(ECDHContext, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
|
||||
auto DH2pos = HKDF_input.end(); // remember current end of buffer so we will insert DH2 there
|
||||
HKDF_input.insert(HKDF_input.end(), ECDHContext->sharedSecret, ECDHContext->sharedSecret+ECDHContext->pointCoordinateLength); // HKDF_input holds F || DH1 || DH3
|
||||
|
||||
// DH2 = DH(self Ik, Ek), Ek is already DH context
|
||||
// convert self ED Ik pair into X keys
|
||||
get_SelfIdentityKey(); // make sure self IK is in context
|
||||
bctbx_EDDSA_setPublicKey(EDDSAContext, m_Ik.publicKey().data(), m_Ik.publicKey().size());
|
||||
bctbx_EDDSA_setSecretKey(EDDSAContext, m_Ik.privateKey().data(), m_Ik.privateKey().size());
|
||||
bctbx_EDDSA_ECDH_publicKeyConversion(EDDSAContext, ECDHContext, BCTBX_ECDH_ISSELF);
|
||||
bctbx_EDDSA_ECDH_privateKeyConversion(EDDSAContext, ECDHContext);
|
||||
bctbx_DestroyEDDSAContext(EDDSAContext); // don't need the EDDSA context anymore
|
||||
|
||||
// compute shared secret and insert it at correct position
|
||||
bctbx_ECDHComputeSecret(ECDHContext, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
|
||||
HKDF_input.insert(DH2pos, ECDHContext->sharedSecret, ECDHContext->sharedSecret+ECDHContext->pointCoordinateLength); // HKDF_input holds F || DH1 || DH2 || DH3
|
||||
|
||||
if (OPk_flag) { // there is an OPk id
|
||||
// DH4 = DH(OPk, Ek) Ek is already in context
|
||||
bctbx_ECDHSetSelfPublicKey(ECDHContext, OPk.publicKey().data(), OPk.publicKey().size());
|
||||
bctbx_ECDHSetSecretKey(ECDHContext, OPk.privateKey().data(), OPk.privateKey().size());
|
||||
bctbx_clean(OPk.privateKey().data(), OPk.privateKey().size());
|
||||
bctbx_ECDHComputeSecret(ECDHContext, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, m_RNG);
|
||||
HKDF_input.insert(HKDF_input.end(), ECDHContext->sharedSecret, ECDHContext->sharedSecret+ECDHContext->pointCoordinateLength); // HKDF_input holds F || DH1 || DH2 || DH3 || DH4
|
||||
}
|
||||
|
||||
//ECDH Context not needed anymore
|
||||
bctbx_DestroyECDHContext(ECDHContext);
|
||||
|
||||
// Compute SK = HKDF(F || DH1 || DH2 || DH3 || DH4) (DH4 optionnal)
|
||||
DRChainKey SK;
|
||||
X3DH_HKDF<DRChainKey>(HKDF_input, lime::settings::X3DH_SK_info, SK);
|
||||
bctbx_clean(HKDF_input.data(), HKDF_input.size());
|
||||
|
||||
// Generate the shared AD used in DR session
|
||||
SharedADBuffer AD; // AD is HKDF(session Initiator Ik || session receiver Ik || session Initiator device Id || session receiver device Id), we are receiver on this one
|
||||
std::vector<uint8_t> AD_input{peerIk.begin(), peerIk.end()};
|
||||
AD_input.insert(AD_input.end(), m_Ik.publicKey().begin(), m_Ik.publicKey().end());
|
||||
AD_input.insert(AD_input.end(), senderDeviceId.begin(), senderDeviceId.end());
|
||||
AD_input.insert(AD_input.end(), m_selfDeviceId.begin(), m_selfDeviceId.end());
|
||||
X3DH_HKDF<SharedADBuffer>(AD_input, lime::settings::X3DH_AD_info, AD);
|
||||
|
||||
// insert the new peer device Id in Storage, keep the Id used in table to give it to DR_Session which will need it to save itself into DB.
|
||||
long int peerDid=0;
|
||||
peerDid = store_peerDevice(senderDeviceId, peerIk);
|
||||
|
||||
auto DRSession = make_shared<DR<Curve>>(m_localStorage.get(), SK, AD, SPk, peerDid);
|
||||
bctbx_clean(SPk.privateKey().data(), SPk.privateKey().size());
|
||||
|
||||
return DRSession;
|
||||
}
|
||||
|
||||
/* Instanciate templated member functions */
|
||||
#ifdef EC25519_ENABLED
|
||||
template void Lime<C255>::X3DH_init_sender_session(const std::vector<X3DH_peerBundle<C255>> &peerBundle);
|
||||
template std::shared_ptr<DR<C255>> Lime<C255>::X3DH_init_receiver_session(const std::vector<uint8_t> X3DH_initMessage, const std::string &peerDeviceId);
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
template void Lime<C448>::X3DH_init_sender_session(const std::vector<X3DH_peerBundle<C448>> &peerBundle);
|
||||
template std::shared_ptr<DR<C448>> Lime<C448>::X3DH_init_receiver_session(const std::vector<uint8_t> X3DH_initMessage, const std::string &peerDeviceId);
|
||||
#endif
|
||||
|
||||
}
|
571
src/lime_x3dh_protocol.cpp
Normal file
571
src/lime_x3dh_protocol.cpp
Normal file
|
@ -0,0 +1,571 @@
|
|||
/*
|
||||
lime_x3dh_protocol.cpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define BCTBX_LOG_DOMAIN "lime"
|
||||
#include <bctoolbox/logging.h>
|
||||
|
||||
#include "lime/lime.hpp"
|
||||
#include "lime_x3dh_protocol.hpp"
|
||||
#include "lime_utils.hpp"
|
||||
#include "lime_impl.hpp"
|
||||
|
||||
#include "belle-sip/belle-sip.h"
|
||||
#include "bctoolbox/exception.hh"
|
||||
|
||||
using namespace::std;
|
||||
using namespace::lime;
|
||||
|
||||
namespace lime {
|
||||
// Group in this namespace all the functions related to building or parsing x3dh packets
|
||||
namespace x3dh_protocol {
|
||||
/* Version 0x01:
|
||||
* Header is : Protccol Version Number<1 byte> || Message type<1 byte> || Curve id<1 byte>
|
||||
* Messages are : header<3 bytes> || Message content
|
||||
*
|
||||
* If not an error the server responds with a message holding just a header of the same message type
|
||||
* except for getPeerBundle which shall be answered with a peerBundle message
|
||||
*
|
||||
* Message types description :
|
||||
* - registerUser : Identity Key<EDDSA Public Key length>
|
||||
* - deleteUser : empty message, user to delete is retrieved from header From
|
||||
*
|
||||
* - postSPk : SPk<ECDH Public key length> ||
|
||||
* SPk Signature<Signature Length> ||
|
||||
* SPk Id < 4 bytes>
|
||||
*
|
||||
* - postOPks : Keys Count<2 bytes unsigned integer Big endian> ||
|
||||
* ( OPk<ECDH Public key length> || OPk Id <4 bytes>){Keys Count}
|
||||
*
|
||||
* - getPeerBundle : request Count < 2 bytes unsigned Big Endian> ||
|
||||
* (userId Size <2 bytes unsigned Big Endian> || UserId <...> (the GRUU of user we wan't to send a message)) {request Count}
|
||||
*
|
||||
* - peerBundle : bundle Count < 2 bytes unsigned Big Endian> ||
|
||||
* ( Flag<1 byte: 0 if no OPK in bundle, 1 if present> ||
|
||||
* Ik <EDDSA Public Key Length> ||
|
||||
* SPk <ECDH Public Key Length> || SPK id <4 bytes>
|
||||
* SPk_sig <Signature Length> ||
|
||||
* (OPk <ECDH Public Key Length> || OPk id <4 bytes>){0,1 in accordance to flag}
|
||||
* ) { bundle Count}
|
||||
*
|
||||
* - error : errorCode<1 byte> || (errorMessage<...>){0,1}
|
||||
*/
|
||||
|
||||
constexpr uint8_t X3DH_protocolVersion = 0x01;
|
||||
constexpr size_t X3DH_headerSize = 3;
|
||||
enum class x3dh_message_type : uint8_t{ unset_type=0x00,
|
||||
registerUser=0x01,
|
||||
deleteUser=0x02,
|
||||
postSPk=0x03,
|
||||
postOPks=0x04,
|
||||
getPeerBundle=0x05,
|
||||
peerBundle=0x06,
|
||||
error=0xff};
|
||||
|
||||
enum class x3dh_error_code : uint8_t{ bad_content_type=0x00,
|
||||
bad_curve=0x01,
|
||||
missing_senderId=0x02,
|
||||
bad_x3dh_protocol_version=0x03,
|
||||
bad_size=0x04,
|
||||
user_already_in=0x05,
|
||||
user_not_found=0x06,
|
||||
db_error=0x07,
|
||||
bad_request=0x08,
|
||||
unset_error_code=0xff};
|
||||
/* X3DH protocol packets builds */
|
||||
static std::vector<uint8_t> X3DH_makeHeader(const x3dh_message_type message_type, const lime::CurveId curve) noexcept{
|
||||
return std::vector<uint8_t> {X3DH_protocolVersion, static_cast<uint8_t>(message_type), static_cast<uint8_t>(curve)};
|
||||
}
|
||||
|
||||
// registerUser : Identity Key<EDDSA Public Key length>
|
||||
template <typename Curve>
|
||||
void buildMessage_registerUser(std::vector<uint8_t> &message, const ED<Curve> &Ik) noexcept {
|
||||
// create the header
|
||||
message = X3DH_makeHeader(x3dh_message_type::registerUser, Curve::curveId());
|
||||
// append the Ik
|
||||
message.insert(message.end(), Ik.begin(), Ik.end());
|
||||
}
|
||||
|
||||
// deleteUser : empty message, server retrieves deviceId to delete from authentication header, you cannot delete someone else!
|
||||
template <typename Curve>
|
||||
void buildMessage_deleteUser(std::vector<uint8_t> &message) noexcept {
|
||||
// create the header
|
||||
message = X3DH_makeHeader(x3dh_message_type::deleteUser, Curve::curveId());
|
||||
}
|
||||
|
||||
|
||||
// postSPk : SPk<ECDH Public key length> ||
|
||||
// SPk Signature<Signature Length> ||
|
||||
// SPk Id < 4 bytes>
|
||||
template <typename Curve>
|
||||
void buildMessage_publishSPk(std::vector<uint8_t> &message, const X<Curve> &SPk, const Signature<Curve> &Sig, const uint32_t SPk_id) noexcept {
|
||||
// create the header
|
||||
message = X3DH_makeHeader(x3dh_message_type::postSPk, Curve::curveId());
|
||||
// append SPk, Signature and SPkId
|
||||
message.insert(message.end(), SPk.begin(), SPk.end());
|
||||
message.insert(message.end(), Sig.begin(), Sig.end());
|
||||
message.push_back(static_cast<uint8_t>((SPk_id>>24)&0xFF));
|
||||
message.push_back(static_cast<uint8_t>((SPk_id>>16)&0xFF));
|
||||
message.push_back(static_cast<uint8_t>((SPk_id>>8)&0xFF));
|
||||
message.push_back(static_cast<uint8_t>((SPk_id)&0xFF));
|
||||
}
|
||||
|
||||
// postOPks : Keys Count<2 bytes unsigned integer Big endian> ||
|
||||
// ( OPk<ECDH Public key length> || OPk Id <4 bytes>){Keys Count}
|
||||
template <typename Curve>
|
||||
void buildMessage_publishOPks(std::vector<uint8_t> &message, const std::vector<X<Curve>> &OPks, const std::vector<uint32_t> &OPk_ids) noexcept {
|
||||
// create the header
|
||||
message = X3DH_makeHeader(x3dh_message_type::postOPks, Curve::curveId());
|
||||
|
||||
auto OPkCount = OPks.size();
|
||||
// append OPks number and a sequence of OPk || OPk_id
|
||||
message.push_back(static_cast<uint8_t>(((OPkCount)>>8)&0xFF));
|
||||
message.push_back(static_cast<uint8_t>((OPkCount)&0xFF));
|
||||
|
||||
for (decltype(OPkCount) i=0; i<OPkCount; i++) {
|
||||
message.insert(message.end(), OPks[i].begin(), OPks[i].end());
|
||||
message.push_back(static_cast<uint8_t>((OPk_ids[i]>>24)&0xFF));
|
||||
message.push_back(static_cast<uint8_t>((OPk_ids[i]>>16)&0xFF));
|
||||
message.push_back(static_cast<uint8_t>((OPk_ids[i]>>8)&0xFF));
|
||||
message.push_back(static_cast<uint8_t>((OPk_ids[i])&0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
// getPeerBundle : request Count < 2 bytes unsigned Big Endian> ||
|
||||
// (userId Size <2 bytes unsigned Big Endian> || UserId <...> (the GRUU of user we wan't to send a message)) {request Count}
|
||||
template <typename Curve>
|
||||
void buildMessage_getPeerBundles(std::vector<uint8_t> &message, std::vector<std::string> &peer_device_ids) noexcept {
|
||||
// create the header
|
||||
message = X3DH_makeHeader(x3dh_message_type::getPeerBundle, Curve::curveId());
|
||||
|
||||
// append peer number
|
||||
message.push_back(static_cast<uint8_t>(((peer_device_ids.size())>>8)&0xFF));
|
||||
message.push_back(static_cast<uint8_t>((peer_device_ids.size())&0xFF));
|
||||
|
||||
if (peer_device_ids.size()>0xFFFF) { // we're asking for more than 2^16 key bundles, really?
|
||||
bctbx_warning("We are about to request for more than 2^16 key bundles to the X3DH server, it won't fit in protocol, truncate the request to 2^16 but it's very very unusual");
|
||||
peer_device_ids.resize(0xFFFF); // resize to max possible value
|
||||
}
|
||||
|
||||
// append a sequence of peer device Id size(on 2 bytes) || device id
|
||||
for (auto &peer_device_id : peer_device_ids) {
|
||||
message.push_back(static_cast<uint8_t>(((peer_device_id.size())>>8)&0xFF));
|
||||
message.push_back(static_cast<uint8_t>((peer_device_id.size())&0xFF));
|
||||
message.insert(message.end(),peer_device_id.begin(), peer_device_id.end());
|
||||
bctbx_message("Request X3DH keys for device %s",peer_device_id.data());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Perform validity verifications on x3dh message and extract its type and error code if its the case
|
||||
*
|
||||
* @param[in] body a buffer holding the message
|
||||
* @param[in] bodySize size of previous buffer
|
||||
* @param[out] message_type the message type
|
||||
* @param[out] error_code the error code, unchanged if the message type is not error
|
||||
* @param[in] callback in case of error, directly call it giving a meaningfull error message
|
||||
*/
|
||||
template <typename Curve>
|
||||
bool parseMessage_getType(const uint8_t *body, const size_t bodySize, x3dh_message_type &message_type, x3dh_error_code &error_code, const limeCallback callback) noexcept {
|
||||
// check message holds at leat a header before trying to read it
|
||||
if (body == nullptr || bodySize<X3DH_headerSize) {
|
||||
bctbx_error("Got an invalid response from X3DH server");
|
||||
if (callback) callback(lime::callbackReturn::fail, "Got an invalid response from X3DH server");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check X3DH protocol version
|
||||
if (body[0] != static_cast<uint8_t>(X3DH_protocolVersion)) {
|
||||
bctbx_error("X3DH server runs an other version of X3DH protocol(server %d - local %d)", body[0], static_cast<uint8_t>(X3DH_protocolVersion));
|
||||
if (callback) callback(lime::callbackReturn::fail, "X3DH server and client protocol version mismatch");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check curve id
|
||||
if (body[2] != static_cast<uint8_t>(Curve::curveId())) {
|
||||
bctbx_error("X3DH server runs curve Id %d while local is set to %d for this server)", body[2], static_cast<uint8_t>(Curve::curveId()));
|
||||
if (callback) callback(lime::callbackReturn::fail, "X3DH server and client curve Id mismatch");
|
||||
return false;
|
||||
}
|
||||
|
||||
// retrieve message_type from body[1]
|
||||
switch (static_cast<uint8_t>(body[1])) {
|
||||
case static_cast<uint8_t>(x3dh_message_type::registerUser) :
|
||||
message_type = x3dh_message_type::registerUser;
|
||||
break;
|
||||
case static_cast<uint8_t>(x3dh_message_type::deleteUser) :
|
||||
message_type = x3dh_message_type::deleteUser;
|
||||
break;
|
||||
case static_cast<uint8_t>(x3dh_message_type::postSPk) :
|
||||
message_type = x3dh_message_type::postSPk;
|
||||
break;
|
||||
case static_cast<uint8_t>(x3dh_message_type::postOPks) :
|
||||
message_type = x3dh_message_type::postOPks;
|
||||
break;
|
||||
case static_cast<uint8_t>(x3dh_message_type::getPeerBundle) :
|
||||
message_type = x3dh_message_type::getPeerBundle;
|
||||
break;
|
||||
case static_cast<uint8_t>(x3dh_message_type::peerBundle) :
|
||||
message_type = x3dh_message_type::peerBundle;
|
||||
break;
|
||||
case static_cast<uint8_t>(x3dh_message_type::error) :
|
||||
message_type = x3dh_message_type::error;
|
||||
break;
|
||||
default: // unknown message type: invalid packet
|
||||
return false;
|
||||
}
|
||||
|
||||
// retrieve the error code if needed
|
||||
if (message_type == x3dh_message_type::error) {
|
||||
if (bodySize<X3DH_headerSize+1) { // error message contains at least 1 byte of error code + possible message
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bodySize==X3DH_headerSize+1) {
|
||||
bctbx_error("X3DH server respond error : code %x (no error message)", body[X3DH_headerSize]);
|
||||
} else {
|
||||
bctbx_error("X3DH server respond error : code %x : %s", body[X3DH_headerSize], body+X3DH_headerSize+1);
|
||||
}
|
||||
|
||||
switch (static_cast<uint8_t>(body[X3DH_headerSize])) {
|
||||
case static_cast<uint8_t>(x3dh_error_code::bad_content_type):
|
||||
error_code = x3dh_error_code::bad_content_type;
|
||||
break;
|
||||
case static_cast<uint8_t>(x3dh_error_code::bad_curve):
|
||||
error_code = x3dh_error_code::bad_curve;
|
||||
break;
|
||||
case static_cast<uint8_t>(x3dh_error_code::missing_senderId):
|
||||
error_code = x3dh_error_code::missing_senderId;
|
||||
break;
|
||||
case static_cast<uint8_t>(x3dh_error_code::bad_x3dh_protocol_version):
|
||||
error_code = x3dh_error_code::bad_x3dh_protocol_version;
|
||||
break;
|
||||
case static_cast<uint8_t>(x3dh_error_code::bad_size):
|
||||
error_code = x3dh_error_code::bad_size;
|
||||
break;
|
||||
case static_cast<uint8_t>(x3dh_error_code::user_already_in):
|
||||
error_code = x3dh_error_code::user_already_in;
|
||||
break;
|
||||
case static_cast<uint8_t>(x3dh_error_code::user_not_found):
|
||||
error_code = x3dh_error_code::user_not_found;
|
||||
break;
|
||||
case static_cast<uint8_t>(x3dh_error_code::db_error):
|
||||
error_code = x3dh_error_code::db_error;
|
||||
break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* peerBundle : bundle Count < 2 bytes unsigned Big Endian> ||
|
||||
* ( deviceId Size < 2 bytes unsigned Big Endian > || deviceId
|
||||
* Flag<1 byte: 0 if no OPK in bundle, 1 if present> ||
|
||||
* Ik <EDDSA Public Key Length> ||
|
||||
* SPk <ECDH Public Key Length> || SPK id <4 bytes>
|
||||
* SPk_sig <Signature Length> ||
|
||||
* (OPk <ECDH Public Key Length> || OPk id <4 bytes>){0,1 in accordance to flag}
|
||||
* ) { bundle Count}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Parse a peerBundles message and populate a vector of peerBundles
|
||||
* Warning: no checks are done on message type, they are performed before calling this function
|
||||
*
|
||||
* @param[in] body a buffer holding the message
|
||||
* @param[in] bodySize size of previous buffer
|
||||
* @param[out] peersBundle a vector to be populated from message content, is empty if none found
|
||||
*
|
||||
* @return true if all went ok, false and empty peersBundle otherwise
|
||||
*/
|
||||
template <typename Curve>
|
||||
bool parseMessage_getPeerBundles(const uint8_t *body, const size_t bodySize, std::vector<X3DH_peerBundle<Curve>> &peersBundle) noexcept {
|
||||
peersBundle.clear();
|
||||
if (bodySize < X3DH_headerSize+2) { // we must be able to at least have a count of bundles
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t peersBundleCount = (static_cast<uint16_t>(body[X3DH_headerSize]))<<8|body[X3DH_headerSize+1];
|
||||
size_t index = X3DH_headerSize+2;
|
||||
|
||||
// loop on all expected bundles
|
||||
for (auto i=0; i<peersBundleCount; i++) {
|
||||
if (bodySize < index + 2) { // check we have at least a device size to read
|
||||
peersBundle.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// get device id (ASCII string)
|
||||
uint16_t deviceIdSize = (static_cast<uint16_t>(body[index]))<<8|body[index+1];
|
||||
index += 2;
|
||||
|
||||
if (bodySize < index + deviceIdSize + 1) { // check we have at enough data to read: device size and the following OPk flag
|
||||
peersBundle.clear();
|
||||
return false;
|
||||
}
|
||||
std::string deviceId{body+index, body+index+deviceIdSize};
|
||||
index += deviceIdSize;
|
||||
|
||||
// check if we have an OPk
|
||||
bool haveOPk = (body[index]==0)?false:true;
|
||||
index += 1;
|
||||
|
||||
|
||||
if (bodySize < index + ED<Curve>::keyLength() + X<Curve>::keyLength() + Signature<Curve>::signatureLength() + 4 + (haveOPk?(X<Curve>::keyLength()+4):0) ) {
|
||||
peersBundle.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// retrieve simple pointers to all keys and signature, the X3DH_peerBundle constructor will construct the keys out of them
|
||||
const uint8_t *Ik = body+index; index += ED<Curve>::keyLength();
|
||||
const uint8_t *SPk = body+index; index += X<Curve>::keyLength();
|
||||
uint32_t SPk_id = static_cast<uint32_t>(body[index])<<24 |
|
||||
static_cast<uint32_t>(body[index+1])<<16 |
|
||||
static_cast<uint32_t>(body[index+2])<<8 |
|
||||
static_cast<uint32_t>(body[index+3]);
|
||||
index += 4;
|
||||
const uint8_t *SPk_sig = body+index; index += Signature<Curve>::signatureLength();
|
||||
if (haveOPk) {
|
||||
const uint8_t *OPk = body+index; index += X<Curve>::keyLength();
|
||||
uint32_t OPk_id = static_cast<uint32_t>(body[index])<<24 |
|
||||
static_cast<uint32_t>(body[index+1])<<16 |
|
||||
static_cast<uint32_t>(body[index+2])<<8 |
|
||||
static_cast<uint32_t>(body[index+3]);
|
||||
index += 4;
|
||||
peersBundle.emplace_back(std::move(deviceId), Ik, SPk, SPk_id, SPk_sig, OPk, OPk_id);
|
||||
} else {
|
||||
peersBundle.emplace_back(std::move(deviceId), Ik, SPk, SPk_id, SPk_sig);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Instanciate templated functions */
|
||||
#ifdef EC25519_ENABLED
|
||||
template void buildMessage_registerUser<C255>(std::vector<uint8_t> &message, const ED<C255> &Ik) noexcept;
|
||||
template void buildMessage_deleteUser<C255>(std::vector<uint8_t> &message) noexcept;
|
||||
template void buildMessage_publishSPk<C255>(std::vector<uint8_t> &message, const X<C255> &SPk, const Signature<C255> &Sig, const uint32_t SPk_id) noexcept;
|
||||
template void buildMessage_publishOPks<C255>(std::vector<uint8_t> &message, const std::vector<X<C255>> &OPks, const std::vector<uint32_t> &OPk_ids) noexcept;
|
||||
template void buildMessage_getPeerBundles<C255>(std::vector<uint8_t> &message, std::vector<std::string> &peer_device_ids) noexcept;
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
template void buildMessage_registerUser<C448>(std::vector<uint8_t> &message, const ED<C448> &Ik) noexcept;
|
||||
template void buildMessage_deleteUser<C448>(std::vector<uint8_t> &message) noexcept;
|
||||
template void buildMessage_publishSPk<C448>(std::vector<uint8_t> &message, const X<C448> &SPk, const Signature<C448> &Sig, const uint32_t SPk_id) noexcept;
|
||||
template void buildMessage_publishOPks<C448>(std::vector<uint8_t> &message, const std::vector<X<C448>> &OPks, const std::vector<uint32_t> &OPk_ids) noexcept;
|
||||
template void buildMessage_getPeerBundles<C448>(std::vector<uint8_t> &message, std::vector<std::string> &peer_device_ids) noexcept;
|
||||
#endif
|
||||
} //namespace x3dh_protocol
|
||||
|
||||
/* Network related functions */
|
||||
static void on_progress(belle_sip_body_handler_t *bh, belle_sip_message_t *m, void *data, size_t offset, size_t total) {
|
||||
}
|
||||
|
||||
static void process_response_header(void *data, const belle_http_response_event_t *event){
|
||||
if (event->response){
|
||||
//auto code=belle_http_response_get_status_code(event->response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clean user data in case of problem or when we're done, it also process the asynchronous encryption queue
|
||||
*
|
||||
* @param[in/out] userData the structure holding the data passed through the bellesip callback
|
||||
*/
|
||||
template <typename Curve>
|
||||
void Lime<Curve>::cleanUserData(callbackUserData<Curve> *userData) {
|
||||
if (userData->plainMessage!=nullptr) { // only encryption request for X3DH bundle would populate the plainMessage field of user data structure
|
||||
// userData is actually a part of the Lime Object and allocated as a shared pointer, just set it to nullptr it will cleanly destroy it
|
||||
m_ongoing_encryption = nullptr;
|
||||
// check if others encryptions are in queue and call them if needed
|
||||
if (!m_encryption_queue.empty()) {
|
||||
auto userData = m_encryption_queue.front();
|
||||
m_encryption_queue.pop(); // remove it from queue and do it, as there is no more ongoing it shall be processed even if the queue still holds elements
|
||||
encrypt(userData->recipientUserId, userData->recipients, userData->plainMessage, userData->cipherMessage, userData->callback);
|
||||
}
|
||||
} else { // its not an encryption, user Data was generated through new, just delete it
|
||||
delete(userData);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Curve>
|
||||
void Lime<Curve>::process_response(void *data, const belle_http_response_event_t *event) noexcept {
|
||||
if (event->response){
|
||||
auto code=belle_http_response_get_status_code(event->response);
|
||||
if (code == 200) { // HTTP server is happy with our packet
|
||||
// check response from X3DH server: header shall be X3DH protocol version || message type || curveId
|
||||
belle_sip_message_t *message = BELLE_SIP_MESSAGE(event->response);
|
||||
// all raw data access functions in lime use uint8_t *, so safely cast the body pointer to it, it's just a data stream pointer anyway
|
||||
auto body = reinterpret_cast<const uint8_t *>(belle_sip_message_get_body(message));
|
||||
auto bodySize = belle_sip_message_get_body_size(message);
|
||||
|
||||
callbackUserData<Curve> *userData = static_cast<callbackUserData<Curve> *>(data);
|
||||
auto thiz = userData->limeObj.lock(); // get a shared pointer to Lime Object from the weak pointer stored in userData
|
||||
// check it is valid (lock() returns nullptr)
|
||||
if (!thiz) { // our Lime caller object doesn't exists anymore
|
||||
bctbx_error("Got response from X3DH server but our Lime Object has been destroyed");
|
||||
delete(userData);
|
||||
return;
|
||||
}
|
||||
auto callback = userData->callback; // get callback
|
||||
|
||||
lime::x3dh_protocol::x3dh_message_type message_type{x3dh_protocol::x3dh_message_type::unset_type};
|
||||
lime::x3dh_protocol::x3dh_error_code error_code{x3dh_protocol::x3dh_error_code::unset_error_code};
|
||||
if (!x3dh_protocol::parseMessage_getType<Curve>(body, bodySize, message_type, error_code, callback)) {
|
||||
thiz->cleanUserData(userData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Is it an error message?
|
||||
if (message_type == lime::x3dh_protocol::x3dh_message_type::error) {
|
||||
// check error code: if we have a user_already_in it means we tried to insert a user on server but it failed, we must delete it from local Storagemak
|
||||
if (error_code == lime::x3dh_protocol::x3dh_error_code::user_already_in) {
|
||||
thiz->m_localStorage->delete_LimeUser(thiz->m_selfDeviceId); // do not use the lime delete function as it forwards the delete to X3DH server, local delete only
|
||||
}
|
||||
|
||||
if (callback) callback(lime::callbackReturn::fail, "X3DH server error");
|
||||
thiz->cleanUserData(userData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Is it a peerBundle message?
|
||||
if (message_type == lime::x3dh_protocol::x3dh_message_type::peerBundle) {
|
||||
std::vector<X3DH_peerBundle<Curve>> peersBundle;
|
||||
if (!x3dh_protocol::parseMessage_getPeerBundles(body, bodySize, peersBundle)) { // parsing went wrong
|
||||
bctbx_error("Got an invalid peerBundle packet from X3DH server");
|
||||
if (callback) callback(lime::callbackReturn::fail, "Got an invalid peerBundle packet from X3DH server");
|
||||
thiz->cleanUserData(userData);
|
||||
return;
|
||||
}
|
||||
|
||||
// generate X3DH init packets, create a store DR Sessions(in Lime obj cache, they'll be stored in DB when the first encryption will occurs)
|
||||
try {
|
||||
//Note: if while we were waiting for the peer bundle we did get an init message from him and created a session
|
||||
// just do nothing : create a second session with the peer bundle we retrieved and at some point one session will stale
|
||||
// when message stop crossing themselves on the network
|
||||
thiz->X3DH_init_sender_session(peersBundle);
|
||||
} catch (BctbxException &e) { // something went wrong but we can't forward the exception to belle-sip, go for callback
|
||||
if (callback) callback(lime::callbackReturn::fail, std::string{"Error during the peer Bundle processing : "}.append(e.what()));
|
||||
thiz->cleanUserData(userData);
|
||||
return;
|
||||
}
|
||||
|
||||
// call the encrypt function again, it will call the callback when done, encryption queue won't be processed as still locked by the m_ongoing_encryption member
|
||||
thiz->encrypt(userData->recipientUserId, userData->recipients, userData->plainMessage, userData->cipherMessage, callback);
|
||||
|
||||
// now we can safely delete the user data, note that this may trigger an other encryption if there is one in queue
|
||||
thiz->cleanUserData(userData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rudimental state machine active at user registration only:
|
||||
// - after registering a new user on X3dh server, if all goes well(server responde message type is registerIdentity), we shall upload SPK
|
||||
// - after uploading SPk on X3dh server, if all goes well(server responde message type is registerIdentity), we shall upload SPK
|
||||
if (userData->network_state_machine == lime::network_state::sendSPk && message_type == lime::x3dh_protocol::x3dh_message_type::registerUser) {
|
||||
userData->network_state_machine = lime::network_state::sendOPk;
|
||||
// generate and publish the SPk
|
||||
X<Curve> SPk{};
|
||||
Signature<Curve> SPk_sig{};
|
||||
uint32_t SPk_id=0;
|
||||
thiz->X3DH_generate_SPk(SPk, SPk_sig, SPk_id);
|
||||
std::vector<uint8_t> X3DHmessage{};
|
||||
x3dh_protocol::buildMessage_publishSPk(X3DHmessage, SPk, SPk_sig, SPk_id);
|
||||
thiz->postToX3DHServer(userData, X3DHmessage);
|
||||
} else if (userData->network_state_machine == lime::network_state::sendOPk && message_type == lime::x3dh_protocol::x3dh_message_type::postSPk) {
|
||||
userData->network_state_machine = lime::network_state::done;
|
||||
// generate and publish the OPks
|
||||
std::vector<X<Curve>> OPks{};
|
||||
std::vector<uint32_t> OPk_ids{};
|
||||
thiz->X3DH_generate_OPks(OPks, OPk_ids, lime::settings::OPk_batch_number);
|
||||
std::vector<uint8_t> X3DHmessage{};
|
||||
x3dh_protocol::buildMessage_publishOPks(X3DHmessage, OPks, OPk_ids);
|
||||
thiz->postToX3DHServer(userData, X3DHmessage);
|
||||
} else { // we're done
|
||||
if (callback) callback(lime::callbackReturn::success, "");
|
||||
delete(userData);
|
||||
return;
|
||||
}
|
||||
} else { // response code is not 200Ok
|
||||
//TODO : something here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void process_io_error(void *data, const belle_sip_io_error_event_t *event){
|
||||
//TODO : something here
|
||||
}
|
||||
|
||||
static void process_auth_requested(void *data, belle_sip_auth_event_t *event){
|
||||
if (belle_sip_auth_event_get_mode(event)==BELLE_SIP_AUTH_MODE_TLS){
|
||||
//TODO : something here
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename Curve>
|
||||
void Lime<Curve>::postToX3DHServer(callbackUserData<Curve> *userData, const std::vector<uint8_t> &message) {
|
||||
belle_http_request_listener_callbacks_t cbs={};
|
||||
belle_http_request_listener_t *l;
|
||||
belle_generic_uri_t *uri;
|
||||
belle_http_request_t *req;
|
||||
belle_sip_memory_body_handler_t *bh;
|
||||
|
||||
bh = belle_sip_memory_body_handler_new_copy_from_buffer(message.data(), message.size(), on_progress, NULL);
|
||||
|
||||
uri=belle_generic_uri_parse(m_X3DH_Server_URL.data());
|
||||
|
||||
req=belle_http_request_create("POST",
|
||||
uri,
|
||||
belle_http_header_create("User-Agent", "lime"),
|
||||
belle_http_header_create("Content-type", "x3dh/octet-stream"),
|
||||
belle_http_header_create("From", m_selfDeviceId.data()),
|
||||
NULL);
|
||||
|
||||
belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(req),BELLE_SIP_BODY_HANDLER(bh));
|
||||
cbs.process_response=Lime<Curve>::process_response;
|
||||
cbs.process_response_headers=process_response_header;
|
||||
cbs.process_io_error=process_io_error;
|
||||
cbs.process_auth_requested=process_auth_requested;
|
||||
l=belle_http_request_listener_create_from_callbacks(&cbs,static_cast<void *>(userData));
|
||||
belle_sip_object_data_set(BELLE_SIP_OBJECT(req), "http_request_listener", l, belle_sip_object_unref); // Ensure the listener object is destroyed when the request is destroyed
|
||||
belle_http_provider_send_request(m_http_provider,req,l);
|
||||
}
|
||||
|
||||
|
||||
/* X3DH Messages building functions */
|
||||
|
||||
/* Instanciate templated member functions */
|
||||
#ifdef EC25519_ENABLED
|
||||
template void Lime<C255>::postToX3DHServer(callbackUserData<C255> *userData, const std::vector<uint8_t> &message);
|
||||
template void Lime<C255>::process_response(void *data, const belle_http_response_event_t *event) noexcept;
|
||||
template void Lime<C255>::cleanUserData(callbackUserData<C255> *userData);
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
template void Lime<C448>::postToX3DHServer(callbackUserData<C448> *userData, const std::vector<uint8_t> &message);
|
||||
template void Lime<C448>::process_response(void *data, const belle_http_response_event_t *event) noexcept;
|
||||
template void Lime<C448>::cleanUserData(callbackUserData<C448> *userData);
|
||||
#endif
|
||||
} //namespace lime
|
80
src/lime_x3dh_protocol.hpp
Normal file
80
src/lime_x3dh_protocol.hpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
lime_x3dh_protocol.hpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef lime_x3dh_protocol_hpp
|
||||
#define lime_x3dh_protocol_hpp
|
||||
|
||||
#include "lime_keys.hpp"
|
||||
|
||||
namespace lime {
|
||||
|
||||
template <typename Curve>
|
||||
struct X3DH_peerBundle {
|
||||
std::string deviceId;
|
||||
ED<Curve> Ik;
|
||||
X<Curve> SPk;
|
||||
uint32_t SPk_id;
|
||||
Signature<Curve> SPk_sig;
|
||||
bool haveOPk;
|
||||
X<Curve> OPk;
|
||||
uint32_t OPk_id;
|
||||
// use uint8_t * constructor for all keys/signatures
|
||||
X3DH_peerBundle(std::string &&deviceId, const uint8_t *Ik, const uint8_t *SPk, uint32_t SPk_id, const uint8_t *SPk_sig) :
|
||||
deviceId{deviceId}, Ik{Ik}, SPk{SPk}, SPk_id{SPk_id}, SPk_sig{SPk_sig}, haveOPk{false}, OPk{0}, OPk_id{0} {};
|
||||
X3DH_peerBundle(std::string &&deviceId, const uint8_t *Ik, const uint8_t *SPk, uint32_t SPk_id, const uint8_t *SPk_sig, const uint8_t *OPk, uint32_t OPk_id) :
|
||||
deviceId{deviceId}, Ik{Ik}, SPk{SPk}, SPk_id{SPk_id}, SPk_sig{SPk_sig}, haveOPk{true}, OPk{OPk}, OPk_id{OPk_id} {};
|
||||
|
||||
};
|
||||
|
||||
namespace x3dh_protocol {
|
||||
template <typename Curve>
|
||||
void buildMessage_registerUser(std::vector<uint8_t> &message, const ED<Curve> &Ik) noexcept;
|
||||
|
||||
template <typename Curve>
|
||||
void buildMessage_deleteUser(std::vector<uint8_t> &message) noexcept;
|
||||
|
||||
template <typename Curve>
|
||||
void buildMessage_publishSPk(std::vector<uint8_t> &message, const X<Curve> &SPk, const Signature<Curve> &Sig, const uint32_t SPk_id) noexcept;
|
||||
|
||||
template <typename Curve>
|
||||
void buildMessage_publishOPks(std::vector<uint8_t> &message, const std::vector<X<Curve>> &OPks, const std::vector<uint32_t> &OPk_ids) noexcept;
|
||||
|
||||
template <typename Curve>
|
||||
void buildMessage_getPeerBundles(std::vector<uint8_t> &message, std::vector<std::string> &peer_device_ids) noexcept;
|
||||
|
||||
/* this templates are intanciated in lime_x3dh_procotocol.cpp, do not re-instanciate it anywhere else */
|
||||
#ifdef EC25519_ENABLED
|
||||
extern template void buildMessage_registerUser<C255>(std::vector<uint8_t> &message, const ED<C255> &Ik) noexcept;
|
||||
extern template void buildMessage_deleteUser<C255>(std::vector<uint8_t> &message) noexcept;
|
||||
extern template void buildMessage_publishSPk<C255>(std::vector<uint8_t> &message, const X<C255> &SPk, const Signature<C255> &Sig, const uint32_t SPk_id) noexcept;
|
||||
extern template void buildMessage_publishOPks<C255>(std::vector<uint8_t> &message, const std::vector<X<C255>> &OPks, const std::vector<uint32_t> &OPk_ids) noexcept;
|
||||
extern template void buildMessage_getPeerBundles<C255>(std::vector<uint8_t> &message, std::vector<std::string> &peer_device_ids) noexcept;
|
||||
#endif
|
||||
|
||||
#ifdef EC448_ENABLED
|
||||
extern template void buildMessage_registerUser<C448>(std::vector<uint8_t> &message, const ED<C448> &Ik) noexcept;
|
||||
extern template void buildMessage_deleteUser<C448>(std::vector<uint8_t> &message) noexcept;
|
||||
extern template void buildMessage_publishSPk<C448>(std::vector<uint8_t> &message, const X<C448> &SPk, const Signature<C448> &Sig, const uint32_t SPk_id) noexcept;
|
||||
extern template void buildMessage_publishOPks<C448>(std::vector<uint8_t> &message, const std::vector<X<C448>> &OPks, const std::vector<uint32_t> &OPk_ids) noexcept;
|
||||
extern template void buildMessage_getPeerBundles<C448>(std::vector<uint8_t> &message, std::vector<std::string> &peer_device_ids) noexcept;
|
||||
#endif
|
||||
|
||||
} // namespace x3dh_protocol
|
||||
} // namespace lime
|
||||
|
||||
#endif /* lime_x3dh_protocol_hpp */
|
57
tester/CMakeLists.txt
Normal file
57
tester/CMakeLists.txt
Normal file
|
@ -0,0 +1,57 @@
|
|||
############################################################################
|
||||
# CMakeLists.txt
|
||||
# Copyright (C) 2017 Belledonne Communications, Grenoble France
|
||||
#
|
||||
############################################################################
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
############################################################################
|
||||
|
||||
if(ENABLE_SHARED)
|
||||
set(LIME_LIBRARIES_FOR_TESTER lime)
|
||||
else()
|
||||
set(LIME_LIBRARIES_FOR_TESTER lime-static)
|
||||
endif()
|
||||
set(HEADER_FILES_CXX lime-tester.hpp lime-tester-utils.hpp)
|
||||
set(SOURCE_FILES_CXX
|
||||
lime-tester.cpp
|
||||
lime-tester-utils.cpp
|
||||
lime_double_ratchet-tester.cpp
|
||||
lime_lime-tester.cpp
|
||||
)
|
||||
|
||||
bc_apply_compile_flags(SOURCE_FILES_CXX STRICT_OPTIONS_CPP STRICT_OPTIONS_CXX)
|
||||
|
||||
add_executable(lime_tester ${SOURCE_FILES_CXX} ${HEADER_FILES_CXX})
|
||||
set_target_properties(lime_tester PROPERTIES LINKER_LANGUAGE CXX)
|
||||
target_include_directories(lime_tester PUBLIC ${BCTOOLBOX_TESTER_INCLUDE_DIRS})
|
||||
target_link_libraries(lime_tester ${LIME_LIBRARIES_FOR_TESTER} ${BCTOOLBOX_LIBRARIES} ${BCTOOLBOX_TESTER_LIBRARIES} ${BELLESIP_LIBRARIES} ${SOCI_LIBRARIES} ${SOCI_sqlite3_PLUGIN} ${SQLITE3_LIBRARIES})
|
||||
|
||||
if(APPLE)
|
||||
set_target_properties(lime_tester PROPERTIES LINK_FLAGS "-stdlib=libc++")
|
||||
endif()
|
||||
|
||||
# line test suit request a local server to be launched, so keep testing double ratchet only for now
|
||||
add_test(NAME double_ratchet COMMAND lime_tester --verbose --resource-dir ${CMAKE_CURRENT_SOURCE_DIR} --suite "double ratchet")
|
||||
|
||||
if(NOT IOS)
|
||||
install(TARGETS lime_tester
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
|
||||
)
|
||||
endif()
|
32
tester/data/x3dh-cert.pem
Normal file
32
tester/data/x3dh-cert.pem
Normal file
|
@ -0,0 +1,32 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIFiDCCA3CgAwIBAgIJALsKyRSa4G9EMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNzEwMDUwMjE4
|
||||
NDNaFw0yNzA3MDUwMjE4NDNaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l
|
||||
LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV
|
||||
BAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKgn
|
||||
DutdWl2naVa4XuQr6zTKcPMMaz8et7siyAe2bWkJZhL3ULkepU89RFbNAa6TeOOO
|
||||
XqV50UL/xvRN9j0zahR7Cy8ZJvwRlDVCTcKY6mayKqIXSQ6vbc6TY2jnPEI53Z8e
|
||||
WdcMcCnbCGuIhwtPSA7LXbTnk346jZo0WafTw7SZ9MYK9irEFAFAMjnckHCa3qry
|
||||
i6/yh1AmRXd1ZEBwWyqOPMNuKiprMlaixx2HFikD5K0T6V6r1j64r5uIpGyCLUsk
|
||||
evnYUcnimuiVcqT/sTYRSPBcdRiDalzpa1VS9oYiBZbSxdp3dytr7pHETzSjRoS4
|
||||
ASSHR3B2ptjMG0/IHuw/S71w/cI0GA1zVsGjqjkcWR1d5XJWFipDEX1nNmFjreHW
|
||||
ylMBnbDg9VYqxIjqPXFuFrstuZl5jFKJlYYOsyH0XBgJrItdqaEKpgcBh83dEwsw
|
||||
ohwcy/v5Vn/VkG/LvEx0T+nqoKs5HanyJplo4ioE4q4CaL1/C7OYU9Ow1UlFHlzg
|
||||
YB0l33vZfSdSWR70AJvDM8o0rBQUnQ1BWAXjNgKWMngJjJBYGormXOpZDm7ta2yb
|
||||
AH9ji7VUqBdNUlNGCvVmyWsCnUeZmvxVjzJuYekl0SeOMML+JbabfAIgR/27xTD/
|
||||
TsybPjog8E8E3oAySTv1J7tHQMLXOpBj0QtFFfCBAgMBAAGjUzBRMB0GA1UdDgQW
|
||||
BBTTQjx6ywWHvF4lq9GOpRR1PZIHyDAfBgNVHSMEGDAWgBTTQjx6ywWHvF4lq9GO
|
||||
pRR1PZIHyDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAlD4kB
|
||||
xKKbybnrzwOVEsgUZj10Sra5UmvZbz7A18U010mv2tTnZlMyjmwpF40j8vTj3vmA
|
||||
LR99ScyjwgOEYi3L9VMbnljXxDkYEpfLYkH8Xg9rzkaSycZ9gLf6OToiId3nlq1p
|
||||
YC3KQI9oPk3RGGk5iJ7Ndepc/M7PBsKgFSNHTIYpGQr24dNdianVL3Zulc6tTl02
|
||||
ClBstjf+6KZW3CLbNnwm+odcgyJHSLKyGGroeuJm50YtXIZ1xm9Kwxs4t476F/kZ
|
||||
Rmu9j5F8Dp4wh2CTu9sqkL9haeMNegOrXj+evOaNetjThsOy/LR6jEGARV/z60yn
|
||||
FzIGjwcEnmq26MLo7ALnRcCxC1MvKEQ0H6ccdWpK10CREsyX1wIGMFs5IUVkuy8r
|
||||
G0LdmAqZAsbXOwphcq2B6+KoMPFzI75ZU1ITbUuCUpXprxvI6S1OtetrR2Ax84Bm
|
||||
sGKUa/W6d484SR1kuhP0nRehOq7YAYkUoAN4qPP5j4IghgNmrp/ddJcAayVni7ua
|
||||
PTnMHp1Xu0hNPVFmUrwK3NzuZ2xGgkH4aZdlsVPbh0aOCbkdW1E8g8MEn1LqXg9c
|
||||
HomO0ButaxsAOnu/rbfUhvzMZUwT0GkdDsmwiycnottpbhXFgoso/mryj/+HYyUI
|
||||
35F32LtKz8Ns+KSzFADTzpjzM10e8GU/S+8YXA==
|
||||
-----END CERTIFICATE-----
|
257
tester/lime-tester-utils.cpp
Normal file
257
tester/lime-tester-utils.cpp
Normal file
|
@ -0,0 +1,257 @@
|
|||
/*
|
||||
lime-tester-utils.cpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define BCTBX_LOG_DOMAIN "lime-tester"
|
||||
#include <bctoolbox/logging.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "lime_utils.hpp"
|
||||
#include "lime/lime.hpp"
|
||||
#include "lime_keys.hpp"
|
||||
#include "lime_double_ratchet_protocol.hpp"
|
||||
#include "bctoolbox/exception.hh"
|
||||
|
||||
#include "soci/soci.h"
|
||||
#include "soci/sqlite3/soci-sqlite3.h"
|
||||
using namespace::std;
|
||||
using namespace::soci;
|
||||
|
||||
namespace lime {
|
||||
std::vector<std::string> lime_messages_pattern = {
|
||||
{"Frankly, my dear, I don't give a damn."},
|
||||
{"I'm gonna make him an offer he can't refuse."},
|
||||
{"You don't understand! I coulda had class. I coulda been a contender. I could've been somebody, instead of a bum, which is what I am."},
|
||||
{"Toto, I've a feeling we're not in Kansas anymore."},
|
||||
{"Here's looking at you, kid."},
|
||||
{"Go ahead, make my day."},
|
||||
{"All right, Mr. DeMille, I'm ready for my close-up."},
|
||||
{"May the Force be with you."},
|
||||
{"Fasten your seatbelts. It's going to be a bumpy night."},
|
||||
{"You talking to me?"},
|
||||
{"What we've got here is failure to communicate."},
|
||||
{"I love the smell of napalm in the morning. "},
|
||||
{"Love means never having to say you're sorry."},
|
||||
{"The stuff that dreams are made of."},
|
||||
{"E.T. phone home."},
|
||||
{"They call me Mister Tibbs!"},
|
||||
{"Rosebud."},
|
||||
{"Made it, Ma! Top of the world!"},
|
||||
{"I'm as mad as hell, and I'm not going to take this anymore!"},
|
||||
{"Louis, I think this is the beginning of a beautiful friendship."},
|
||||
{"A census taker once tried to test me. I ate his liver with some fava beans and a nice Chianti."},
|
||||
{"Bond. James Bond."},
|
||||
{"There's no place like home. "},
|
||||
{"I am big! It's the pictures that got small."},
|
||||
{"Show me the money!"},
|
||||
{"Why don't you come up sometime and see me?"},
|
||||
{"I'm walking here! I'm walking here!"},
|
||||
{"Play it, Sam. Play 'As Time Goes By.'"},
|
||||
{"You can't handle the truth!"},
|
||||
{"I want to be alone."},
|
||||
{"After all, tomorrow is another day!"},
|
||||
{"Round up the usual suspects."},
|
||||
{"I'll have what she's having."},
|
||||
{"You know how to whistle, don't you, Steve? You just put your lips together and blow."},
|
||||
{"You're gonna need a bigger boat."},
|
||||
{"Badges? We ain't got no badges! We don't need no badges! I don't have to show you any stinking badges!"},
|
||||
{"I'll be back."},
|
||||
{"Today, I consider myself the luckiest man on the face of the earth."},
|
||||
{"If you build it, he will come."},
|
||||
{"My mama always said life was like a box of chocolates. You never know what you're gonna get."},
|
||||
{"We rob banks."},
|
||||
{"Plastics."},
|
||||
{"We'll always have Paris."},
|
||||
{"I see dead people."},
|
||||
{"Stella! Hey, Stella!"},
|
||||
{"Oh, Jerry, don't let's ask for the moon. We have the stars."},
|
||||
{"Shane. Shane. Come back!"},
|
||||
{"Well, nobody's perfect."},
|
||||
{"It's alive! It's alive!"},
|
||||
{"Houston, we have a problem."},
|
||||
{"You've got to ask yourself one question: 'Do I feel lucky?' Well, do ya, punk?"},
|
||||
{"You had me at 'hello.'"},
|
||||
{"One morning I shot an elephant in my pajamas. How he got in my pajamas, I don't know."},
|
||||
{"There's no crying in baseball!"},
|
||||
{"La-dee-da, la-dee-da."},
|
||||
{"A boy's best friend is his mother."},
|
||||
{"Greed, for lack of a better word, is good."},
|
||||
{"Keep your friends close, but your enemies closer."},
|
||||
{"As God is my witness, I'll never be hungry again."},
|
||||
{"Well, here's another nice mess you've gotten me into!"},
|
||||
{"Say 'hello' to my little friend!"},
|
||||
{"What a dump."},
|
||||
{"Mrs. Robinson, you're trying to seduce me. Aren't you?"},
|
||||
{"Gentlemen, you can't fight in here! This is the War Room!"},
|
||||
{"Elementary, my dear Watson."},
|
||||
{"Take your stinking paws off me, you damned dirty ape."},
|
||||
{"Of all the gin joints in all the towns in all the world, she walks into mine."},
|
||||
{"Here's Johnny!"},
|
||||
{"They're here!"},
|
||||
{"Is it safe?"},
|
||||
{"Wait a minute, wait a minute. You ain't heard nothin' yet!"},
|
||||
{"No wire hangers, ever!"},
|
||||
{"Mother of mercy, is this the end of Rico?"},
|
||||
{"Forget it, Jake, it's Chinatown."},
|
||||
{"I have always depended on the kindness of strangers."},
|
||||
{"Hasta la vista, baby."},
|
||||
{"Soylent Green is people!"},
|
||||
{"Open the pod bay doors, please, HAL."},
|
||||
{"Striker: Surely you can't be serious. "},
|
||||
{"Rumack: I am serious...and don't call me Shirley."},
|
||||
{"Yo, Adrian!"},
|
||||
{"Hello, gorgeous."},
|
||||
{"Toga! Toga!"},
|
||||
{"Listen to them. Children of the night. What music they make."},
|
||||
{"Oh, no, it wasn't the airplanes. It was Beauty killed the Beast."},
|
||||
{"My precious."},
|
||||
{"Attica! Attica!"},
|
||||
{"Sawyer, you're going out a youngster, but you've got to come back a star!"},
|
||||
{"Listen to me, mister. You're my knight in shining armor. Don't you forget it. You're going to get back on that horse, and I'm going to be right behind you, holding on tight, and away we're gonna go, go, go!"},
|
||||
{"Tell 'em to go out there with all they got and win just one for the Gipper."},
|
||||
{"A martini. Shaken, not stirred."},
|
||||
{"Who's on first."},
|
||||
{"Cinderella story. Outta nowhere. A former greenskeeper, now, about to become the Masters champion. It looks like a mirac...It's in the hole! It's in the hole! It's in the hole!"},
|
||||
{"Life is a banquet, and most poor suckers are starving to death!"},
|
||||
{"I feel the need - the need for speed!"},
|
||||
{"Carpe diem. Seize the day, boys. Make your lives extraordinary."},
|
||||
{"Snap out of it!"},
|
||||
{"My mother thanks you. My father thanks you. My sister thanks you. And I thank you."},
|
||||
{"Nobody puts Baby in a corner."},
|
||||
{"I'll get you, my pretty, and your little dog, too!"},
|
||||
{"I'm the king of the world!"},
|
||||
{"I have come here to chew bubble gum and kick ass, and I'm all out of bubble gum."}
|
||||
};
|
||||
|
||||
bool DR_message_holdsX3DHInit(std::vector<uint8_t> &message) {
|
||||
// checks on length
|
||||
if (message.size()<4) return false;
|
||||
|
||||
// check protocol version
|
||||
if (message[0] != static_cast<uint8_t>(lime::double_ratchet_protocol::DR_v01)) return false;
|
||||
// check message type: we must have a X3DH init message
|
||||
if (message[1] !=static_cast<uint8_t>(lime::double_ratchet_protocol::DR_message_type::x3dhinit)) return false;
|
||||
|
||||
// check packet length, packet is :
|
||||
// header<3 bytes>, X3DH init packet, Ns+PN<8 bytes>, DHs<X<Curve>::keyLength>, Cipher message Key+tag: DRMessageKey + DRMessageIV <48 bytes>, key auth tag<16 bytes> = <75 + X<Curve>::keyLengh + X3DH init size>
|
||||
// X3DH init size = OPk_flag<1 byte> + selfIK<ED<Curve>::keyLength> + EK<X<Curve>::keyLenght> + SPk id<4 bytes> + OPk id (if flag is 1)<4 bytes>
|
||||
switch (message[2]) {
|
||||
case static_cast<uint8_t>(lime::CurveId::c25519):
|
||||
if (message[3] == 0x00) { // no OPk in the X3DH init message
|
||||
if (message.size() != (75 + X<C255>::keyLength() + 5 + ED<C255>::keyLength() + X<C255>::keyLength())) return false;
|
||||
} else { // OPk present in the X3DH init message
|
||||
if (message.size() != (75 + X<C255>::keyLength() + 9 + ED<C255>::keyLength() + X<C255>::keyLength())) return false;
|
||||
}
|
||||
return true;
|
||||
break;
|
||||
case static_cast<uint8_t>(lime::CurveId::c448):
|
||||
if (message[3] == 0x00) { // no OPk in the X3DH init message
|
||||
if (message.size() != (75 + X<C448>::keyLength() + 5 + ED<C448>::keyLength() + X<C448>::keyLength())) return false;
|
||||
} else { // OPk present in the X3DH init message
|
||||
if (message.size() != (75 + X<C448>::keyLength() + 9 + ED<C448>::keyLength() + X<C448>::keyLength())) return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool DR_message_extractX3DHInit(std::vector<uint8_t> &message, std::vector<uint8_t> &X3DH_initMessage) {
|
||||
if (DR_message_holdsX3DHInit(message) == false) return false;
|
||||
|
||||
// compute size
|
||||
size_t X3DH_length = 5;
|
||||
if (message[2] == static_cast<uint8_t>(lime::CurveId::c25519)) { // curve 25519
|
||||
X3DH_length += ED<C255>::keyLength() + X<C255>::keyLength();
|
||||
} else { // curve 448
|
||||
X3DH_length += ED<C448>::keyLength() + X<C448>::keyLength();
|
||||
}
|
||||
|
||||
if (message[3] == 0x00) { // there is an OPk id
|
||||
X3DH_length += 4;
|
||||
}
|
||||
|
||||
// copy it in buffer
|
||||
X3DH_initMessage.assign(message.begin()+4, message.begin()+4+X3DH_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* Open provided DB and look for DRSessions established between selfDevice and peerDevice
|
||||
* Populate the sessionsId vector with the Ids of sessions found
|
||||
* return the id of the active session if one is found, 0 otherwise */
|
||||
long int get_DRsessionsId(const std::string &dbFilename, const std::string &selfDeviceId, const std::string &peerDeviceId, std::vector<long int> &sessionsId) {
|
||||
soci::session sql(sqlite3, dbFilename); // open the DB
|
||||
sessionsId.clear();
|
||||
sessionsId.resize(25); // no more than 25 sessions id fetched
|
||||
std::vector<int> status(25);
|
||||
try {
|
||||
soci::statement st = (sql.prepare << "SELECT s.sessionId, s.Status FROM DR_sessions as s INNER JOIN lime_PeerDevices as d on s.Did = d.Did INNER JOIN lime_LocalUsers as u on u.Uid = d.Uid WHERE u.UserId = :selfId AND d.DeviceId = :peerId ORDER BY s.Status DESC, s.Did;", into(sessionsId), into(status), use(selfDeviceId), use(peerDeviceId));
|
||||
st.execute();
|
||||
if (st.fetch()) { // all retrieved session shall fit in the arrays no need to go on several fetch
|
||||
// check we don't have more than one active session
|
||||
if (status.size()>=2 && status[0]==1 && status[1]==1) {
|
||||
throw BCTBX_EXCEPTION << "In DB "<<dbFilename<<" local user "<<selfDeviceId<<" and peer device "<<peerDeviceId<<" share more than one active session";
|
||||
}
|
||||
|
||||
// return the active session id if there is one
|
||||
if (status.size()>=1 && status[0]==1) {
|
||||
return sessionsId[0];
|
||||
}
|
||||
}
|
||||
sessionsId.clear();
|
||||
return 0;
|
||||
|
||||
} catch (exception &e) { // swallow any error on DB
|
||||
bctbx_error("Got an error on DB: %s", e.what());
|
||||
sessionsId.clear();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const char charset[] =
|
||||
"0123456789"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz";
|
||||
/**
|
||||
* @brief append a random suffix to user name to avoid collision if test server is user by several tests runs
|
||||
*
|
||||
* @param[in] basename
|
||||
*
|
||||
* @return a shared ptr towards a string holding name+ 6 chars random suffix
|
||||
*/
|
||||
std::shared_ptr<std::string> makeRandomDeviceName(const char *basename) {
|
||||
auto ret = make_shared<std::string>(basename);
|
||||
bctbx_rng_context_t *RNG = bctbx_rng_context_new();
|
||||
std::array<uint8_t,6> rnd;
|
||||
bctbx_rng_get(RNG, rnd.data(), rnd.size());
|
||||
for (auto x : rnd) {
|
||||
ret->append(1, charset[x%(sizeof(charset)-1)]);
|
||||
}
|
||||
bctbx_rng_context_free(RNG);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace lime
|
||||
|
171
tester/lime-tester-utils.hpp
Normal file
171
tester/lime-tester-utils.hpp
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
lime-tester-utils.hpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef lime_tester_utils_hpp
|
||||
#define lime_tester_utils_hpp
|
||||
|
||||
#include "bctoolbox/crypto.h"
|
||||
#include "lime_double_ratchet.hpp"
|
||||
#include "lime_localStorage.hpp"
|
||||
|
||||
#include "soci/sqlite3/soci-sqlite3.h"
|
||||
|
||||
namespace lime {
|
||||
|
||||
extern std::vector<std::string> lime_messages_pattern;
|
||||
|
||||
/**
|
||||
* @brief Create and initialise the two sessions given in parameter. Alice as sender session and Bob as receiver one
|
||||
* Alice must then send the first message, once bob got it, sessions are fully initialised
|
||||
* if fileName doesn't exists as a DB, it will be created, caller shall then delete it if needed
|
||||
*/
|
||||
template <typename Curve>
|
||||
void dr_sessionsInit(std::shared_ptr<DR<Curve>> &alice, std::shared_ptr<DR<Curve>> &bob, std::shared_ptr<lime::Db> &localStorageAlice, std::shared_ptr<lime::Db> &localStorageBob, std::string dbFilenameAlice, std::string dbFilenameBob, bool initStorage=true) {
|
||||
if (initStorage==true) {
|
||||
// create or load Db
|
||||
localStorageAlice = std::make_shared<lime::Db>(dbFilenameAlice);
|
||||
localStorageBob = std::make_shared<lime::Db>(dbFilenameBob);
|
||||
}
|
||||
|
||||
// create and init a RNG needed for shared secret generation
|
||||
bctbx_rng_context_t *RNG = bctbx_rng_context_new();
|
||||
|
||||
/* generate key pair for bob */
|
||||
bctbx_ECDHContext_t *tempECDH = ECDHInit<Curve>();
|
||||
bctbx_ECDHCreateKeyPair(tempECDH, (int (*)(void *, uint8_t *, size_t))bctbx_rng_get, RNG);
|
||||
KeyPair<X<Curve>> bobKeyPair{tempECDH->selfPublic, tempECDH->secret};
|
||||
bctbx_DestroyECDHContext(tempECDH);
|
||||
|
||||
/* generate a shared secret and AD */
|
||||
lime::DRChainKey SK;
|
||||
lime::SharedADBuffer AD;
|
||||
bctbx_rng_get(RNG, SK.data(), SK.size());
|
||||
bctbx_rng_get(RNG, AD.data(), AD.size());
|
||||
bctbx_rng_context_free(RNG);
|
||||
|
||||
// insert the peer Device (with dummy datas in lime_PeerDevices and lime_LocalUsers tables, not used in the DR tests but needed to satisfy foreign key condition on session insertion)
|
||||
long int aliceUid,bobUid,bobDid,aliceDid;
|
||||
localStorageAlice->sql<<"INSERT INTO lime_LocalUsers(UserId, Ik, server) VALUES ('dummy', 1, 'dummy')";
|
||||
localStorageAlice->sql<<"select last_insert_rowid()",soci::into(aliceUid);
|
||||
localStorageAlice->sql<<"INSERT INTO lime_PeerDevices(DeviceId, Uid, Ik) VALUES ('dummy', :Uid, 1)", soci::use(aliceUid);
|
||||
localStorageAlice->sql<<"select last_insert_rowid()",soci::into(aliceDid);
|
||||
|
||||
localStorageBob->sql<<"INSERT INTO lime_LocalUsers(UserId, Ik, server) VALUES ('dummy', 1, 'dummy')";
|
||||
localStorageBob->sql<<"select last_insert_rowid()",soci::into(bobUid);
|
||||
localStorageBob->sql<<"INSERT INTO lime_PeerDevices(DeviceId, Uid, Ik) VALUES ('dummy', :Uid, 1)", soci::use(bobUid);
|
||||
localStorageBob->sql<<"select last_insert_rowid()",soci::into(bobDid);
|
||||
|
||||
// create DR sessions
|
||||
std::vector<uint8_t> X3DH_initMessage{};
|
||||
alice = std::make_shared<DR<Curve>>(localStorageAlice.get(), SK, AD, bobKeyPair.publicKey(), aliceDid, X3DH_initMessage);
|
||||
bob = std::make_shared<DR<Curve>>(localStorageBob.get(), SK, AD, bobKeyPair, bobDid);
|
||||
}
|
||||
|
||||
/* non efficient but used friendly structure to store all details about a session */
|
||||
/* the self_xx are redundants but it's for testing purpose */
|
||||
template <typename Curve>
|
||||
struct sessionDetails {
|
||||
std::string self_userId;
|
||||
std::size_t self_userIndex;
|
||||
std::size_t self_deviceIndex;
|
||||
std::string peer_userId;
|
||||
std::size_t peer_userIndex;
|
||||
std::size_t peer_deviceIndex;
|
||||
std::shared_ptr<DR<Curve>> DRSession; // Session to reach recipient
|
||||
std::shared_ptr<lime::Db> localStorage; // db linked to device
|
||||
sessionDetails() : self_userId{}, self_userIndex{0}, self_deviceIndex{0}, peer_userId{}, peer_userIndex{0}, peer_deviceIndex{0}, DRSession{}, localStorage{} {};
|
||||
sessionDetails(std::string &s_userId, size_t s_userIndex, size_t s_deviceIndex, std::string &p_userId, size_t p_userIndex, size_t p_deviceIndex)
|
||||
: self_userId{s_userId}, self_userIndex{s_userIndex}, self_deviceIndex{s_deviceIndex}, peer_userId{p_userId}, peer_userIndex{p_userIndex}, peer_deviceIndex{p_deviceIndex}, DRSession{}, localStorage{} {};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create and initialise all requested DR sessions for specified number of devices between two or more users
|
||||
* users is a vector of users(already sized to correct size, matching usernames size), each one holds a vector of devices(already sized for each device)
|
||||
* This function will create and instanciate in each device a vector of vector of DR sessions towards all other devices: each device vector holds a bidimentionnal array indexed by userId and deviceId.
|
||||
* Session init is done considering as initial sender the lowest id user and in it the lowest id device
|
||||
*/
|
||||
template <typename Curve>
|
||||
void dr_devicesInit(std::string dbBaseFilename, std::vector<std::vector<std::vector<std::vector<sessionDetails<Curve>>>>> &users, std::vector<std::string> &usernames) {
|
||||
/* each device must have a db, produce filename for them from provided base name and given username */
|
||||
for (size_t i=0; i<users.size(); i++) { // loop on users
|
||||
for (size_t j=0; j<users[i].size(); j++) { // loop on devices
|
||||
// create the db for this device, filename would be <dbBaseFilename>.<username>.<dev><deviceIndex>.sqlite3
|
||||
std::string dbFilename{dbBaseFilename};
|
||||
dbFilename.append(".").append(usernames[i]).append(".dev").append(std::to_string(j)).append(".sqlite3");
|
||||
|
||||
remove(dbFilename.data());
|
||||
|
||||
std::shared_ptr<lime::Db> localStorage = std::make_shared<lime::Db>(dbFilename);
|
||||
|
||||
users[i][j].resize(users.size()); // each device holds a vector towards all users, dimension it
|
||||
// instanciate the session details for all needed sessions
|
||||
for (size_t i_fw=0; i_fw<users.size(); i_fw++) { // loop on the rest of users
|
||||
users[i][j][i_fw].resize(users[i_fw].size()); // [i][j][i_fw] holds a vector of sessions toward user i_fw devices
|
||||
for (size_t j_fw=0; j_fw<users[i].size(); j_fw++) { // loop on the rest of devices
|
||||
if (!((i_fw==i) && (j_fw==j))) { // no session towards ourself
|
||||
/* create session details with self and peer userId, user and device index */
|
||||
sessionDetails<Curve> sessionDetail{usernames[i], i, j, usernames[i_fw], i_fw, j_fw};
|
||||
sessionDetail.localStorage = localStorage;
|
||||
|
||||
users[i][j][i_fw][j_fw]=std::move(sessionDetail);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* users is a vector of users, each of them has a vector of devices, each device hold a vector of sessions */
|
||||
/* sessions init are done by users[i] to all users[>i] (same thing intra device) */
|
||||
for (size_t i=0; i<users.size(); i++) { // loop on users
|
||||
for (size_t j=0; j<users[i].size(); j++) { // loop on devices
|
||||
for (size_t j_fw=j+1; j_fw<users[i].size(); j_fw++) { // loop on the rest of our devices
|
||||
dr_sessionsInit(users[i][j][i][j_fw].DRSession, users[i][j_fw][i][j].DRSession, users[i][j][i][j_fw].localStorage, users[i][j_fw][i][j].localStorage, " ", " ", false);
|
||||
}
|
||||
for (size_t i_fw=i+1; i_fw<users.size(); i_fw++) { // loop on the rest of users
|
||||
for (size_t j_fw=0; j_fw<users[i].size(); j_fw++) { // loop on the rest of devices
|
||||
dr_sessionsInit(users[i][j][i_fw][j_fw].DRSession, users[i_fw][j_fw][i][j].DRSession, users[i][j][i_fw][j_fw].localStorage, users[i_fw][j_fw][i][j].localStorage, " ", " ", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* return true if the message buffer is a valid DR message holding a X3DH init one in its header */
|
||||
bool DR_message_holdsX3DHInit(std::vector<uint8_t> &message);
|
||||
|
||||
/* return true if the message buffer is a valid DR message holding a X3DH init one in its header and copy the X3DH init message in the provided buffer */
|
||||
bool DR_message_extractX3DHInit(std::vector<uint8_t> &message, std::vector<uint8_t> &X3DH_initMessage);
|
||||
|
||||
/* Open provided DB and look for DRSessions established between selfDevice and peerDevice
|
||||
* Populate the sessionsId vector with the Ids of sessions found
|
||||
* return the id of the active session if one is found, 0 otherwise */
|
||||
long int get_DRsessionsId(const std::string &dbFilename, const std::string &selfDeviceId, const std::string &peerDeviceId, std::vector<long int> &sessionsId);
|
||||
|
||||
/**
|
||||
* @brief append a random suffix to user name to avoid collision if test server is user by several tests runs
|
||||
*
|
||||
* @param[in] basename
|
||||
*
|
||||
* @return a shared ptr towards a string holding name+ 6 chars random suffix
|
||||
*/
|
||||
std::shared_ptr<std::string> makeRandomDeviceName(const char *basename);
|
||||
|
||||
} // namespace lime
|
||||
|
||||
#endif
|
132
tester/lime-tester.cpp
Normal file
132
tester/lime-tester.cpp
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
lime-tester.cpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define BCTBX_LOG_DOMAIN "lime-tester"
|
||||
#include <bctoolbox/logging.h>
|
||||
|
||||
#include "lime-tester.hpp"
|
||||
|
||||
static FILE * log_file = NULL;
|
||||
static const char *log_domain = "lime";
|
||||
|
||||
static void log_handler(int lev, const char *fmt, va_list args) {
|
||||
#ifdef _WIN32
|
||||
/* We must use stdio to avoid log formatting (for autocompletion etc.) */
|
||||
vfprintf(lev == BCTBX_LOG_ERROR ? stderr : stdout, fmt, args);
|
||||
fprintf(lev == BCTBX_LOG_ERROR ? stderr : stdout, "\n");
|
||||
#else
|
||||
va_list cap;
|
||||
va_copy(cap,args);
|
||||
vfprintf(lev == BCTBX_LOG_ERROR ? stderr : stdout, fmt, cap);
|
||||
fprintf(lev == BCTBX_LOG_ERROR ? stderr : stdout, "\n");
|
||||
va_end(cap);
|
||||
#endif
|
||||
if (log_file){
|
||||
bctbx_logv(log_domain, (BctbxLogLevel)lev, fmt, args);
|
||||
}
|
||||
}
|
||||
|
||||
void lime_tester_init(void(*ftester_printf)(int level, const char *fmt, va_list args)) {
|
||||
if (ftester_printf == NULL) ftester_printf = log_handler;
|
||||
bc_tester_init(ftester_printf, BCTBX_LOG_MESSAGE, BCTBX_LOG_ERROR, "data");
|
||||
|
||||
bc_tester_add_suite(&lime_double_ratchet_test_suite);
|
||||
bc_tester_add_suite(&lime_lime_test_suite);
|
||||
}
|
||||
|
||||
void lime_tester_uninit(void) {
|
||||
bc_tester_uninit();
|
||||
}
|
||||
|
||||
void lime_tester_before_each() {
|
||||
}
|
||||
|
||||
int lime_tester_set_log_file(const char *filename) {
|
||||
bctbx_log_handler_t* filehandler;
|
||||
char* dir;
|
||||
char* base;
|
||||
if (log_file) {
|
||||
fclose(log_file);
|
||||
}
|
||||
log_file = fopen(filename, "w");
|
||||
if (!log_file) {
|
||||
bctbx_error("Cannot open file [%s] for writing logs because [%s]", filename, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
dir = bctbx_dirname(filename);
|
||||
base = bctbx_basename(filename);
|
||||
bctbx_message("Redirecting traces to file [%s]", filename);
|
||||
filehandler = bctbx_create_file_log_handler(0, dir, base, log_file);
|
||||
bctbx_add_log_handler(filehandler);
|
||||
if (dir) bctbx_free(dir);
|
||||
if (base) bctbx_free(base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if !defined(__ANDROID__) && !(defined(BCTBX_WINDOWS_PHONE) || defined(BCTBX_WINDOWS_UNIVERSAL))
|
||||
|
||||
static const char* lime_helper =
|
||||
"\t\t\t--verbose\n"
|
||||
"\t\t\t--silent\n"
|
||||
"\t\t\t--log-file <output log file path>\n";
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
lime_tester_init(nullptr);
|
||||
|
||||
if (strstr(argv[0], ".libs")) {
|
||||
int prefix_length = (int)(strstr(argv[0], ".libs") - argv[0]) + 1;
|
||||
char prefix[200];
|
||||
sprintf(prefix, "%s%.*s", argv[0][0] == '/' ? "" : "./", prefix_length, argv[0]);
|
||||
bc_tester_set_resource_dir_prefix(prefix);
|
||||
bc_tester_set_writable_dir_prefix(prefix);
|
||||
}
|
||||
|
||||
for(i = 1; i < argc; ++i) {
|
||||
if (strcmp(argv[i],"--verbose")==0){
|
||||
bctbx_set_log_level(log_domain, BCTBX_LOG_DEBUG);
|
||||
bctbx_set_log_level(BCTBX_LOG_DOMAIN,BCTBX_LOG_DEBUG);
|
||||
} else if (strcmp(argv[i],"--silent")==0){
|
||||
bctbx_set_log_level(log_domain, BCTBX_LOG_FATAL);
|
||||
bctbx_set_log_level(BCTBX_LOG_DOMAIN, BCTBX_LOG_FATAL);
|
||||
} else if (strcmp(argv[i],"--log-file")==0){
|
||||
CHECK_ARG("--log-file", ++i, argc);
|
||||
if (lime_tester_set_log_file(argv[i]) < 0) return -2;
|
||||
}else {
|
||||
int ret = bc_tester_parse_args(argc, argv, i);
|
||||
if (ret>0) {
|
||||
i += ret - 1;
|
||||
continue;
|
||||
} else if (ret<0) {
|
||||
bc_tester_helper(argv[0], lime_helper);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
ret = bc_tester_start(argv[0]);
|
||||
lime_tester_uninit();
|
||||
bctbx_uninit_logger();
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
39
tester/lime-tester.hpp
Normal file
39
tester/lime-tester.hpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
lime-tester.hpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef lime_tester_hpp
|
||||
#define lime_tester_hpp
|
||||
|
||||
#include <bctoolbox/tester.h>
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
extern "C" {
|
||||
|
||||
extern test_suite_t lime_double_ratchet_test_suite;
|
||||
extern test_suite_t lime_lime_test_suite;
|
||||
|
||||
void lime_tester_init(void(*ftester_printf)(int level, const char *fmt, va_list args));
|
||||
void lime_tester_uninit(void);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
461
tester/lime_double_ratchet-tester.cpp
Normal file
461
tester/lime_double_ratchet-tester.cpp
Normal file
|
@ -0,0 +1,461 @@
|
|||
/*
|
||||
lime_double_ratchet-tester.cpp
|
||||
Copyright (C) 2017 Belledonne Communications SARL
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define BCTBX_LOG_DOMAIN "lime-tester"
|
||||
#include <bctoolbox/logging.h>
|
||||
|
||||
#include "lime-tester.hpp"
|
||||
#include "lime-tester-utils.hpp"
|
||||
#include "lime_localStorage.hpp"
|
||||
|
||||
#include <bctoolbox/tester.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "bctoolbox/crypto.h"
|
||||
|
||||
|
||||
using namespace::std;
|
||||
using namespace::lime;
|
||||
|
||||
static const std::string AD{"alice@sip.linphone.org;opaque=user:epid:UghFocauauCHBHoLhAAA;gruu;bob@sip.linphone.org;opaque=user:epid:CTboShduoSCdauSEUDbka;gruu;"};
|
||||
|
||||
/*****************************************************************************/
|
||||
/* Direct use of DR encrypt/decrypt, is not what is done in real usage */
|
||||
/*****************************************************************************/
|
||||
|
||||
/**
|
||||
* @param[in] period altern sended each <period> messages (sequence will anyways always start with alice send - bob receive - bob send)
|
||||
* @param[in] skip_period same than above but for reception skipping: at each begining of skip_period, skip reception of skip_length messages
|
||||
* @param[in] skip_length see previous: number of messages to be skipped
|
||||
* @param[in] skip_delay number of messages sending before the skip messages are received
|
||||
* ex: if message 5 is skipped and skip_delay is 10, message 5 will be received after message 15 was sent - and may be received
|
||||
* All delayed messaged are received in their order of sending at the end of message stack processing
|
||||
*/
|
||||
template <typename Curve>
|
||||
static void dr_skippedMessages_basic_test(const uint8_t period=1, const uint8_t skip_period=1000, const uint8_t skip_length=0, const uint8_t skip_delay=0, const std::string db_filename="dr_skipped_message_basic_tmp") {
|
||||
std::shared_ptr<DR<Curve>> alice, bob;
|
||||
std::shared_ptr<lime::Db> aliceLocalStorage, bobLocalStorage;
|
||||
std::string aliceFilename(db_filename);
|
||||
std::string bobFilename(db_filename);
|
||||
aliceFilename.append(".alice.sqlite3");
|
||||
bobFilename.append(".bob.sqlite3");
|
||||
|
||||
//clean tmp files
|
||||
remove(aliceFilename.data());
|
||||
remove(bobFilename.data());
|
||||
|
||||
// create sessions
|
||||
dr_sessionsInit(alice, bob, aliceLocalStorage, bobLocalStorage, aliceFilename, bobFilename);
|
||||
std::vector<std::vector<uint8_t>> cipher;
|
||||
std::vector<std::vector<recipientInfos<Curve>>> recipients;
|
||||
std::vector<uint8_t> messageSender; // hold status of message: 0 not sent, 1 sent by Alice, 2 sent by Bob, 3 received
|
||||
std::vector<std::string> plainMessage;
|
||||
|
||||
// resize vectors to hold all materials
|
||||
cipher.resize(lime_messages_pattern.size());
|
||||
recipients.resize(lime_messages_pattern.size());
|
||||
plainMessage.resize(lime_messages_pattern.size());
|
||||
messageSender.resize(lime_messages_pattern.size(), 0);
|
||||
|
||||
bool aliceSender=true;
|
||||
bctbx_debug("Start skip test\n\n");
|
||||
for (size_t i=0; i<lime_messages_pattern.size(); i++) {
|
||||
/* sending */
|
||||
if (aliceSender) {
|
||||
// alice encrypt a message
|
||||
recipients[i].emplace_back("bob",alice);
|
||||
std::vector<uint8_t> plaintext{lime_messages_pattern[i].begin(), lime_messages_pattern[i].end()};
|
||||
std::vector<uint8_t> cipherMessage{};
|
||||
encryptMessage(recipients[i], plaintext, "bob", "alice", cipher[i]);
|
||||
bctbx_debug("alice encrypt %d", int(i));
|
||||
|
||||
messageSender[i] = 1;
|
||||
if (i%period == 0) {
|
||||
aliceSender=false;
|
||||
}
|
||||
} else {
|
||||
// bob encrypt a message
|
||||
recipients[i].emplace_back("alice",bob);
|
||||
std::vector<uint8_t> plaintext{lime_messages_pattern[i].begin(), lime_messages_pattern[i].end()};
|
||||
std::vector<uint8_t> cipherMessage{};
|
||||
encryptMessage(recipients[i], plaintext, "alice", "bob", cipher[i]);
|
||||
bctbx_debug("bob encrypt %d", int(i));
|
||||
|
||||
messageSender[i] = 2;
|
||||
if (i%period == 0) {
|
||||
aliceSender=true;
|
||||
}
|
||||
}
|
||||
|
||||
/* receiving (or later): immediate reception is skipped for skip_length messages eack skip_period messages */
|
||||
if ((i==0) || !(i%skip_period<skip_length)) { // do not skip the first message otherwise bob wont be able to write to alice
|
||||
if (messageSender[i]==2) {
|
||||
bctbx_debug("alice decrypt %d", int(i));
|
||||
// alice decrypt it
|
||||
std::vector<shared_ptr<DR<Curve>>> recipientDRSessions{};
|
||||
recipientDRSessions.push_back(alice);
|
||||
std::vector<uint8_t> plainBuffer{};
|
||||
decryptMessage("bob", "alice", "alice", recipientDRSessions, recipients[i][0].cipherHeader, cipher[i], plainBuffer);
|
||||
plainMessage[i] = std::string{plainBuffer.begin(), plainBuffer.end()};
|
||||
|
||||
messageSender[i]=3;
|
||||
} else if (messageSender[i]==1) {
|
||||
bctbx_debug("bob decrypt %d", int(i));
|
||||
// bob decrypt it
|
||||
std::vector<shared_ptr<DR<Curve>>> recipientDRSessions{};
|
||||
recipientDRSessions.push_back(bob);
|
||||
std::vector<uint8_t> plainBuffer{};
|
||||
decryptMessage("alice", "bob", "bob", recipientDRSessions, recipients[i][0].cipherHeader, cipher[i], plainBuffer);
|
||||
plainMessage[i] = std::string{plainBuffer.begin(), plainBuffer.end()};
|
||||
|
||||
messageSender[i]=3;
|
||||
} else {
|
||||
BC_FAIL("That should never happend, something is wrong in the test not the lib");
|
||||
}
|
||||
}
|
||||
|
||||
/* Do we have some old message to decrypt */
|
||||
if (i>=skip_delay) {
|
||||
for (size_t j=0; j<i-skip_delay; j++) {
|
||||
if (messageSender[j]==2) {
|
||||
bctbx_debug("alice decrypt %d", int(j));
|
||||
// alice decrypt it
|
||||
std::vector<shared_ptr<DR<Curve>>> recipientDRSessions{};
|
||||
recipientDRSessions.push_back(alice);
|
||||
std::vector<uint8_t> plainBuffer{};
|
||||
decryptMessage("bob", "alice", "alice", recipientDRSessions, recipients[j][0].cipherHeader, cipher[j], plainBuffer);
|
||||
plainMessage[j] = std::string{plainBuffer.begin(), plainBuffer.end()};
|
||||
|
||||
messageSender[j]=3;
|
||||
} else if (messageSender[j]==1) {
|
||||
bctbx_debug("bob decrypt %d", int(j));
|
||||
// bob decrypt it
|
||||
std::vector<shared_ptr<DR<Curve>>> recipientDRSessions{};
|
||||
recipientDRSessions.push_back(bob);
|
||||
std::vector<uint8_t> plainBuffer{};
|
||||
decryptMessage("alice", "bob", "bob", recipientDRSessions, recipients[j][0].cipherHeader, cipher[j], plainBuffer);
|
||||
plainMessage[j] = std::string{plainBuffer.begin(), plainBuffer.end()};
|
||||
|
||||
messageSender[j]=3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Do we have some old message to decrypt(ignore delay we're at the end of test */
|
||||
for (size_t j=0; j<lime_messages_pattern.size(); j++) {
|
||||
if (messageSender[j]==2) {
|
||||
bctbx_debug("alice decrypt %d", int(j));
|
||||
// alice decrypt it
|
||||
std::vector<shared_ptr<DR<Curve>>> recipientDRSessions{};
|
||||
recipientDRSessions.push_back(alice);
|
||||
std::vector<uint8_t> plainBuffer{};
|
||||
decryptMessage("bob", "alice", "alice", recipientDRSessions, recipients[j][0].cipherHeader, cipher[j], plainBuffer);
|
||||
plainMessage[j] = std::string{plainBuffer.begin(), plainBuffer.end()};
|
||||
|
||||
messageSender[j]=3;
|
||||
} else if (messageSender[j]==1) {
|
||||
bctbx_debug("bob decrypt %d", int(j));
|
||||
// bob decrypt it
|
||||
std::vector<shared_ptr<DR<Curve>>> recipientDRSessions{};
|
||||
recipientDRSessions.push_back(bob);
|
||||
std::vector<uint8_t> plainBuffer{};
|
||||
decryptMessage("alice", "bob", "bob", recipientDRSessions, recipients[j][0].cipherHeader, cipher[j], plainBuffer);
|
||||
plainMessage[j] = std::string{plainBuffer.begin(), plainBuffer.end()};
|
||||
|
||||
messageSender[j]=3;
|
||||
}
|
||||
}
|
||||
|
||||
// same same
|
||||
for (size_t i=0; i<lime_messages_pattern.size(); i++) {
|
||||
BC_ASSERT_TRUE(plainMessage[i] == lime_messages_pattern[i]);
|
||||
}
|
||||
bctbx_debug("End of skip test\n\n");
|
||||
}
|
||||
|
||||
static void dr_skippedMessages_basic(void) {
|
||||
#ifdef EC25519_ENABLED
|
||||
/* send batch of 10 messages, delay by 15 one message each time we reach the end of the batch*/
|
||||
dr_skippedMessages_basic_test<C255>(10, 10, 1, 15, "dr_skipMessage_1_X25519");
|
||||
/* delayed messages covering more than a bath */
|
||||
dr_skippedMessages_basic_test<C255>(3, 7, 4, 17, "dr_skipMessage_2_X25519");
|
||||
#endif
|
||||
#ifdef EC448_ENABLED
|
||||
dr_skippedMessages_basic_test<C448>(10, 10, 1, 15, "dr_skipMessage_1_X448");
|
||||
dr_skippedMessages_basic_test<C448>(5, 5, 1, 10, "dr_skipMessage_2_X448");
|
||||
#endif
|
||||
}
|
||||
|
||||
/* alice send <period> messages to bob, and bob replies with <period> messages and so on until the end of message pattern list */
|
||||
template <typename Curve>
|
||||
static void dr_long_exchange_test(uint8_t period=1, std::string db_filename="dr_long_exchange_tmp") {
|
||||
std::shared_ptr<DR<Curve>> alice, bob;
|
||||
std::shared_ptr<lime::Db> aliceLocalStorage, bobLocalStorage;
|
||||
std::string aliceFilename(db_filename);
|
||||
std::string bobFilename(db_filename);
|
||||
aliceFilename.append(".alice.sqlite3");
|
||||
bobFilename.append(".bob.sqlite3");
|
||||
// create sessions
|
||||
dr_sessionsInit(alice, bob, aliceLocalStorage, bobLocalStorage, aliceFilename, bobFilename);
|
||||
std::vector<uint8_t> aliceCipher, bobCipher;
|
||||
|
||||
bool aliceSender=true;
|
||||
|
||||
for (size_t i=0; i<lime_messages_pattern.size(); i++) {
|
||||
if (aliceSender) {
|
||||
// alice encrypt a message
|
||||
std::vector<recipientInfos<Curve>> recipients;
|
||||
recipients.emplace_back("bob",alice);
|
||||
std::vector<uint8_t> plaintext{lime_messages_pattern[i].begin(), lime_messages_pattern[i].end()};
|
||||
std::vector<uint8_t> cipherMessage{};
|
||||
encryptMessage(recipients, plaintext, "bob", "alice", cipherMessage);
|
||||
|
||||
// bob decrypt it
|
||||
std::vector<shared_ptr<DR<Curve>>> recipientDRSessions{};
|
||||
recipientDRSessions.push_back(bob);
|
||||
std::vector<uint8_t> plainBuffer{};
|
||||
decryptMessage("alice", "bob", "bob", recipientDRSessions, recipients[0].cipherHeader, cipherMessage, plainBuffer);
|
||||
std::string plainMessage{plainBuffer.begin(), plainBuffer.end()};
|
||||
|
||||
// same same?
|
||||
BC_ASSERT_TRUE(plainMessage==lime_messages_pattern[i]);
|
||||
if (i%period == 0) {
|
||||
aliceSender=false;
|
||||
/* destroy and reload bob sessions */
|
||||
auto bobSessionId=bob->dbSessionId();
|
||||
bob = nullptr; // release and destroy bob DR context
|
||||
bob = make_shared<DR<Curve>>(bobLocalStorage.get(), bobSessionId);
|
||||
}
|
||||
} else {
|
||||
// bob replies
|
||||
std::vector<recipientInfos<Curve>> recipients;
|
||||
recipients.emplace_back("alice",bob);
|
||||
std::vector<uint8_t> plaintext{lime_messages_pattern[i].begin(), lime_messages_pattern[i].end()};
|
||||
std::vector<uint8_t> cipherMessage{};
|
||||
encryptMessage(recipients, plaintext, "alice", "bob", cipherMessage);
|
||||
|
||||
// alice decrypt it
|
||||
std::vector<shared_ptr<DR<Curve>>> recipientDRSessions{};
|
||||
recipientDRSessions.push_back(alice);
|
||||
std::vector<uint8_t> plainBuffer{};
|
||||
decryptMessage("bob", "alice", "alice", recipientDRSessions, recipients[0].cipherHeader, cipherMessage, plainBuffer);
|
||||
std::string plainMessage{plainBuffer.begin(), plainBuffer.end()};
|
||||
|
||||
// same same?
|
||||
BC_ASSERT_TRUE(plainMessage==lime_messages_pattern[i]);
|
||||
if (i%period == 0) {
|
||||
aliceSender=true;
|
||||
/* destroy and reload alice sessions */
|
||||
auto aliceSessionId=alice->dbSessionId();
|
||||
alice = nullptr; // release and destroy alice DR context
|
||||
alice = make_shared<DR<Curve>>(aliceLocalStorage.get(), aliceSessionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove temporary db file
|
||||
remove(aliceFilename.data());
|
||||
remove(bobFilename.data());
|
||||
}
|
||||
static void dr_long_exchange1(void) {
|
||||
#ifdef EC25519_ENABLED
|
||||
dr_long_exchange_test<C255>(1, "dr_long_exchange_1_X25519");
|
||||
#endif
|
||||
#ifdef EC448_ENABLED
|
||||
dr_long_exchange_test<C448>(1, "dr_long_exchange_1_X448");
|
||||
#endif
|
||||
}
|
||||
static void dr_long_exchange3(void) {
|
||||
#ifdef EC25519_ENABLED
|
||||
dr_long_exchange_test<C255>(3, "dr_long_exchange_3_X25519");
|
||||
#endif
|
||||
#ifdef EC448_ENABLED
|
||||
dr_long_exchange_test<C448>(3, "dr_long_exchange_3_X448");
|
||||
#endif
|
||||
}
|
||||
static void dr_long_exchange10(void) {
|
||||
#ifdef EC25519_ENABLED
|
||||
dr_long_exchange_test<C255>(10, "dr_long_exchange_10_X25519");
|
||||
#endif
|
||||
#ifdef EC448_ENABLED
|
||||
dr_long_exchange_test<C448>(10, "dr_long_exchange_10_X448");
|
||||
#endif
|
||||
}
|
||||
|
||||
/* alice send a message to bob, and he replies */
|
||||
template <typename Curve>
|
||||
static void dr_basic_test(std::string db_filename) {
|
||||
std::shared_ptr<DR<Curve>> alice, bob;
|
||||
std::shared_ptr<lime::Db> localStorageAlice, localStorageBob;
|
||||
std::string aliceFilename(db_filename);
|
||||
std::string bobFilename(db_filename);
|
||||
aliceFilename.append(".alice.sqlite3");
|
||||
bobFilename.append(".bob.sqlite3");
|
||||
|
||||
// remove temporary db file if they are here
|
||||
remove(aliceFilename.data());
|
||||
remove(bobFilename.data());
|
||||
|
||||
// create sessions: alice sender, bob receiver
|
||||
dr_sessionsInit(alice, bob, localStorageAlice, localStorageBob, aliceFilename, bobFilename);
|
||||
std::vector<uint8_t> aliceCipher, bobCipher;
|
||||
|
||||
// alice encrypt a message
|
||||
std::vector<recipientInfos<Curve>> recipients;
|
||||
recipients.emplace_back("bob",alice);
|
||||
std::vector<uint8_t> plaintextAlice{lime_messages_pattern[0].begin(), lime_messages_pattern[0].end()};
|
||||
encryptMessage(recipients, plaintextAlice, "bob", "alice", aliceCipher);
|
||||
|
||||
// bob decrypt it
|
||||
std::vector<shared_ptr<DR<Curve>>> recipientDRSessions{};
|
||||
recipientDRSessions.push_back(bob);
|
||||
std::vector<uint8_t> plainBuffer{};
|
||||
decryptMessage("alice", "bob", "bob", recipientDRSessions, recipients[0].cipherHeader, aliceCipher, plainBuffer);
|
||||
std::string plainMessageBob{plainBuffer.begin(), plainBuffer.end()};
|
||||
|
||||
// same same?
|
||||
BC_ASSERT_TRUE(plainMessageBob==lime_messages_pattern[0]);
|
||||
|
||||
|
||||
// bob replies
|
||||
recipients.clear();
|
||||
recipients.emplace_back("alice",bob);
|
||||
std::vector<uint8_t> plaintextBob{lime_messages_pattern[1].begin(), lime_messages_pattern[1].end()};
|
||||
encryptMessage(recipients, plaintextBob, "alice", "bob", bobCipher);
|
||||
|
||||
// alice decrypt it
|
||||
recipientDRSessions.clear();
|
||||
recipientDRSessions.push_back(alice);
|
||||
plainBuffer.clear();
|
||||
decryptMessage("bob", "alice", "alice", recipientDRSessions, recipients[0].cipherHeader, bobCipher, plainBuffer);
|
||||
std::string plainMessageAlice{plainBuffer.begin(), plainBuffer.end()};
|
||||
|
||||
// same same?
|
||||
BC_ASSERT_TRUE(plainMessageAlice==lime_messages_pattern[1]);
|
||||
}
|
||||
|
||||
static void dr_basic(void) {
|
||||
#ifdef EC25519_ENABLED
|
||||
dr_basic_test<C255>("dr_basic_X25519");
|
||||
#endif
|
||||
#ifdef EC448_ENABLED
|
||||
dr_basic_test<C448>("dr_basic_X448");
|
||||
#endif
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
/* */
|
||||
/* Use the encrypt/decrypt message functions: encrypt for multiple recipients*/
|
||||
/* This is the API to be used even in case of single recipient */
|
||||
/* */
|
||||
/*****************************************************************************/
|
||||
|
||||
/* alice send a message to bob, and he replies */
|
||||
template <typename Curve>
|
||||
static void multidevice_basic_test(std::string db_filename) {
|
||||
/* we have 2 users "alice" and "bob" with 3 devices each */
|
||||
std::vector<std::string> usernames{"alice", "bob"};
|
||||
std::vector<std::vector<std::vector<std::vector<sessionDetails<Curve>>>>> users;
|
||||
|
||||
/* give correct size to our users vector for users and devices count */
|
||||
users.resize(usernames.size());
|
||||
for (auto &user : users) user.resize(3);
|
||||
|
||||
/* init and instanciate, session will be then found in a 4 dimensional vector indexed this way : [self user id][self device id][peer user id][peer device id] */
|
||||
dr_devicesInit(db_filename, users, usernames);
|
||||
|
||||
/* Send a message from alice.dev0 to all bob device(and copy to alice devices too) */
|
||||
std::vector<recipientInfos<Curve>> recipients;
|
||||
for (size_t u=0; u<users.size(); u++) { // loop users
|
||||
for (size_t d=0; d<users[u].size(); d++) { // devices
|
||||
if (u!=0 || d!=0) { // sender is users 0, device 0, do not encode for him
|
||||
recipientInfos<Curve> recipient;
|
||||
recipient.deviceId = users[0][0][u][d].peer_userId; // source is 0,0 dest is u,d
|
||||
recipient.deviceId.append("@").append(std::to_string(users[0][0][u][d].peer_deviceIndex)); //deviceId is peerUserId@peerDeviceId
|
||||
recipient.DRSession = users[0][0][u][d].DRSession;
|
||||
recipients.push_back(recipient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string sourceId = usernames[0];
|
||||
sourceId.append("@").append(to_string(0)); // source deviceId shall be alice@0
|
||||
std::vector<std::vector<uint8_t>> cipherHeader;
|
||||
std::vector<uint8_t> cipherMessage;
|
||||
std::vector<uint8_t> plaintext{lime_messages_pattern[0].begin(), lime_messages_pattern[0].end()};
|
||||
|
||||
encryptMessage(recipients, plaintext, usernames[1], sourceId, cipherMessage);
|
||||
|
||||
// Now try decrypt the messages received on every device
|
||||
// pop the headers from front in recipients so loop on the exact same order as when building it
|
||||
for (size_t u=0; u<users.size(); u++) { // loop users
|
||||
for (size_t d=0; d<users[u].size(); d++) { // devices
|
||||
if (u!=0 || d!=0) { // sender is users 0, device 0, do not decode with it
|
||||
recipientInfos<Curve> recipient = recipients.front();
|
||||
// store our DRSession in a vector as interface request a vector of DRSession to try them all, we may have several sessions with one peer device
|
||||
std::vector<shared_ptr<DR<Curve>>> recipientDRSessions{};
|
||||
recipientDRSessions.push_back(users[u][d][0][0].DRSession); // we are u,d receiving from 0,0
|
||||
|
||||
std::vector<uint8_t> plaintext_back;
|
||||
decryptMessage(sourceId, recipient.deviceId, usernames[1], recipientDRSessions, recipient.cipherHeader, cipherMessage, plaintext_back); // recipient id is username 1
|
||||
|
||||
// convert back the output vector to a string
|
||||
std::string plaintext_back_string{plaintext_back.begin(), plaintext_back.end()};
|
||||
|
||||
BC_ASSERT_TRUE(plaintext_back_string==lime_messages_pattern[0]);
|
||||
|
||||
recipients.erase(recipients.begin());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void multidevice_basic(void) {
|
||||
#ifdef EC25519_ENABLED
|
||||
multidevice_basic_test<C255>("ed_basic_C25519");
|
||||
#endif
|
||||
#ifdef EC448_ENABLED
|
||||
multidevice_basic_test<C448>("ed_basic_C448");
|
||||
#endif
|
||||
}
|
||||
static test_t tests[] = {
|
||||
TEST_NO_TAG("DR Basic", dr_basic),
|
||||
TEST_NO_TAG("DR Long Exchange 1", dr_long_exchange1),
|
||||
TEST_NO_TAG("DR Long Exchange 3", dr_long_exchange3),
|
||||
TEST_NO_TAG("DR Long Exchange 10", dr_long_exchange10),
|
||||
TEST_NO_TAG("DR Skip message", dr_skippedMessages_basic),
|
||||
TEST_NO_TAG("Multidevices Basic", multidevice_basic),
|
||||
};
|
||||
|
||||
test_suite_t lime_double_ratchet_test_suite = {
|
||||
"Double Ratchet",
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
sizeof(tests) / sizeof(tests[0]),
|
||||
tests
|
||||
};
|
1144
tester/lime_lime-tester.cpp
Normal file
1144
tester/lime_lime-tester.cpp
Normal file
File diff suppressed because it is too large
Load diff
1
tester/server/.gitignore
vendored
Normal file
1
tester/server/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
node_modules*
|
60
tester/server/README.md
Normal file
60
tester/server/README.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
README for the X3DH test server running on nodejs
|
||||
=================================================
|
||||
|
||||
Disclaimer
|
||||
-----------
|
||||
THIS SERVER IS NOT INTENDED FOR REAL USE BUT FOR TEST PURPOSE ONLY
|
||||
DO NOT USE IN REAL LIFE IT IS NOT SECURE
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
- nodejs
|
||||
- sqlite3
|
||||
- certificate(default x3dh-cert.pem)
|
||||
- associated private key(default x3dh-key.pem)
|
||||
|
||||
The certificate must also be accessible to tester client
|
||||
|
||||
|
||||
Install
|
||||
-------
|
||||
run :
|
||||
npm install
|
||||
|
||||
in the current directory
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
node x3dh.js -d <path> [options]
|
||||
Options:
|
||||
--help Show help [boolean]
|
||||
--version Show version number [boolean]
|
||||
--database, -d path to database file [string] [required]
|
||||
--port, -p port to listen [number] [default: 25519]
|
||||
--certificate, -c path to server certificate
|
||||
[string] [default: "x3dh-cert.pem"]
|
||||
--key, -k path to server private key
|
||||
[string] [default: "x3dh-key.pem"]
|
||||
--ellipticCurve, -e set which Elliptic Curve users of this server must use,
|
||||
option used once at database creation, it is then ignored
|
||||
[string] [choices: "c25519", "c448"] [default: "c25519"]
|
||||
--resource_dir, -r set directory path to used as base to find database and
|
||||
key files [string] [default: "./"]
|
||||
--lifetime, -l lifetime of a user in seconds, 0 is forever.
|
||||
[number] [default: 300]
|
||||
|
||||
|
||||
Script
|
||||
------
|
||||
Server is managing one type of keys(Curve 25519 or Curve 448 based).
|
||||
To run automated tests if you build liblime with both curves enabled you must
|
||||
run two servers, on ports 25519 and 25520.
|
||||
|
||||
A convenient launching script is provided : localServerStart.sh
|
||||
It does:
|
||||
- killall nodejs instance running(beware if you use it no shared server)
|
||||
- wipe out c25519.sqlite3 and c448.sqlite3
|
||||
- start server using c22519.sqlite3 db listening on port 25519 using curve25519
|
||||
- start server using c22520.sqlite3 db listening on port 25520 using curve448
|
1
tester/server/localServerStart.sh
Executable file
1
tester/server/localServerStart.sh
Executable file
|
@ -0,0 +1 @@
|
|||
killall node; rm c25519.sqlite3; rm c448.sqlite3; node x3dh.js -d c25519.sqlite3 & node x3dh.js -d c448.sqlite3 -p 25520 -e c448
|
17
tester/server/package.json
Normal file
17
tester/server/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "x3dh_server",
|
||||
"version": "1.0.0",
|
||||
"description": "A key management server for X3DH",
|
||||
"main": "x3dh.js",
|
||||
"dependencies": {
|
||||
"rwlock": "^5.0.0",
|
||||
"sqlite3": "^3.1.13",
|
||||
"yargs": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Belledonne Communications",
|
||||
"license": "GNU GPL"
|
||||
}
|
32
tester/server/x3dh-cert.pem
Normal file
32
tester/server/x3dh-cert.pem
Normal file
|
@ -0,0 +1,32 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIFiDCCA3CgAwIBAgIJALsKyRSa4G9EMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNzEwMDUwMjE4
|
||||
NDNaFw0yNzA3MDUwMjE4NDNaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l
|
||||
LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV
|
||||
BAMMCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKgn
|
||||
DutdWl2naVa4XuQr6zTKcPMMaz8et7siyAe2bWkJZhL3ULkepU89RFbNAa6TeOOO
|
||||
XqV50UL/xvRN9j0zahR7Cy8ZJvwRlDVCTcKY6mayKqIXSQ6vbc6TY2jnPEI53Z8e
|
||||
WdcMcCnbCGuIhwtPSA7LXbTnk346jZo0WafTw7SZ9MYK9irEFAFAMjnckHCa3qry
|
||||
i6/yh1AmRXd1ZEBwWyqOPMNuKiprMlaixx2HFikD5K0T6V6r1j64r5uIpGyCLUsk
|
||||
evnYUcnimuiVcqT/sTYRSPBcdRiDalzpa1VS9oYiBZbSxdp3dytr7pHETzSjRoS4
|
||||
ASSHR3B2ptjMG0/IHuw/S71w/cI0GA1zVsGjqjkcWR1d5XJWFipDEX1nNmFjreHW
|
||||
ylMBnbDg9VYqxIjqPXFuFrstuZl5jFKJlYYOsyH0XBgJrItdqaEKpgcBh83dEwsw
|
||||
ohwcy/v5Vn/VkG/LvEx0T+nqoKs5HanyJplo4ioE4q4CaL1/C7OYU9Ow1UlFHlzg
|
||||
YB0l33vZfSdSWR70AJvDM8o0rBQUnQ1BWAXjNgKWMngJjJBYGormXOpZDm7ta2yb
|
||||
AH9ji7VUqBdNUlNGCvVmyWsCnUeZmvxVjzJuYekl0SeOMML+JbabfAIgR/27xTD/
|
||||
TsybPjog8E8E3oAySTv1J7tHQMLXOpBj0QtFFfCBAgMBAAGjUzBRMB0GA1UdDgQW
|
||||
BBTTQjx6ywWHvF4lq9GOpRR1PZIHyDAfBgNVHSMEGDAWgBTTQjx6ywWHvF4lq9GO
|
||||
pRR1PZIHyDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAlD4kB
|
||||
xKKbybnrzwOVEsgUZj10Sra5UmvZbz7A18U010mv2tTnZlMyjmwpF40j8vTj3vmA
|
||||
LR99ScyjwgOEYi3L9VMbnljXxDkYEpfLYkH8Xg9rzkaSycZ9gLf6OToiId3nlq1p
|
||||
YC3KQI9oPk3RGGk5iJ7Ndepc/M7PBsKgFSNHTIYpGQr24dNdianVL3Zulc6tTl02
|
||||
ClBstjf+6KZW3CLbNnwm+odcgyJHSLKyGGroeuJm50YtXIZ1xm9Kwxs4t476F/kZ
|
||||
Rmu9j5F8Dp4wh2CTu9sqkL9haeMNegOrXj+evOaNetjThsOy/LR6jEGARV/z60yn
|
||||
FzIGjwcEnmq26MLo7ALnRcCxC1MvKEQ0H6ccdWpK10CREsyX1wIGMFs5IUVkuy8r
|
||||
G0LdmAqZAsbXOwphcq2B6+KoMPFzI75ZU1ITbUuCUpXprxvI6S1OtetrR2Ax84Bm
|
||||
sGKUa/W6d484SR1kuhP0nRehOq7YAYkUoAN4qPP5j4IghgNmrp/ddJcAayVni7ua
|
||||
PTnMHp1Xu0hNPVFmUrwK3NzuZ2xGgkH4aZdlsVPbh0aOCbkdW1E8g8MEn1LqXg9c
|
||||
HomO0ButaxsAOnu/rbfUhvzMZUwT0GkdDsmwiycnottpbhXFgoso/mryj/+HYyUI
|
||||
35F32LtKz8Ns+KSzFADTzpjzM10e8GU/S+8YXA==
|
||||
-----END CERTIFICATE-----
|
52
tester/server/x3dh-key.pem
Normal file
52
tester/server/x3dh-key.pem
Normal file
|
@ -0,0 +1,52 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCoJw7rXVpdp2lW
|
||||
uF7kK+s0ynDzDGs/Hre7IsgHtm1pCWYS91C5HqVPPURWzQGuk3jjjl6ledFC/8b0
|
||||
TfY9M2oUewsvGSb8EZQ1Qk3CmOpmsiqiF0kOr23Ok2No5zxCOd2fHlnXDHAp2whr
|
||||
iIcLT0gOy12055N+Oo2aNFmn08O0mfTGCvYqxBQBQDI53JBwmt6q8ouv8odQJkV3
|
||||
dWRAcFsqjjzDbioqazJWoscdhxYpA+StE+leq9Y+uK+biKRsgi1LJHr52FHJ4pro
|
||||
lXKk/7E2EUjwXHUYg2pc6WtVUvaGIgWW0sXad3cra+6RxE80o0aEuAEkh0dwdqbY
|
||||
zBtPyB7sP0u9cP3CNBgNc1bBo6o5HFkdXeVyVhYqQxF9ZzZhY63h1spTAZ2w4PVW
|
||||
KsSI6j1xbha7LbmZeYxSiZWGDrMh9FwYCayLXamhCqYHAYfN3RMLMKIcHMv7+VZ/
|
||||
1ZBvy7xMdE/p6qCrOR2p8iaZaOIqBOKuAmi9fwuzmFPTsNVJRR5c4GAdJd972X0n
|
||||
Ulke9ACbwzPKNKwUFJ0NQVgF4zYCljJ4CYyQWBqK5lzqWQ5u7WtsmwB/Y4u1VKgX
|
||||
TVJTRgr1ZslrAp1HmZr8VY8ybmHpJdEnjjDC/iW2m3wCIEf9u8Uw/07Mmz46IPBP
|
||||
BN6AMkk79Se7R0DC1zqQY9ELRRXwgQIDAQABAoICABzhWHakOfkL39O9Js8Zm/Qd
|
||||
MPkNkP3uULAbcS+h7Xi03Is1Xu/si4fohexCmZ9aRNEQisDxAzf2pj0fhsNMKVQL
|
||||
LgiQ0VlJy6K6GJDropaw0xGz3iBfkQSB2/kQfhEBz0ac9+EvibQmonOVp1wR6dZg
|
||||
p1+CxppPhDKP+zYP8PT73EaHa3A87RKp6/Z0I3qznrrNnaBCj2r8p8G0r9tBcOcG
|
||||
Nvl52lQexS6MFpbDtNKmkIgJe3N/H/T103NrLJJWaWEWiuiOdr2t5d08jcw4/j7D
|
||||
CTv/JT1olMQQcxbcgOrYvQ0CdmjnS9hxWTA//zqTMHFrCQc1FNgECPQDWk2BmY3C
|
||||
qKADdjYwNpaXuiGhLGc8lHqQ7ZpKyhaxCfjMAH9im9kkwLSZ17kuurLQLBbl023w
|
||||
uTq9RMrJHGLjZO1qTaRk6DgdLIsgwWceIxvl6qvd6jqwlsNvFlpBegEE/BOfFylX
|
||||
9QwLdiFEWOc5UaoThW1ZyyaLqBX+wfZ2qEwCMTupcThP3CZLg+dIhSMyQWF038An
|
||||
mTio4utf7q5LCD7UFvSB4i/DZZiBIeFhFvImLKtOk3hQihgy/wxS0U4F6sgqRRGl
|
||||
tl6e5XNlJFrmkB4jq+5iAth6wsAVmcAnIGwJ9XJ6bpdua6BcKpYG5ecgIn1i80+9
|
||||
6umNsfibWIlPSzgdI96BAoIBAQDTn6L5xPCYm3Kaw1BJfBCOga2Wp6us8HfKlnp/
|
||||
0CZ8EchoTryavgnVFklpzkhxPtb8xXLFrPfPj+BK5qP+UX/DA+6ZiQzVZJseCbEV
|
||||
EWT4xxns0xt1gD2FQiJhhewp2UOEUwv0LXyCwvs8XoiAh9J4QqCNLLTSgB1/vbvy
|
||||
3tLqXJtciN0URPg2wM9SV2odqfeUUplZodHdmgrjhg6R7v/bYxjYXQaJFUtpJbZp
|
||||
FBRhRdhAyeJDeHdmXIpblGKuA0ptDiHNUJobzuUe+y9oo+JMRtGJRTRK0uTBMmR+
|
||||
8+gfoNXVcQWMvZkscs98RzJaVx4H116jdvjchxFt/wHFZezlAoIBAQDLadBR9o1p
|
||||
tNqQ30iz7NR41UXOsoNUzlQmls67KBvnLB3I3lqqRxyenVTr7Sz18IH3nRN+cIT2
|
||||
yWj4dKQ4KIgdxWXnY+WTcoNL5oYdzYWgSqexq/om+3l/zysTrMwoSF6nm/rhef0q
|
||||
kbs2wxOE1sYw6Y02T9QeFNZ9xvnYlv0d4BnquLmeBFgMteQdA5+Wz7Q1lJXJ2wmQ
|
||||
e65EkA8N00dOCQL/8CHDgOpFKx7lokHpJlUgHgi47ZgiYtIaHbAR0NAHjyf5WKJV
|
||||
aRw9yZRP7gOfG3kwg5nKkjBDcKmVpmfaV/yebNDi8zSpntbpttZcwGJPr+VzT2CC
|
||||
xZW1IEm30ZdtAoIBAE9cYWDrcxK30N2q+zPjm83lYbTKwj3DnBjUH7JI2/XRMWe0
|
||||
h1Q8ijFn/zX3jwExAKygWy84c2JaYVGVdtCLva7jjZn0ZGSuKuGz3r243TXSbK18
|
||||
aaLB6dBMgdoyv+LdwE0iW6xBVp0vA8qa5PPWe55v1ge7SUBbnTRncdFdsJkFC5pD
|
||||
FS1hw/e8jMMjVv8y2067Pxj27138Q7MZdeNSTo72db3F/Qpxeus+ok57ojzsVXHO
|
||||
g9Srm5M+hwBbADqjr0iAcTquzGw7RX890YSbWOUj9bZVxot81R6CiF93logt56L4
|
||||
lI04GPYrRBYmGQL6AqAMK0h/+EmzOV0XsZFWbOUCggEAatUkKCgJlV76++OJV5Ym
|
||||
8V9xzmFLQJss7yd9ZCZooT02V20QySWYDmYrV821M2YhnF7PAjUuYD7f9r2sEiKX
|
||||
eqtWHfUFei9GbfKgtb5fVBRhhpsAQsEIyuuPgEHc57s6m2T8mrdrE5R+cwB5m+cB
|
||||
KGdBVykOCs5+6Ig8aFjEj5BHjFOAiVFWvTY6nFJu2tIzn4BuOxnjKBMNL81JWsWA
|
||||
SafJ/4mq8fcfEzzWeVyCqWUMiFW91wJSSJ0GI9k7w9+bopy5LegzOhB+WKUqU+B5
|
||||
3nKbb7NubknhN0pQhtxi5vYXI02bV9bL7doSnaT9aLFR8PA83hbuZUBIpFxU20ok
|
||||
8QKCAQA/Fc+Q3BQVpDEFm9pCj08zFT9MA5VvlnRpdAENMPftYjc8N50lQcaK0v3X
|
||||
2/Z0I2QGfn8U7UPzck/AUT7T6YjfN2NyfOzOw33zYCER0EBDKOuesmlYnsErvQtl
|
||||
mI52+7hxjr5MY+ORTGlvse41+cR6JJgo92j5dgHgpoJZV/yRrxgFdgNx95J3s8nn
|
||||
AIIcftjcTliB7xSSgLGoibNku5dSgPGgJTlFz2EBgamYLSuguPUJVCIIewlhSYIq
|
||||
pP74hAkxmFEhxD3rxAQjsYIUjnsSSEP2/yU9nY39dAnEGbz0W9GZPU+i4KKmkao/
|
||||
ZQ3QhovPIsUhUWmfUb2Dst2AqCPL
|
||||
-----END PRIVATE KEY-----
|
533
tester/server/x3dh.js
Normal file
533
tester/server/x3dh.js
Normal file
|
@ -0,0 +1,533 @@
|
|||
// parse arguments to find port to listen and path to DB
|
||||
// db path is mandatory
|
||||
// port default to 25519
|
||||
// path to server certificate default to ./x3dh-cert.pem
|
||||
// path to server key default to ./x3dh-key.pem
|
||||
const yargs = require('yargs')
|
||||
.option('database', {
|
||||
alias : 'd',
|
||||
describe: 'path to database file',
|
||||
demandOption: true,
|
||||
type: 'string'
|
||||
})
|
||||
.option('port', {
|
||||
alias : 'p',
|
||||
describe: 'port to listen',
|
||||
type: 'number',
|
||||
default: 25519
|
||||
})
|
||||
.option('certificate', {
|
||||
alias : 'c',
|
||||
describe: 'path to server certificate',
|
||||
type: 'string',
|
||||
default : 'x3dh-cert.pem'
|
||||
})
|
||||
.option('key', {
|
||||
alias : 'k',
|
||||
describe: 'path to server private key',
|
||||
type: 'string',
|
||||
default : 'x3dh-key.pem'
|
||||
})
|
||||
.option('ellipticCurve', {
|
||||
alias : 'e',
|
||||
describe: 'set which Elliptic Curve users of this server must use, option used once at database creation, it is then ignored',
|
||||
type : 'string',
|
||||
choices : ['c25519', 'c448'],
|
||||
default : 'c25519'
|
||||
})
|
||||
.option('resource_dir', {
|
||||
alias : 'r',
|
||||
describe : 'set directory path to used as base to find database and key files',
|
||||
type : 'string',
|
||||
default : './'
|
||||
})
|
||||
.option('lifetime', {
|
||||
alias : 'l',
|
||||
describe : 'lifetime of a user in seconds, 0 is forever.',
|
||||
type : 'number',
|
||||
default : 300
|
||||
})
|
||||
.argv;
|
||||
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const ReadWriteLock = require('rwlock');
|
||||
var lock = new ReadWriteLock();
|
||||
|
||||
const options = {
|
||||
key: fs.readFileSync(yargs.resource_dir+yargs.key),
|
||||
cert: fs.readFileSync(yargs.resource_dir+yargs.certificate)
|
||||
};
|
||||
|
||||
// define the curve Id used on this server, default is curve25519, it will be either load from DB or got from args if we are creation the DB.
|
||||
// WARNING: value shall be in sync with defines in client code: Curve25519 = 1, Curve448 = 2 */
|
||||
const enum_curveId = {
|
||||
CURVE25519 : 1,
|
||||
CURVE448 : 2
|
||||
};
|
||||
|
||||
var curveId = enum_curveId.CURVE25519;
|
||||
|
||||
const X3DH_protocolVersion = 0x01;
|
||||
const X3DH_headerSize = 3;
|
||||
|
||||
const enum_messageTypes = {
|
||||
unset_type:0x00,
|
||||
registerUser:0x01,
|
||||
deleteUser:0x02,
|
||||
postSPk:0x03,
|
||||
postOPks:0x04,
|
||||
getPeerBundle:0x05,
|
||||
peerBundle:0x06,
|
||||
error:0xff
|
||||
};
|
||||
|
||||
const enum_errorCodes = {
|
||||
bad_content_type : 0x00,
|
||||
bad_curve : 0x01,
|
||||
missing_senderId : 0x02,
|
||||
bad_x3dh_protocol_version : 0x03,
|
||||
bad_size : 0x04,
|
||||
user_already_in : 0x05,
|
||||
user_not_found : 0x06,
|
||||
db_error : 0x07,
|
||||
bad_request : 0x08
|
||||
};
|
||||
|
||||
const keySizes = { // 1 is for enum_curveId.CURVE25519, 2 is for enum_curveId.CURVE448
|
||||
1 : {X_pub : 32, X_priv : 32, ED_pub : 32, ED_priv : 32, Sig : 64},
|
||||
2 : {X_pub : 56, X_priv : 56, ED_pub : 57, ED_priv : 57, Sig : 114}
|
||||
};
|
||||
|
||||
// open DataBase
|
||||
var db = new sqlite3.Database(yargs.resource_dir+yargs.database);
|
||||
db.run("PRAGMA foreign_keys = ON;"); // enable foreign keys
|
||||
//db.on('trace', function(query) {console.log(query);});
|
||||
//db.on('profile', function(query,time) {console.log("Profile Sqlite "); console.log(query); console.log(time);});
|
||||
|
||||
|
||||
// Check the user version, do we need to do something on this DB?
|
||||
db.get("PRAGMA user_version;", function(err, row) {
|
||||
if (row.user_version < 1) { // current user version is 1
|
||||
curveId = (yargs.ellipticCurve=='c25519')?enum_curveId.CURVE25519:enum_curveId.CURVE448;
|
||||
console.log("Create Database "+yargs.database+" using curve "+yargs.ellipticCurve);
|
||||
db.run(
|
||||
/* Users table:
|
||||
* - id as primary key (internal use)
|
||||
* - a userId(shall be the GRUU)
|
||||
* - Ik (Identity key - EDDSA public key)
|
||||
* - SPk(the current signed pre-key - ECDH public key)
|
||||
* - SPk_sig(current SPk public key signed with Identity key)
|
||||
* - SPh_id (id for SPk provided and internally used by client) - 4 bytes unsigned integer */
|
||||
"CREATE TABLE Users(Uid INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, UserId TEXT NOT NULL, Ik BLOB NOT NULL, SPk BLOB, SPk_sig BLOB, SPk_id UNSIGNED INTEGER);")
|
||||
/* One Time PreKey table:
|
||||
* - an id as primary key (internal use)
|
||||
* - the Uid of user owning that key
|
||||
* - the public key (ECDH public key)
|
||||
* - OPk_id (id for OPk provided and internally used by client) - 4 bytes unsigned integer */
|
||||
.run("CREATE TABLE OPk(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, Uid INTEGER NOT NULL, OPk BLOB NOT NULL, OPk_id UNSIGNED INTEGER NOT NULL, FOREIGN KEY(Uid) REFERENCES Users(Uid) ON UPDATE CASCADE ON DELETE CASCADE);")
|
||||
/* a one row configuration table holding: Id Curve in use on this server*/
|
||||
.run("CREATE TABLE Config(CurveId INTEGER NOT NULL);", function(err) {
|
||||
/* and insert configured curveId */
|
||||
db.run("INSERT INTO Config(CurveId) VALUES(?);", curveId);
|
||||
})
|
||||
// set user_version to 1
|
||||
.run("PRAGMA user_version=1;");
|
||||
} else {
|
||||
// load config table and display it
|
||||
db.get("SELECT CurveId FROM Config", function (err, row) {
|
||||
switch (row.CurveId) {
|
||||
case enum_curveId.CURVE25519:
|
||||
console.log("Open DB "+yargs.database+" using curve c25519");
|
||||
curveId = enum_curveId.CURVE25519;
|
||||
break;
|
||||
case enum_curveId.CURVE448:
|
||||
console.log("Open DB "+yargs.database+" using curve c448");
|
||||
curveId = enum_curveId.CURVE448;
|
||||
break;
|
||||
default:
|
||||
console.log("Open DB "+yargs.database+" but unable to pick or unknow curve in config table"+row.CurveId);
|
||||
process.exit(1);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// close connection, open one for each client connection
|
||||
//db.close();
|
||||
|
||||
function deleteUser(userId) {
|
||||
lock.writeLock(function (release) {
|
||||
db.run("DELETE FROM Users WHERE UserId = ?;", [userId], function(errDelete){
|
||||
release();
|
||||
console.log("Timeout for user "+userId);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// start https server
|
||||
console.log("X3DH server on, listening port "+yargs.port);
|
||||
https.createServer(options, (req, res) => {
|
||||
function returnError(code, errorMessage) {
|
||||
console.log("return an error message code "+code+" : "+errorMessage);
|
||||
var errorBuffer = Buffer.from([X3DH_protocolVersion, enum_messageTypes.error, curveId, code]); // build the X3DH response header, append the error code
|
||||
var errorString = Buffer.from(errorMessage); // encode the errorMessage in UTF8 in a buffer
|
||||
errorBuffer = Buffer.concat([errorBuffer, errorString]);
|
||||
res.writeHead(200, {'Content-type' : 'x3dh/octet-stream'});
|
||||
res.end(errorBuffer);
|
||||
}
|
||||
|
||||
function returnOk(message) {
|
||||
console.log("Ok return a message "+message.toString('hex'));
|
||||
res.writeHead(200, {'Content-type' : 'x3dh/octet-stream'});
|
||||
res.end(message);
|
||||
}
|
||||
|
||||
// TODO: first thing would be to identify and authenticate the request origin against userDB on user server
|
||||
|
||||
// is this someone trying to post something
|
||||
if (req.method=='POST') {
|
||||
var body = Buffer.allocUnsafe(0);
|
||||
req.on('data', function(data) {
|
||||
body = Buffer.concat([body, data]);
|
||||
});
|
||||
req.on('end', function() {
|
||||
// check http header
|
||||
if (req.headers['content-type'] != 'x3dh/octet-stream') {
|
||||
returnError(enum_errorCodes.bad_content_type, "Accept x3dh/octet-stream content type only, not "+req.headers['content-type']);
|
||||
return;
|
||||
}
|
||||
if (!"from" in req.headers) { // from is not a direct property of headers but is inherited, hasOwnProperty would return false
|
||||
returnError(enum_errorCodes.missing_senderId, "From field must be present in http packet header");
|
||||
return;
|
||||
} else {
|
||||
var userId = req.headers['from'];
|
||||
}
|
||||
|
||||
// do we have at least a header to parse?
|
||||
if (body.length<X3DH_headerSize) {
|
||||
returnError(enum_errorCodes.bad_size, "Packet is not even holding a header. Size "+body.length);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check X3DH header
|
||||
// parse message header
|
||||
var protocolVersion = body.readUInt8(0);
|
||||
var messageType = body.readUInt8(1);
|
||||
var message_curveId = body.readUInt8(2);
|
||||
|
||||
if (protocolVersion != X3DH_protocolVersion) {
|
||||
returnError(enum_errorCodes.bad_x3dh_protocol_version, "Server running X3DH procotol version "+X3DH_protocolVersion+". Can't process packet with version "+protocolVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message_curveId != curveId) {
|
||||
returnError(enum_errorCodes.bad_curve, "Server running X3DH procotol using curve "+((curveId==enum_curveId.CURVE25519)?"25519":"448")+"(id "+curveId+"). Can't process serve client using curveId "+message_curveId);
|
||||
return;
|
||||
}
|
||||
|
||||
// connect to DB
|
||||
//let db = new sqlite3.Database(yargs.resource_dir+yargs.database);
|
||||
//db.run("PRAGMA foreign_keys = ON;"); // enable foreign keys
|
||||
|
||||
var returnHeader = Buffer.from([protocolVersion, messageType, message_curveId]); // acknowledge message by sending an empty message with same header (modified in case of getPeerBundle request)
|
||||
switch (messageType) {
|
||||
/* Register User Identity Key : Identity Key <EDDSA Public key size >*/
|
||||
case enum_messageTypes.registerUser:
|
||||
console.log("Got a registerUser Message from "+userId);
|
||||
var x3dh_expectedSize = keySizes[curveId]['ED_pub'];
|
||||
if (body.length<X3DH_headerSize + x3dh_expectedSize) {
|
||||
returnError(enum_errorCodes.bad_size, "Register Identity packet is expexted to be "+(X3DH_headerSize+x3dh_expectedSize)+" bytes, but we got "+body.length+" bytes");
|
||||
return;
|
||||
}
|
||||
var Ik = body.slice(X3DH_headerSize, X3DH_headerSize + x3dh_expectedSize);
|
||||
lock.writeLock(function (release) {
|
||||
// check it is not already present in DB
|
||||
db.get("SELECT Uid FROM Users WHERE UserId = ?;", userId , function (err, row) {
|
||||
if (row != undefined) { // usedId is already present in base
|
||||
release();
|
||||
returnError(enum_errorCodes.user_already_in, "Can't insert user "+userId+" - is already present in base");
|
||||
} else {
|
||||
db.run("INSERT INTO Users(UserId,Ik) VALUES(?,?);", [userId, Ik], function(errInsert){
|
||||
release();
|
||||
if (errInsert == null) {
|
||||
if (yargs.lifetime!=0) {
|
||||
console.log("User inserted will be deleted in "+yargs.lifetime*1000);
|
||||
setTimeout(deleteUser, yargs.lifetime*1000, userId);
|
||||
}
|
||||
returnOk(returnHeader);
|
||||
} else {
|
||||
console.log("INSERT failed err is "); console.log(errInsert);
|
||||
returnError(enum_errorCodes.db_error, "Error while trying to insert user "+userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
/* Delete user: message is empty(or at least shall be, anyway, just ignore anything present in the messange and just delete the user given in From header */
|
||||
case enum_messageTypes.deleteUser:
|
||||
console.log("Got a deleteUser Message from "+userId);
|
||||
lock.writeLock(function (release) {
|
||||
db.run("DELETE FROM Users WHERE UserId = ?;", [userId], function(errDelete){
|
||||
release();
|
||||
if (errDelete == null) {
|
||||
returnOk(returnHeader);
|
||||
} else {
|
||||
console.log("DELETE failed err is "); console.log(errInsert);
|
||||
returnError(enum_errorCodes.db_error, "Error while trying to delete user "+userId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
/* Post Signed Pre Key: Signed Pre Key <ECDH public key size> | SPk Signature <Signature length> | SPk id <uint32_t big endian: 4 bytes> */
|
||||
case enum_messageTypes.postSPk:
|
||||
var x3dh_expectedSize = keySizes[curveId]['X_pub'] + keySizes[curveId]['Sig'] + 4;
|
||||
console.log("Got a postSPk Message from "+userId);
|
||||
if (body.length<X3DH_headerSize + x3dh_expectedSize) {
|
||||
returnError(enum_errorCodes.bad_size, "post SPK packet is expexted to be "+(X3DH_headerSize+x3dh_expectedSize)+" bytes, but we got "+body.length+" bytes");
|
||||
return;
|
||||
}
|
||||
|
||||
// parse message
|
||||
var bufferIndex = X3DH_headerSize;
|
||||
var SPk = body.slice(bufferIndex, bufferIndex + keySizes[curveId]['X_pub']);
|
||||
bufferIndex += keySizes[curveId]['X_pub'];
|
||||
var Sig = body.slice(bufferIndex, bufferIndex + keySizes[curveId]['Sig']);
|
||||
bufferIndex += keySizes[curveId]['Sig'];
|
||||
var SPk_id = body.readUInt32BE(bufferIndex); // SPk id is a 32 bits unsigned integer in Big endian
|
||||
|
||||
// check we have a matching user in DB
|
||||
lock.writeLock(function (release) {
|
||||
db.get("SELECT Uid FROM Users WHERE UserId = ?;", userId , function (err, row) {
|
||||
if (row == undefined) { // user not found in DB
|
||||
release();
|
||||
returnError(enum_errorCodes.user_not_found, "Post SPk but "+userId+" not found in db");
|
||||
} else {
|
||||
var Uid = row['Uid'];
|
||||
|
||||
db.run("UPDATE Users SET SPk = ?, SPk_sig = ?, SPk_id = ? WHERE Uid = ?;", [SPk, Sig, SPk_id, Uid], function(errInsert){
|
||||
release();
|
||||
if (errInsert == null) {
|
||||
returnOk(returnHeader);
|
||||
} else {
|
||||
console.log("INSERT failed err is "); console.log(errInsert);
|
||||
returnError(enum_errorCodes.db_error, "Error while trying to insert SPK for user "+userId+". Backend says :"+errInsert);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
/* Post OPks : OPk number < uint16_t big endian: 2 bytes> | (OPk <ECDH public key size> | OPk id <uint32_t big endian: 4 bytes> ){OPk number} */
|
||||
case enum_messageTypes.postOPks:
|
||||
console.log("Got a postOPks Message from "+userId);
|
||||
// get the OPks number in the first message bytes(unsigned int 16 in big endian)
|
||||
var bufferIndex = X3DH_headerSize;
|
||||
var OPk_number = body.readUInt16BE(bufferIndex);
|
||||
var x3dh_expectedSize = 2 + OPk_number*(keySizes[curveId]['X_pub'] + 4); // read the public key
|
||||
if (body.length<X3DH_headerSize + x3dh_expectedSize) {
|
||||
returnError(enum_errorCodes.bad_size, "post SPK packet is expexted to be "+(X3DH_headerSize+x3dh_expectedSize)+" bytes, but we got "+body.length+" bytes");
|
||||
return;
|
||||
}
|
||||
bufferIndex+=2; // point to the beginning of first key
|
||||
|
||||
console.log("It contains "+OPk_number+" keys");
|
||||
|
||||
// check we have a matching user in DB
|
||||
lock.writeLock(function (release) {
|
||||
db.get("SELECT Uid FROM Users WHERE UserId = ?;", userId , function (err, row) {
|
||||
if (row == undefined) { // user not found in DB
|
||||
release();
|
||||
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++;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
/* peerBundle : bundle Count < 2 bytes unsigned Big Endian> |
|
||||
* ( deviceId Size < 2 bytes unsigned Big Endian > | deviceId
|
||||
* Flag<1 byte: 0 if no OPK in bundle, 1 if present> |
|
||||
* Ik <EDDSA Public Key Length> |
|
||||
* SPk <ECDH Public Key Length> | SPK id <4 bytes>
|
||||
* SPk_sig <Signature Length> |
|
||||
* (OPk <ECDH Public Key Length> | OPk id <4 bytes>){0,1 in accordance to flag}
|
||||
* ) { bundle Count}
|
||||
*/
|
||||
case enum_messageTypes.getPeerBundle:
|
||||
function buildPeerBundlePacket(peersBundle) {
|
||||
let peersBundleBuffer = Buffer.allocUnsafe(X3DH_headerSize+2);
|
||||
peersBundleBuffer.writeUInt8(X3DH_protocolVersion, 0);
|
||||
peersBundleBuffer.writeUInt8(enum_messageTypes.peerBundle, 1);
|
||||
peersBundleBuffer.writeUInt8(curveId, 2);
|
||||
peersBundleBuffer.writeUInt16BE(peersBundle.length, 3); // peers bundle count on 2 bytes in Big Endian
|
||||
|
||||
for (let i=0; i<peersBundle.length; i++) {
|
||||
let userId = peersBundle[i][0];
|
||||
let haveOPk = (peersBundle[i].length>5)?1:0; // elements in peersBundle[i] are : deviceId, Ik, SPk, SPk_id, SPk_sig, [OPk, OPk_id] last 2 are optionnal
|
||||
let peerBundle = Buffer.allocUnsafe(2);
|
||||
peerBundle.writeUInt16BE(userId.length, 0); // peers bundle count on 2 bytes in Big Endian
|
||||
let flagBuffer = Buffer.allocUnsafe(1);
|
||||
flagBuffer.writeUInt8(haveOPk,0);
|
||||
let SPk_idBuffer = Buffer.allocUnsafe(4);
|
||||
SPk_idBuffer.writeUInt32BE(peersBundle[i][3], 0); // SPk id on 4 bytes in Big Endian
|
||||
peerBundle = Buffer.concat([peerBundle, Buffer.from(userId), flagBuffer, peersBundle[i][1], peersBundle[i][2], SPk_idBuffer, peersBundle[i][4]]);
|
||||
if (haveOPk) {
|
||||
let OPk_idBuffer = Buffer.allocUnsafe(4);
|
||||
OPk_idBuffer.writeUInt32BE(peersBundle[i][6], 0); // OPk id on 4 bytes in Big Endian
|
||||
peerBundle = Buffer.concat([peerBundle, peersBundle[i][5], OPk_idBuffer]);
|
||||
}
|
||||
peersBundleBuffer = Buffer.concat([peersBundleBuffer, peerBundle]);
|
||||
}
|
||||
returnOk(peersBundleBuffer);
|
||||
}
|
||||
|
||||
console.log("Got a getPeerBundle Message from "+userId);
|
||||
|
||||
|
||||
// first parse the message
|
||||
var bufferIndex = X3DH_headerSize;
|
||||
var bufferIndex = X3DH_headerSize;
|
||||
var peersCount = body.readUInt16BE(bufferIndex); // 2 bytes BE unsigned int : number of devices uri
|
||||
|
||||
if (peersCount == 0) {
|
||||
returnError(enum_errorCodes.bad_request, "Ask for peer Bundles but no device id given");
|
||||
release();
|
||||
}
|
||||
|
||||
bufferIndex+=2;
|
||||
var peersBundle = [];
|
||||
for (let i=0; i<peersCount; i++) {
|
||||
var IdLength = body.readUInt16BE(bufferIndex); // 2 bytes BE unsigned int : length of the following id string
|
||||
bufferIndex+=2;
|
||||
peersBundle.push([body.toString('utf8', bufferIndex, bufferIndex+IdLength)]); // create element of peersBundle as an array as we will then push the keys on it
|
||||
bufferIndex+=IdLength;
|
||||
}
|
||||
|
||||
console.log("Found request for "+peersCount+" keys bundles");
|
||||
console.dir(peersBundle);
|
||||
|
||||
lock.writeLock(function (release) {
|
||||
console.log("Process a getPeerBundle Message from "+userId);
|
||||
// Try to access all requested values
|
||||
var queries_ran = 0;
|
||||
var queries_success = 0;
|
||||
var queries_err_message = '';
|
||||
|
||||
for (let i=0; i<peersCount; i++) {
|
||||
// left join as we may not have any OPk but shall cope with it
|
||||
db.get("SELECT u.Ik, u.SPk, u.SPk_id, u.SPk_sig, o.OPk, o.OPk_id, o.id FROM Users as u LEFT JOIN OPk as o ON u.Uid=o.Uid WHERE UserId = ? LIMIT 1;", peersBundle[i][0] , function (err, row) {
|
||||
queries_ran++;
|
||||
if (err) {
|
||||
queries_err_message +=' ## get bundle for user '+peersBundle[i]+" error : "+err;
|
||||
return;
|
||||
}
|
||||
if (row == undefined) { // user not found in DB
|
||||
queries_err_message +=' ## bundle not found for user '+peersBundle[i];
|
||||
} else {
|
||||
console.log("Ok push on peersBundle array "+i);
|
||||
console.dir(peersBundle[i]);
|
||||
peersBundle[i].push(row['Ik']);
|
||||
peersBundle[i].push(row['SPk']);
|
||||
peersBundle[i].push(row['SPk_id']);
|
||||
peersBundle[i].push(row['SPk_sig']);
|
||||
if (row['OPk']) {
|
||||
peersBundle[i].push(row['OPk']);
|
||||
peersBundle[i].push(row['OPk_id']);
|
||||
console.log("Ok we retrieved OPk "+row['id']+" from table, we must remove it");
|
||||
//NOTE: in real situation we have to find a way to insure the value wasn't also read by an other request
|
||||
queries_ran--; // this query is not over: step back
|
||||
db.run("DELETE FROM OPk WHERE id = ?;", [row['id']], function(errDelete){
|
||||
queries_ran++;
|
||||
if (errDelete == null) {
|
||||
queries_success++;
|
||||
} else {
|
||||
queries_err_message +=' ## could not delete OPk key retrieved user '+peersBundle[i]+" err : "+errDelete;
|
||||
}
|
||||
|
||||
if (queries_ran == peersCount) {
|
||||
if (queries_ran != queries_success) {
|
||||
returnError(enum_errorCodes.db_error, "Error while trying to get peers bundles :"+queries_err_message);
|
||||
} else {
|
||||
// build the peerBundle Message
|
||||
buildPeerBundlePacket(peersBundle); // this one build and return the buffer
|
||||
}
|
||||
release();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
queries_success++;
|
||||
}
|
||||
}
|
||||
|
||||
if (queries_ran == peersCount) {
|
||||
if (queries_ran != queries_success) {
|
||||
returnError(enum_errorCodes.db_error, "Error while trying to get peers bundles :"+queries_err_message);
|
||||
} else {
|
||||
// build the peerBundle Message
|
||||
buildPeerBundlePacket(peersBundle); // this one build and return the buffer
|
||||
}
|
||||
release();
|
||||
}
|
||||
});
|
||||
}
|
||||
}); // writeLock
|
||||
|
||||
|
||||
break;
|
||||
default:
|
||||
returnError(enum_errorCodes.bad_message_type, "Unknown message type "+messageType);
|
||||
break;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// do nothing we just want POST
|
||||
}
|
||||
}).listen(yargs.port);
|
Loading…
Add table
Add a link
Reference in a new issue