Initial Commit

This commit is contained in:
Johan Pascal 2017-11-11 00:54:52 +07:00
commit 8390564a37
47 changed files with 8243 additions and 0 deletions

32
.gitignore vendored Normal file
View 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
View file

@ -0,0 +1,3 @@
Johan Pascal
Belledonne Communications SARL <info@belledonne-communications.com>

169
CMakeLists.txt Normal file
View 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
View 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
View file

2
NEWS Normal file
View file

@ -0,0 +1,2 @@
lime-0.0.1 - October 22nd 2017
* Initial release

45
README.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 */

View 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

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View 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
View 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
View 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-----

View 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

View 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
View 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
View 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

View 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

File diff suppressed because it is too large Load diff

1
tester/server/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules*

60
tester/server/README.md Normal file
View 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

View 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

View 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"
}

View 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-----

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