mirror of
https://github.com/valitydev/lechiffre.git
synced 2024-11-06 01:05:23 +00:00
impl thrift (de)serialyze (#1)
This commit is contained in:
parent
a33315e503
commit
083ee3842a
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
log
|
||||
logs
|
||||
/_build/
|
||||
/rebar.lock
|
||||
*~
|
||||
*.beam
|
||||
erl_crash.dump
|
||||
dev.config
|
||||
.tags*
|
||||
*.sublime-workspace
|
||||
.DS_Store
|
||||
docker-compose.yml
|
||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "build_utils"]
|
||||
path = build_utils
|
||||
url = "git@github.com:rbkmoney/build_utils.git"
|
45
Jenkinsfile
vendored
Normal file
45
Jenkinsfile
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
#!groovy
|
||||
// -*- mode: groovy -*-
|
||||
|
||||
def finalHook = {
|
||||
runStage('store CT logs') {
|
||||
archive '_build/test/logs/'
|
||||
}
|
||||
}
|
||||
|
||||
build('lechiffre', 'docker-host', finalHook) {
|
||||
checkoutRepo()
|
||||
loadBuildUtils()
|
||||
|
||||
def pipeDefault
|
||||
def withWsCache
|
||||
runStage('load pipeline') {
|
||||
env.JENKINS_LIB = "build_utils/jenkins_lib"
|
||||
pipeDefault = load("${env.JENKINS_LIB}/pipeDefault.groovy")
|
||||
withWsCache = load("${env.JENKINS_LIB}/withWsCache.groovy")
|
||||
}
|
||||
|
||||
pipeDefault() {
|
||||
if (env.BRANCH_NAME != 'master') {
|
||||
runStage('compile') {
|
||||
withGithubPrivkey {
|
||||
sh 'make wc_compile'
|
||||
}
|
||||
}
|
||||
runStage('lint') {
|
||||
sh 'make wc_lint'
|
||||
}
|
||||
runStage('xref') {
|
||||
sh 'make wc_xref'
|
||||
}
|
||||
runStage('dialyze') {
|
||||
withWsCache("_build/default/rebar3_21.3.8.7_plt") {
|
||||
sh 'make wc_dialyze'
|
||||
}
|
||||
}
|
||||
runStage('test') {
|
||||
sh "make wc_test"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
176
LICENSE
Normal file
176
LICENSE
Normal file
@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
60
Makefile
Normal file
60
Makefile
Normal file
@ -0,0 +1,60 @@
|
||||
REBAR ?= $(shell which rebar3 2>/dev/null || which ./rebar3)
|
||||
SUBMODULES = build_utils
|
||||
SUBTARGETS = $(patsubst %,%/.git,$(SUBMODULES))
|
||||
|
||||
UTILS_PATH := build_utils
|
||||
TEMPLATES_PATH := .
|
||||
|
||||
# Name of the service
|
||||
SERVICE_NAME := lechiffre
|
||||
# Service image default tag
|
||||
SERVICE_IMAGE_TAG ?= $(shell git rev-parse HEAD)
|
||||
# The tag for service image to be pushed with
|
||||
SERVICE_IMAGE_PUSH_TAG ?= $(SERVICE_IMAGE_TAG)
|
||||
|
||||
# Base image for the service
|
||||
BASE_IMAGE_NAME := service-erlang
|
||||
BASE_IMAGE_TAG := da0ab769f01b650b389d18fc85e7418e727cbe96
|
||||
|
||||
BUILD_IMAGE_TAG := 4536c31941b9c27c134e8daf0fd18848809219c9
|
||||
|
||||
CALL_ANYWHERE := \
|
||||
submodules \
|
||||
all compile xref lint dialyze test cover \
|
||||
start devrel release clean distclean
|
||||
|
||||
CALL_W_CONTAINER := $(CALL_ANYWHERE)
|
||||
|
||||
.PHONY: $(CALL_W_CONTAINER) all
|
||||
|
||||
all: compile
|
||||
|
||||
-include $(UTILS_PATH)/make_lib/utils_container.mk
|
||||
-include $(UTILS_PATH)/make_lib/utils_image.mk
|
||||
|
||||
$(SUBTARGETS): %/.git: %
|
||||
git submodule update --init $<
|
||||
touch $@
|
||||
|
||||
submodules: $(SUBTARGETS)
|
||||
|
||||
compile: submodules
|
||||
$(REBAR) compile
|
||||
|
||||
test:
|
||||
$(REBAR) ct
|
||||
|
||||
clean:
|
||||
$(REBAR) clean
|
||||
|
||||
distclean:
|
||||
$(REBAR) clean -a
|
||||
|
||||
xref:
|
||||
$(REBAR) xref
|
||||
|
||||
dialyze:
|
||||
$(REBAR) dialyzer
|
||||
|
||||
lint:
|
||||
elvis rock
|
16
README.md
16
README.md
@ -1 +1,15 @@
|
||||
# lechiffre
|
||||
# Le Chiffre
|
||||
|
||||
|
||||
|
||||
________________ _______________ ________
|
||||
| | | | encrypt | |
|
||||
| VersionedToken | --------> | Thrift Binary | --------> | Binary |
|
||||
| | | | | |
|
||||
---------------- --------------- --------
|
||||
|
||||
________ _______________ ________________
|
||||
| | decrypt | | | |
|
||||
| Binary | ---------> | Thrift Binary | ----------> | VersionedToken |
|
||||
| | | | | |
|
||||
-------- --------------- ----------------
|
||||
|
1
build_utils
Submodule
1
build_utils
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ea4aa042f482551d624fd49a570d28488f479e93
|
88
elvis.config
Normal file
88
elvis.config
Normal file
@ -0,0 +1,88 @@
|
||||
[
|
||||
{elvis, [
|
||||
{config, [
|
||||
#{
|
||||
dirs => ["src"],
|
||||
filter => "*.erl",
|
||||
rules => [
|
||||
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
|
||||
{elvis_style, no_tabs},
|
||||
{elvis_style, no_trailing_whitespace},
|
||||
{elvis_style, macro_module_names},
|
||||
{elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}},
|
||||
{elvis_style, nesting_level, #{level => 3}},
|
||||
{elvis_style, god_modules, #{limit => 25}},
|
||||
{elvis_style, no_if_expression},
|
||||
{elvis_style, invalid_dynamic_call, #{ignore => [elvis]}},
|
||||
{elvis_style, used_ignored_variable},
|
||||
{elvis_style, no_behavior_info},
|
||||
{elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}},
|
||||
{elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}},
|
||||
{elvis_style, state_record_and_type},
|
||||
{elvis_style, no_spec_with_records},
|
||||
{elvis_style, dont_repeat_yourself, #{min_complexity => 10}},
|
||||
{elvis_style, no_debug_call, #{ignore => [elvis, elvis_utils]}}
|
||||
]
|
||||
},
|
||||
#{
|
||||
dirs => ["/test"],
|
||||
filter => "*.erl",
|
||||
rules => [
|
||||
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
|
||||
{elvis_style, no_tabs},
|
||||
{elvis_style, no_trailing_whitespace},
|
||||
{elvis_style, macro_module_names},
|
||||
{elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}},
|
||||
{elvis_style, nesting_level, #{level => 3}},
|
||||
{elvis_style, god_modules, #{limit => 25}},
|
||||
{elvis_style, no_if_expression},
|
||||
{elvis_style, invalid_dynamic_call, #{ignore => [elvis]}},
|
||||
{elvis_style, used_ignored_variable},
|
||||
{elvis_style, no_behavior_info},
|
||||
{elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}},
|
||||
{elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}},
|
||||
{elvis_style, state_record_and_type},
|
||||
{elvis_style, no_spec_with_records},
|
||||
{elvis_style, no_debug_call, #{ignore => [elvis, elvis_utils]}}
|
||||
]
|
||||
},
|
||||
#{
|
||||
dirs => ["."],
|
||||
filter => "Makefile",
|
||||
ruleset => makefiles
|
||||
},
|
||||
#{
|
||||
dirs => ["."],
|
||||
filter => "elvis.config",
|
||||
ruleset => elvis_config
|
||||
},
|
||||
#{
|
||||
dirs => ["."],
|
||||
filter => "rebar.config",
|
||||
rules => [
|
||||
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
|
||||
{elvis_style, no_tabs},
|
||||
{elvis_style, no_trailing_whitespace}
|
||||
]
|
||||
},
|
||||
#{
|
||||
dirs => ["."],
|
||||
filter => "rebar.config",
|
||||
rules => [
|
||||
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
|
||||
{elvis_style, no_tabs},
|
||||
{elvis_style, no_trailing_whitespace}
|
||||
]
|
||||
},
|
||||
#{
|
||||
dirs => ["src"],
|
||||
filter => "*.app.src",
|
||||
rules => [
|
||||
{elvis_style, line_length, #{limit => 120, skip_comments => false}},
|
||||
{elvis_style, no_tabs},
|
||||
{elvis_style, no_trailing_whitespace}
|
||||
]
|
||||
}
|
||||
]}
|
||||
]}
|
||||
].
|
46
rebar.config
Normal file
46
rebar.config
Normal file
@ -0,0 +1,46 @@
|
||||
{erl_opts, [
|
||||
|
||||
% mandatory
|
||||
debug_info,
|
||||
warnings_as_errors,
|
||||
warn_export_all,
|
||||
warn_missing_spec,
|
||||
warn_untyped_record,
|
||||
warn_export_vars,
|
||||
|
||||
% by default
|
||||
warn_unused_record,
|
||||
warn_bif_clash,
|
||||
warn_obsolete_guard,
|
||||
warn_unused_vars,
|
||||
warn_shadow_vars,
|
||||
warn_unused_import,
|
||||
warn_unused_function,
|
||||
warn_deprecated_function,
|
||||
|
||||
% at will
|
||||
% bin_opt_info
|
||||
% no_auto_import
|
||||
warn_missing_spec_all
|
||||
]}.
|
||||
|
||||
{deps, [
|
||||
{genlib, {git, "https://github.com/rbkmoney/genlib.git", {branch, "master"}}},
|
||||
{thrift, {git, "https://github.com/rbkmoney/thrift_erlang.git", {branch, "master"}}}
|
||||
]}.
|
||||
|
||||
{xref_checks, [
|
||||
undefined_function_calls,
|
||||
undefined_functions,
|
||||
deprecated_functions_calls,
|
||||
deprecated_functions
|
||||
]}.
|
||||
|
||||
{dialyzer, [
|
||||
{warnings, [
|
||||
unmatched_returns,
|
||||
error_handling,
|
||||
race_conditions,
|
||||
unknown
|
||||
]}
|
||||
]}.
|
11
src/lechiffre.app.src
Normal file
11
src/lechiffre.app.src
Normal file
@ -0,0 +1,11 @@
|
||||
{application, lechiffre, [
|
||||
{description, "Encrypt/decrypt data, described in thrift protocol"},
|
||||
{vsn, "0.1"},
|
||||
{registered, []},
|
||||
{applications, [
|
||||
kernel,
|
||||
stdlib,
|
||||
thrift,
|
||||
genlib
|
||||
]}
|
||||
]}.
|
203
src/lechiffre.erl
Normal file
203
src/lechiffre.erl
Normal file
@ -0,0 +1,203 @@
|
||||
-module(lechiffre).
|
||||
|
||||
-define(SECRET_KEYS_TABLE, ?MODULE).
|
||||
|
||||
-behaviour(gen_server).
|
||||
|
||||
-type options() :: #{
|
||||
encryption_key_path := {key_version(), key_path()},
|
||||
decryption_key_path := #{
|
||||
key_version() := key_path()
|
||||
}
|
||||
}.
|
||||
|
||||
-type key_path() :: binary().
|
||||
-type key_version() :: lechiffre_crypto:key_version().
|
||||
-type secret_keys() :: lechiffre_crypto:secret_keys().
|
||||
-type data() :: term().
|
||||
-type encoded_data() :: binary().
|
||||
|
||||
-type encoding_error() :: {encryption_failed, lechiffre_crypto:encryption_error()} |
|
||||
lechiffre_thrift_utils:thrift_error().
|
||||
|
||||
-type decoding_error() :: {decryption_failed, lechiffre_crypto:decryption_error()} |
|
||||
lechiffre_thrift_utils:thrift_error().
|
||||
|
||||
-type thrift_type() :: lechiffre_thrift_utils:thrift_type().
|
||||
|
||||
-export_type([secret_keys/0]).
|
||||
-export_type([encoding_error/0]).
|
||||
-export_type([decoding_error/0]).
|
||||
|
||||
%% GenServer
|
||||
-export([child_spec/2]).
|
||||
-export([start_link/1]).
|
||||
-export([init /1]).
|
||||
-export([handle_call/3]).
|
||||
-export([handle_cast/2]).
|
||||
-export([handle_info/2]).
|
||||
-export([terminate /2]).
|
||||
-export([code_change/3]).
|
||||
|
||||
-export([encode/2]).
|
||||
-export([encode/3]).
|
||||
-export([decode/2]).
|
||||
-export([decode/3]).
|
||||
|
||||
-spec child_spec(atom(), options()) ->
|
||||
supervisor:child_spec().
|
||||
|
||||
child_spec(ChildId, Options) ->
|
||||
#{
|
||||
id => ChildId,
|
||||
start => {?MODULE, start_link, [Options]},
|
||||
type => worker,
|
||||
restart => permanent
|
||||
}.
|
||||
|
||||
-spec start_link(options()) ->
|
||||
{ok, pid()}.
|
||||
|
||||
start_link(Options) ->
|
||||
gen_server:start_link(?MODULE, Options, []).
|
||||
|
||||
-spec encode(thrift_type(), data()) ->
|
||||
{ok, encoded_data()} |
|
||||
{error, encoding_error()}.
|
||||
|
||||
encode(ThriftType, Data) ->
|
||||
SecretKeys = lookup_secret_value(),
|
||||
case lechiffre_thrift_utils:serialize(ThriftType, Data) of
|
||||
{ok, ThriftBin} ->
|
||||
lechiffre_crypto:encrypt(SecretKeys, ThriftBin);
|
||||
{error, _} = Error ->
|
||||
{error, {serialization_failed, Error}}
|
||||
end.
|
||||
|
||||
-spec encode(thrift_type(), data(), secret_keys()) ->
|
||||
{ok, encoded_data()} |
|
||||
{error, encoding_error()}.
|
||||
|
||||
encode(ThriftType, Data, SecretKeys) ->
|
||||
case lechiffre_thrift_utils:serialize(ThriftType, Data) of
|
||||
{ok, ThriftBin} ->
|
||||
lechiffre_crypto:encrypt(SecretKeys, ThriftBin);
|
||||
{error, _} = Error ->
|
||||
{error, {serialization_failed, Error}}
|
||||
end.
|
||||
|
||||
-spec decode(thrift_type(), encoded_data()) ->
|
||||
{ok, data()} |
|
||||
{error, decoding_error()}.
|
||||
|
||||
decode(ThriftType, EncryptedData) ->
|
||||
SecretKeys = lookup_secret_value(),
|
||||
case lechiffre_crypto:decrypt(SecretKeys, EncryptedData) of
|
||||
{ok, ThriftBin} ->
|
||||
lechiffre_thrift_utils:deserialize(ThriftType, ThriftBin);
|
||||
DecryptError ->
|
||||
DecryptError
|
||||
end.
|
||||
|
||||
-spec decode(thrift_type(), encoded_data(), secret_keys()) ->
|
||||
{ok, data()} |
|
||||
{error, decoding_error()}.
|
||||
|
||||
decode(ThriftType, EncryptedData, SecretKeys) ->
|
||||
case lechiffre_crypto:decrypt(SecretKeys, EncryptedData) of
|
||||
{ok, ThriftBin} ->
|
||||
lechiffre_thrift_utils:deserialize(ThriftType, ThriftBin);
|
||||
DecryptError ->
|
||||
DecryptError
|
||||
end.
|
||||
|
||||
%% Supervisor
|
||||
|
||||
-type st() :: #{
|
||||
options => options()
|
||||
}.
|
||||
|
||||
-spec init(options()) ->
|
||||
{ok, st()}.
|
||||
|
||||
init(Options) ->
|
||||
SecretKeys = read_secret_keys(Options),
|
||||
ok = create_table(SecretKeys),
|
||||
{ok, #{options => Options}}.
|
||||
|
||||
-spec handle_call(term(), term(), st()) ->
|
||||
{reply, term(), st()} | {noreply, st()}.
|
||||
|
||||
handle_call(Call, _From, State) ->
|
||||
_ = logger:warning("unexpected call received: ~tp", [Call]),
|
||||
{noreply, State}.
|
||||
|
||||
-spec handle_cast(_, st()) ->
|
||||
{noreply, st()}.
|
||||
|
||||
handle_cast(Cast, State) ->
|
||||
_ = logger:warning("unexpected cast received: ~tp", [Cast]),
|
||||
{noreply, State}.
|
||||
|
||||
-spec handle_info(_, st()) ->
|
||||
{noreply, st()}.
|
||||
|
||||
handle_info(Info, State) ->
|
||||
_ = logger:warning("unexpected info received: ~tp", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
-spec terminate(Reason, atom()) ->
|
||||
ok when
|
||||
Reason :: normal | shutdown | {shutdown, term()} | term().
|
||||
|
||||
terminate(_Reason, _State) ->
|
||||
ok.
|
||||
|
||||
-spec code_change(term(), term(), term()) -> {ok, atom()}.
|
||||
|
||||
code_change(_OldVsn, State, _Extra) ->
|
||||
{ok, State}.
|
||||
|
||||
%%
|
||||
|
||||
-spec read_secret_keys(options()) -> secret_keys().
|
||||
|
||||
read_secret_keys(Options) ->
|
||||
{Ver, EncryptionPath} = maps:get(encryption_key_path, Options),
|
||||
DecryptionKeysPath = maps:get(decryption_key_path, Options),
|
||||
DecryptionKeys = maps:fold(fun(KeyVer, Path, Acc) ->
|
||||
SecretKey = read_key_file(Path),
|
||||
Acc#{
|
||||
KeyVer => SecretKey
|
||||
}
|
||||
end, #{}, DecryptionKeysPath),
|
||||
EncryptionKey = read_key_file(EncryptionPath),
|
||||
#{
|
||||
encryption_key => {Ver, EncryptionKey},
|
||||
decryption_key => DecryptionKeys
|
||||
}.
|
||||
|
||||
-spec read_key_file(binary()) -> binary().
|
||||
|
||||
read_key_file(SecretPath) ->
|
||||
{ok, Secret} = file:read_file(SecretPath),
|
||||
genlib_string:trim(Secret).
|
||||
|
||||
-spec create_table(secret_keys()) -> ok.
|
||||
|
||||
create_table(SecretKeys) ->
|
||||
_ = ets:new(?SECRET_KEYS_TABLE, [set, public, named_table, {read_concurrency, true}]),
|
||||
insert_secret_value(SecretKeys),
|
||||
ok.
|
||||
|
||||
-spec insert_secret_value(secret_keys()) -> ok.
|
||||
|
||||
insert_secret_value(SecretKeys) ->
|
||||
true = ets:insert(?SECRET_KEYS_TABLE, [{secret, SecretKeys}]),
|
||||
ok.
|
||||
|
||||
-spec lookup_secret_value() -> secret_keys().
|
||||
|
||||
lookup_secret_value() ->
|
||||
[{secret, SecretKeys}] = ets:lookup(?SECRET_KEYS_TABLE, secret),
|
||||
SecretKeys.
|
142
src/lechiffre_crypto.erl
Normal file
142
src/lechiffre_crypto.erl
Normal file
@ -0,0 +1,142 @@
|
||||
-module(lechiffre_crypto).
|
||||
|
||||
-define(MAX_UINT_32, 4294967295).
|
||||
|
||||
-type key_version() :: 1..?MAX_UINT_32.
|
||||
-type key() :: <<_:256>>.
|
||||
-type decryption_keys() :: #{
|
||||
key_version() := key()
|
||||
}.
|
||||
-type secret_keys() :: #{
|
||||
encryption_key := {key_version(), key()},
|
||||
decryption_key := decryption_keys()
|
||||
}.
|
||||
|
||||
-type iv() :: binary().
|
||||
-type tag() :: binary().
|
||||
-type aad() :: binary().
|
||||
|
||||
%% Encrypted Data Format
|
||||
-record(edf, {
|
||||
version :: binary(),
|
||||
tag :: tag(),
|
||||
iv :: iv(),
|
||||
aad :: aad(),
|
||||
cipher :: binary(),
|
||||
key_version :: key_version()
|
||||
}).
|
||||
-type edf() :: #edf{}.
|
||||
|
||||
-type decryption_error() :: {decryption_failed, decryption_validation_failed |
|
||||
bad_encrypted_data_format |
|
||||
wrong_data_type |
|
||||
{unknown_key_version, key_version()}
|
||||
}.
|
||||
-type encryption_error() :: {encryption_failed, wrong_data_type}.
|
||||
|
||||
-export_type([decryption_keys/0]).
|
||||
-export_type([encryption_error/0]).
|
||||
-export_type([decryption_error/0]).
|
||||
-export_type([secret_keys/0]).
|
||||
-export_type([key_version/0]).
|
||||
|
||||
-export([encrypt/2]).
|
||||
-export([decrypt/2]).
|
||||
|
||||
-spec encrypt(secret_keys(), binary()) ->
|
||||
{ok, binary()} |
|
||||
{error, {encryption_failed, wrong_data_type}}.
|
||||
|
||||
encrypt(#{encryption_key := {KeyVer, Key}}, Plain) ->
|
||||
IV = iv(),
|
||||
AAD = aad(),
|
||||
Version = <<"edf_v1">>,
|
||||
try
|
||||
{Cipher, Tag} = crypto:block_encrypt(aes_gcm, Key, IV, {AAD, Plain}),
|
||||
EncryptedData = marshal_edf(#edf{
|
||||
version = Version,
|
||||
key_version = KeyVer,
|
||||
iv = IV,
|
||||
aad = AAD,
|
||||
cipher = Cipher,
|
||||
tag = Tag}),
|
||||
{ok, EncryptedData}
|
||||
catch error:badarg ->
|
||||
{error, {encryption_failed, wrong_data_type}}
|
||||
end.
|
||||
|
||||
-spec decrypt(secret_keys(), binary()) ->
|
||||
{ok, binary()} |
|
||||
{error, decryption_error()}.
|
||||
|
||||
decrypt(SecretKeys, MarshalledEDF) ->
|
||||
try
|
||||
#edf{
|
||||
iv = IV,
|
||||
aad = AAD,
|
||||
cipher = Cipher,
|
||||
tag = Tag,
|
||||
key_version = KeyVer} = unmarshal_edf(MarshalledEDF),
|
||||
Key = get_key(KeyVer, SecretKeys),
|
||||
crypto:block_decrypt(aes_gcm, Key, IV, {AAD, Cipher, Tag})
|
||||
of
|
||||
error ->
|
||||
{error, {decryption_failed, decryption_validation_failed}};
|
||||
Plain ->
|
||||
{ok, Plain}
|
||||
catch
|
||||
throw:bad_encrypted_data_format ->
|
||||
{error, {decryption_failed, bad_encrypted_data_format}};
|
||||
throw:{unknown_key_version, Ver} ->
|
||||
{error, {decryption_failed, {unknown_key_version, Ver}}};
|
||||
error:badarg ->
|
||||
{error, {decryption_failed, wrong_data_type}}
|
||||
end.
|
||||
|
||||
%%% Internal functions
|
||||
|
||||
-spec get_key(key_version(), secret_keys()) -> key().
|
||||
|
||||
get_key(KeyVer, #{decryption_key := Keys}) ->
|
||||
case maps:find(KeyVer, Keys) of
|
||||
{ok, Key} ->
|
||||
Key;
|
||||
error ->
|
||||
throw({unknown_key_version, KeyVer})
|
||||
end.
|
||||
|
||||
-spec iv() -> iv().
|
||||
|
||||
iv() ->
|
||||
crypto:strong_rand_bytes(16).
|
||||
|
||||
-spec aad() -> aad().
|
||||
|
||||
aad() ->
|
||||
crypto:strong_rand_bytes(4).
|
||||
|
||||
-spec marshal_edf(edf()) -> binary().
|
||||
|
||||
marshal_edf(#edf{version = Ver, key_version = KeyVer, tag = Tag, iv = IV, aad = AAD, cipher = Cipher})
|
||||
when
|
||||
KeyVer > 0 andalso KeyVer < ?MAX_UINT_32, %% max value unsinged integer 4 byte
|
||||
bit_size(Tag) =:= 128,
|
||||
bit_size(IV) =:= 128,
|
||||
bit_size(AAD) =:= 32
|
||||
->
|
||||
<<Ver:6/binary, KeyVer:32/integer, Tag:16/binary, IV:16/binary, AAD:4/binary, Cipher/binary>>.
|
||||
|
||||
-spec unmarshal_edf(binary()) -> edf().
|
||||
|
||||
unmarshal_edf(<<Ver:6/binary, KeyVer:32/integer, Tag:16/binary, IV:16/binary, AAD:4/binary, Cipher/binary>>)
|
||||
when Ver =:= <<"edf_v1">> ->
|
||||
#edf{
|
||||
version = <<"edf_v1">>,
|
||||
tag = Tag,
|
||||
iv = IV,
|
||||
aad = AAD,
|
||||
cipher = Cipher,
|
||||
key_version = KeyVer
|
||||
};
|
||||
unmarshal_edf(_Other) ->
|
||||
throw(bad_encrypted_data_format).
|
86
src/lechiffre_thrift_utils.erl
Normal file
86
src/lechiffre_thrift_utils.erl
Normal file
@ -0,0 +1,86 @@
|
||||
-module(lechiffre_thrift_utils).
|
||||
|
||||
-export([serialize/2]).
|
||||
-export([deserialize/2]).
|
||||
|
||||
%% Types
|
||||
|
||||
-type thrift_type() ::
|
||||
thrift_base_type() |
|
||||
thrift_collection_type() |
|
||||
thrift_enum_type() |
|
||||
thrift_struct_type().
|
||||
|
||||
-type thrift_base_type() ::
|
||||
bool |
|
||||
double |
|
||||
i8 |
|
||||
i16 |
|
||||
i32 |
|
||||
i64 |
|
||||
string.
|
||||
|
||||
-type thrift_collection_type() ::
|
||||
{list, thrift_type()} |
|
||||
{set, thrift_type()} |
|
||||
{map, thrift_type(), thrift_type()}.
|
||||
|
||||
-type thrift_enum_type() ::
|
||||
{enum, thrift_type_ref()}.
|
||||
|
||||
-type thrift_struct_type() ::
|
||||
{struct, thrift_struct_flavor(), thrift_type_ref() | thrift_struct_def()}.
|
||||
|
||||
-type thrift_struct_flavor() :: struct | union | exception.
|
||||
|
||||
-type thrift_type_ref() :: {module(), Name :: atom()}.
|
||||
|
||||
-type thrift_struct_def() :: list({
|
||||
Tag :: pos_integer(),
|
||||
Requireness :: required | optional | undefined,
|
||||
Type :: thrift_struct_type(),
|
||||
Name :: atom(),
|
||||
Default :: any()
|
||||
}).
|
||||
|
||||
-type thrift_error() :: {serialization_failed, {thrift_protocol, any()}} |
|
||||
{deserialization_failed, {thrift_protocol, any()}}.
|
||||
|
||||
-export_type([thrift_type/0]).
|
||||
-export_type([thrift_error/0]).
|
||||
|
||||
%% API
|
||||
|
||||
-spec serialize(thrift_type(), term()) ->
|
||||
{ok, binary()} | {error, thrift_error()}.
|
||||
|
||||
serialize(Type, Data) ->
|
||||
{ok, Transport} = thrift_membuffer_transport:new(),
|
||||
{ok, Proto} = new_protocol(Transport),
|
||||
case thrift_protocol:write(Proto, {Type, Data}) of
|
||||
{NewProto, ok} ->
|
||||
{_, {ok, Result}} = thrift_protocol:close_transport(NewProto),
|
||||
{ok, Result};
|
||||
{_NewProto, {error, {thrift, {protocol, Reason}}}} ->
|
||||
{error, {serialization_failed, {thrift_protocol, Reason}}}
|
||||
end.
|
||||
|
||||
-spec deserialize(thrift_type(), binary()) ->
|
||||
{ok, term()} | {error, thrift_error()}.
|
||||
|
||||
deserialize(Type, Data) ->
|
||||
{ok, Transport} = thrift_membuffer_transport:new(Data),
|
||||
{ok, Proto} = new_protocol(Transport),
|
||||
case thrift_protocol:read(Proto, Type) of
|
||||
{_NewProto, {ok, Result}} ->
|
||||
{ok, Result};
|
||||
{_NewProto, {error, {thrift, {protocol, Reason}}}} ->
|
||||
{error, {deserialization_failed, {thrift_protocol, Reason}}}
|
||||
end.
|
||||
|
||||
%% Internals
|
||||
|
||||
-spec new_protocol(any()) -> term().
|
||||
|
||||
new_protocol(Transport) ->
|
||||
thrift_binary_protocol:new(Transport, [{strict_read, true}, {strict_write, true}]).
|
186
test/lechiffre_tests_SUITE.erl
Normal file
186
test/lechiffre_tests_SUITE.erl
Normal file
@ -0,0 +1,186 @@
|
||||
-module(lechiffre_tests_SUITE).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-record('BankCard', {
|
||||
token :: binary()
|
||||
}).
|
||||
|
||||
-export([struct_info/1]).
|
||||
-export([record_name/1]).
|
||||
|
||||
-export([all/0]).
|
||||
-export([groups/0]).
|
||||
-export([init_per_suite/1]).
|
||||
-export([end_per_suite/1]).
|
||||
-export([init_per_testcase/2]).
|
||||
-export([end_per_testcase/2]).
|
||||
|
||||
-export([test/0]).
|
||||
|
||||
-export([
|
||||
unknown_decrypt_key_test/1,
|
||||
wrong_key_test/1,
|
||||
wrong_encrypted_key_format_test/1,
|
||||
encrypt_hide_secret_key_ok_test/1
|
||||
]).
|
||||
|
||||
-type config() :: [{atom(), term()}].
|
||||
|
||||
-spec all() ->
|
||||
[atom()].
|
||||
|
||||
all() ->
|
||||
[
|
||||
unknown_decrypt_key_test,
|
||||
wrong_key_test,
|
||||
wrong_encrypted_key_format_test,
|
||||
encrypt_hide_secret_key_ok_test
|
||||
].
|
||||
|
||||
-spec groups() ->
|
||||
list().
|
||||
|
||||
groups() ->
|
||||
[].
|
||||
|
||||
-spec test() ->
|
||||
any().
|
||||
|
||||
test() ->
|
||||
ok.
|
||||
|
||||
-spec init_per_suite(config()) ->
|
||||
config().
|
||||
|
||||
init_per_suite(Config) ->
|
||||
Config.
|
||||
|
||||
-spec end_per_suite(config()) ->
|
||||
ok.
|
||||
|
||||
end_per_suite(_C) ->
|
||||
ok.
|
||||
|
||||
-spec init_per_testcase(atom(), config()) ->
|
||||
config().
|
||||
|
||||
init_per_testcase(_Name, C) ->
|
||||
C.
|
||||
|
||||
-spec end_per_testcase(atom(), config()) ->
|
||||
config().
|
||||
|
||||
end_per_testcase(_Name, _C) ->
|
||||
ok.
|
||||
|
||||
-spec get_source(binary(), config()) ->
|
||||
binary().
|
||||
|
||||
get_source(FileName, Config) ->
|
||||
filename:join(?config(data_dir, Config), FileName).
|
||||
|
||||
%% TESTS
|
||||
|
||||
-spec encrypt_hide_secret_key_ok_test(config()) -> ok.
|
||||
-spec unknown_decrypt_key_test(config()) -> ok.
|
||||
-spec wrong_key_test(config()) -> ok.
|
||||
-spec wrong_encrypted_key_format_test(config()) -> ok.
|
||||
|
||||
encrypt_hide_secret_key_ok_test(Config) ->
|
||||
Filename = <<"secret_key_1.file">>,
|
||||
Options = #{
|
||||
encryption_key_path => {1, get_source(Filename, Config)},
|
||||
decryption_key_path => #{
|
||||
1 => get_source(Filename, Config)
|
||||
}
|
||||
},
|
||||
lechiffre:start_link(Options),
|
||||
{ThriftType, PaymentToolToken} = payment_tool_token(),
|
||||
|
||||
{ok, EncryptedToken} = lechiffre:encode(ThriftType, PaymentToolToken),
|
||||
{ok, Value} = lechiffre:decode(ThriftType, EncryptedToken),
|
||||
?assertEqual(PaymentToolToken, Value).
|
||||
|
||||
unknown_decrypt_key_test(_Config) ->
|
||||
{ThriftType, PaymentToolToken} = payment_tool_token(),
|
||||
Key = crypto:strong_rand_bytes(32),
|
||||
SecretKey = #{
|
||||
encryption_key => {1, Key},
|
||||
decryption_key => #{2 => Key}
|
||||
},
|
||||
{ok, EncryptedToken} = lechiffre:encode(ThriftType, PaymentToolToken, SecretKey),
|
||||
ErrorDecode = lechiffre:decode(ThriftType, EncryptedToken, SecretKey),
|
||||
?assertEqual({error, {decryption_failed, {unknown_key_version, 1}}}, ErrorDecode).
|
||||
|
||||
wrong_key_test(_Config) ->
|
||||
{ThriftType, PaymentToolToken} = payment_tool_token(),
|
||||
Key = crypto:strong_rand_bytes(32),
|
||||
WrongKey = crypto:strong_rand_bytes(32),
|
||||
SecretKey = #{
|
||||
encryption_key => {1, Key},
|
||||
decryption_key => #{1 => WrongKey}
|
||||
},
|
||||
{ok, EncryptedToken} = lechiffre:encode(ThriftType, PaymentToolToken, SecretKey),
|
||||
ErrorDecode = lechiffre:decode(ThriftType, EncryptedToken, SecretKey),
|
||||
?assertEqual({error, {decryption_failed, decryption_validation_failed}}, ErrorDecode).
|
||||
|
||||
wrong_encrypted_key_format_test(_Config) ->
|
||||
{ThriftType, PaymentToolToken} = payment_tool_token(),
|
||||
Key = crypto:strong_rand_bytes(32),
|
||||
WrongKey = crypto:strong_rand_bytes(32),
|
||||
SecretKey = #{
|
||||
encryption_key => {1, Key},
|
||||
decryption_key => #{1 => WrongKey}
|
||||
},
|
||||
{ok, EncryptedToken} = lechiffre:encode(ThriftType, PaymentToolToken, SecretKey),
|
||||
<<_Format:6/binary, Tail/binary>> = EncryptedToken,
|
||||
BadEncryptedToken = <<"edf_v2", Tail/binary>>,
|
||||
ErrorDecode = lechiffre:decode(ThriftType, BadEncryptedToken, SecretKey),
|
||||
?assertEqual({error, {decryption_failed, bad_encrypted_data_format}}, ErrorDecode).
|
||||
|
||||
-spec payment_tool_token() -> {term(), term()}.
|
||||
payment_tool_token() ->
|
||||
Type = {struct, struct, {?MODULE, 'BankCard'}},
|
||||
Token = #'BankCard'{
|
||||
token = <<"TOKEN">>
|
||||
},
|
||||
{Type, Token}.
|
||||
|
||||
%% For Thrift compile
|
||||
|
||||
-type struct_flavour() :: struct | exception | union.
|
||||
-type field_num() :: pos_integer().
|
||||
-type field_name() :: atom().
|
||||
-type field_req() :: required | optional | undefined.
|
||||
|
||||
-type type_ref() :: {module(), atom()}.
|
||||
-type field_type() ::
|
||||
bool | byte | i16 | i32 | i64 | string | double |
|
||||
{enum, type_ref()} |
|
||||
{struct, struct_flavour(), type_ref()} |
|
||||
{list, field_type()} |
|
||||
{set, field_type()} |
|
||||
{map, field_type(), field_type()}.
|
||||
|
||||
-type struct_field_info() ::
|
||||
{field_num(), field_req(), field_type(), field_name(), any()}.
|
||||
-type struct_info() ::
|
||||
{struct, struct_flavour(), [struct_field_info()]}.
|
||||
|
||||
-type struct_name() ::
|
||||
'BankCard'.
|
||||
|
||||
-spec struct_info(struct_name()) -> struct_info() | no_return().
|
||||
|
||||
struct_info('BankCard') ->
|
||||
{struct, struct, [
|
||||
{1, required, string, 'token', undefined}
|
||||
]};
|
||||
struct_info(_) -> erlang:error(badarg).
|
||||
|
||||
-spec record_name(struct_name()) -> atom() | no_return().
|
||||
|
||||
record_name('BankCard') ->
|
||||
'BankCard'.
|
1
test/lechiffre_tests_SUITE_data/secret_key_1.file
Normal file
1
test/lechiffre_tests_SUITE_data/secret_key_1.file
Normal file
@ -0,0 +1 @@
|
||||
1b3a4781d2dbb4d5ddf86efa1a6707c1
|
Loading…
Reference in New Issue
Block a user