Skip to content

Commit 420d893

Browse files
authored
Merge pull request #218 from pavestru/208-integrity-check-correlation-id
208 Integrity-check for the correlation ID
2 parents 28c7145 + fc5e4d1 commit 420d893

File tree

5 files changed

+133
-9
lines changed

5 files changed

+133
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- [API Gateway] New custom metric: `rig_proxy_requests_total`. For details see [`metrics-details.md`](docs/metrics-details.md). [#157](https://github.com/Accenture/reactive-interaction-gateway/issues/157)
13+
- [Rig] Integrity-check for the correlation ID [#218](https://github.com/Accenture/reactive-interaction-gateway/pull/218)
1314
- _Beta_ - Added Apache Avro support for consumer and producer as well as Kafka Schema Registry.
1415
- [Docs] Added new set of topics in documentation about Api Gateway, even streams and scaling.
1516
- [Docs] Added examples section to documentation website.

apps/rig/config/config.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ config :rig, Rig.EventStream.KafkaToHttp,
5858
targets: {:system, :list, "FIREHOSE_KAFKA_HTTP_TARGETS", []},
5959
group_id: {:system, "KAFKATOHTTP_KAFKA_GROUP_ID", "rig-kafka-to-http"}
6060

61+
config :rig, Rig.Connection.Codec,
62+
codec_secret_key: {:system, "NODE_COOKIE", nil},
63+
codec_default_key: "7tsf4Y6GTOfPY1iDo4PqZA=="
64+
6165
config :porcelain, driver: Porcelain.Driver.Basic
6266

6367
import_config "#{Mix.env()}.exs"

apps/rig/lib/rig/connection/codec.ex

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
11
defmodule Rig.Connection.Codec do
22
@moduledoc """
33
Encode and decode a connection token, e.g., for correlation.
4-
5-
TODO: sign the token - using binary_to_term without signature verification is a threat.
64
"""
5+
use Rig.Config, [:codec_secret_key]
76

87
@doc "Turn a pid into an url-encoded string."
98
@spec serialize(pid) :: binary
109
def serialize(pid) do
10+
conf = config()
11+
secret_key = conf.codec_secret_key || conf.codec_default_key
1112
pid
1213
|> :erlang.term_to_binary()
14+
|> encrypt(secret_key)
1315
|> Base.url_encode64()
14-
15-
# TODO sign this
1616
end
1717

1818
# ---
1919

2020
@doc "Convert a serialized string back into a pid."
2121
@spec deserialize(binary) :: {:ok, pid} | {:error, :not_base64 | :invalid_term}
2222
def deserialize(base64_encoded) do
23-
# TODO check signature here
24-
23+
conf = config()
24+
secret_key = conf.codec_secret_key || conf.codec_default_key
2525
with {:ok, decoded_binary} <- decode64(base64_encoded) do
26-
binary_to_term(decoded_binary)
26+
decoded_binary
27+
|> decrypt(secret_key)
28+
|> binary_to_term()
2729
end
2830
end
2931

@@ -38,6 +40,46 @@ defmodule Rig.Connection.Codec do
3840

3941
# ---
4042

43+
@doc """
44+
The function processes any string or number and returns exactly 16 byte binary.
45+
The hash produced by this function can be used as key by both encrypt and decrypt functions.
46+
"""
47+
@spec hash(binary) :: binary
48+
def hash(key) do
49+
:crypto.hash(:md5, :erlang.term_to_binary(key))
50+
end
51+
52+
@doc """
53+
Encrypts value using key and returns init. vector, ciphertag (MAC) and ciphertext concatenated.
54+
Additional authenticated data (AAD) adds parameters like protocol version num. to MAC
55+
"""
56+
@aad "AES256GCM"
57+
@spec encrypt(binary, binary) :: binary
58+
def encrypt(val, key) do
59+
mode = :aes_gcm
60+
secret_key = hash(key)
61+
init_vector = :crypto.strong_rand_bytes(16)
62+
{ciphertext, ciphertag} =
63+
:crypto.block_encrypt(mode, secret_key, init_vector, {@aad, to_string(val), 16})
64+
init_vector <> ciphertag <> ciphertext
65+
end
66+
67+
# ---
68+
69+
@doc """
70+
Decrypts ciphertext using key.
71+
Ciphertext is init. vector, ciphertag (MAC) and the actual ciphertext concatenated.
72+
"""
73+
@spec decrypt(binary, binary) :: binary
74+
def decrypt(ciphertext, key) do
75+
mode = :aes_gcm
76+
secret_key = hash(key)
77+
<<init_vector::binary-16, tag::binary-16, ciphertext::binary>> = ciphertext
78+
:crypto.block_decrypt(mode, secret_key, init_vector, {@aad, ciphertext, tag})
79+
end
80+
81+
# ---
82+
4183
@spec decode64(binary()) :: {:ok, binary()} | {:error, :not_base64}
4284
defp decode64(base64) do
4385
case Base.url_decode64(base64) do

apps/rig/lib/rig/connection/codec_test.exs

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule Rig.ConnectionCodecTest do
55

66
use ExUnitProperties
77

8-
import Rig.Connection.Codec, only: [serialize: 1, deserialize!: 1]
8+
import Rig.Connection.Codec, only: [serialize: 1, deserialize!: 1, encrypt: 2, decrypt: 2]
99

1010
property "Serialize and deserialize invert each other" do
1111
# `check all id <- integer(0..0x7FFF)` does not work on a single node.
@@ -29,4 +29,81 @@ defmodule Rig.ConnectionCodecTest do
2929
refute :c.pid(id, serial, creation) |> serialize() |> String.contains?(bad_chars)
3030
end
3131
end
32+
33+
property "Encryption / decryption works for key \"magiccookie\"" do
34+
id = 0
35+
key = "magiccookie"
36+
37+
check all serial <- integer(0..0x1FFF),
38+
creation <- integer(0..0x03) do
39+
pid = :c.pid(id, serial, creation) |> :erlang.term_to_binary
40+
assert pid == pid |> encrypt(key) |> decrypt(key)
41+
end
42+
end
43+
44+
property "Encryption / decryption works for key of empty string" do
45+
id = 0
46+
key = ""
47+
48+
check all serial <- integer(0..0x1FFF),
49+
creation <- integer(0..0x03) do
50+
pid = :c.pid(id, serial, creation) |> :erlang.term_to_binary
51+
assert pid == pid |> encrypt(key) |> decrypt(key)
52+
end
53+
end
54+
55+
property "Encryption / decryption works for key \"a\"" do
56+
id = 0
57+
key = "a"
58+
59+
check all serial <- integer(0..0x1FFF),
60+
creation <- integer(0..0x03) do
61+
pid = :c.pid(id, serial, creation) |> :erlang.term_to_binary
62+
assert pid == pid |> encrypt(key) |> decrypt(key)
63+
end
64+
end
65+
66+
property "Encryption / decryption works for a very long key (90 bytes)" do
67+
id = 0
68+
key = "QWErty1234QWErty1234QWErty1234QWErty1234QWErty1234QWErty1234QWErty1234QWErty1234"
69+
70+
check all serial <- integer(0..0x1FFF),
71+
creation <- integer(0..0x03) do
72+
pid = :c.pid(id, serial, creation) |> :erlang.term_to_binary
73+
assert pid == pid |> encrypt(key) |> decrypt(key)
74+
end
75+
end
76+
77+
property "Encryption / decryption works for key of integer 0" do
78+
id = 0
79+
key = 0
80+
81+
check all serial <- integer(0..0x1FFF),
82+
creation <- integer(0..0x03) do
83+
pid = :c.pid(id, serial, creation) |> :erlang.term_to_binary
84+
assert pid == pid |> encrypt(key) |> decrypt(key)
85+
end
86+
end
87+
88+
property "Encryption / decryption works for key of negative integer" do
89+
id = 0
90+
key = -123
91+
92+
check all serial <- integer(0..0x1FFF),
93+
creation <- integer(0..0x03) do
94+
pid = :c.pid(id, serial, creation) |> :erlang.term_to_binary
95+
assert pid == pid |> encrypt(key) |> decrypt(key)
96+
end
97+
end
98+
99+
property "Encryption / decryption works for key of large integer" do
100+
id = 0
101+
key = 12_345_678_901_234_567_890
102+
103+
check all serial <- integer(0..0x1FFF),
104+
creation <- integer(0..0x03) do
105+
pid = :c.pid(id, serial, creation) |> :erlang.term_to_binary
106+
assert pid == pid |> encrypt(key) |> decrypt(key)
107+
end
108+
end
32109
end

docs/rig-ops-guide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Variable&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
5858
`KINESIS_OTP_JAR` | Path to the `OtpErlang.jar` file that contains the `JInterface` implementation. If left empty, RIG picks the file from its Erlang environment (Erlang must be compiled with Java support enabled). | nil
5959
`KINESIS_STREAM` | The name of the Kinesis stream to consume. | "RIG-outbound"
6060
`LOG_LEVEL` | Controls logging level for RIG, available values are: "debug", "info", "warn", "error". Production is using "warn" level. | :warn
61-
`NODE_COOKIE` | Erlang cookie used in distributed mode, so nodes in cluster can communicate between each other. | nil
61+
`NODE_COOKIE` | Erlang cookie used in distributed mode, so nodes in cluster can communicate between each other.<br />Used also as secret key for integrity-check of correlation IDs. | nil
6262
`NODE_HOST` | Erlang hostname for given node, used to build Erlang long-name `rig@NODE_HOST`. This value is used by Erlang's distributed mode, so nodes can see each other. | nil
6363
`PROXY_CONFIG_FILE` | Configuration JSON file with initial API definition for API Proxy. Use this variable to pass either a path to a JSON file, or the JSON string itself. A path can be given in absolute or in relative form (e.g., `proxy/your_json_file.json`). If given in relative form, the working directory is one of RIG's `priv` dirs (e.g., `/opt/sites/rig/lib/rig_inbound_gateway-2.0.2/priv/` in a Docker container). | nil
6464
`PROXY_HTTP_ASYNC_RESPONSE_TIMEOUT` | In case an endpoint has `target` set to `http` and `response_from` set to `http_async`, this is the maximum delay between an HTTP request and the corresponding async HTTP response message. | 5000

0 commit comments

Comments
 (0)