Skip to content

Commit 4fe8576

Browse files
mizzet1eddbbt
andauthored
chore: Setup infrastructure for not reusing creds (astarte-platform#1801)
Signed-off-by: Eddy Babetto <eddy.babetto@secomind.com> Co-authored-by: Eddy Babetto <eddy.babetto@secomind.com>
1 parent 9d4bc89 commit 4fe8576

File tree

20 files changed

+329
-37
lines changed

20 files changed

+329
-37
lines changed

.github/workflows/astarte-end-to-end-test-workflow.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,7 @@ jobs:
174174
-k compose/astarte-keys/housekeeping_private.pem
175175
- name: Run FDO
176176
run: just astarte-run
177+
- name: Check Docker
178+
working-directory: .tmp/repos/astarte
179+
if: ${{ failure() }}
180+
run: docker compose logs astarte-pairing

apps/astarte_housekeeping/lib/astarte_housekeeping/realms/queries.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@ defmodule Astarte.Housekeeping.Realms.Queries do
569569
CREATE TABLE #{keyspace_name}.to2_sessions (
570570
guid blob,
571571
device_id uuid,
572+
hmac blob,
572573
nonce blob,
573574
sig_type int,
574575
epid_group blob,
@@ -586,6 +587,10 @@ defmodule Astarte.Housekeeping.Realms.Queries do
586587
device_service_info map<tuple<text, text>, blob>,
587588
owner_service_info list<blob>,
588589
last_chunk_sent int,
590+
replacement_guid blob,
591+
replacement_rv_info blob,
592+
replacement_pub_key blob,
593+
replacement_hmac blob,
589594
PRIMARY KEY (guid)
590595
)
591596
WITH default_time_to_live = 7200;

apps/astarte_housekeeping/priv/migrations/realm/0010_create_device_session_table.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
CREATE TABLE :keyspace.to2_sessions (
22
guid blob,
33
device_id uuid,
4+
hmac blob,
45
nonce blob,
56
sig_type int,
67
epid_group blob,
@@ -18,6 +19,10 @@ CREATE TABLE :keyspace.to2_sessions (
1819
device_service_info map<tuple<text, text>, blob>,
1920
owner_service_info list<blob>,
2021
last_chunk_sent int,
22+
replacement_guid blob,
23+
replacement_rv_info blob,
24+
replacement_pub_key blob,
25+
replacement_hmac blob,
2126
PRIMARY KEY (guid)
2227
)
2328
WITH default_time_to_live = 7200;

apps/astarte_pairing/config/dev.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,4 @@ config :astarte_pairing, :cfssl_url, "http://localhost:8888"
7474
config :astarte_pairing, :base_url_domain, "api.astarte.localhost"
7575
config :astarte_pairing, :base_url_port, 4003
7676
config :astarte_pairing, :base_url_protocol, :http
77+
config :astarte_pairing, :enable_credential_reuse, true

apps/astarte_pairing/config/test.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ config :astarte_pairing, :enable_fdo, true
101101
config :astarte_pairing, :base_url_domain, "api.astarte.localhost"
102102
config :astarte_pairing, :base_url_port, 4003
103103
config :astarte_pairing, :base_url_protocol, :http
104+
config :astarte_pairing, :enable_credential_reuse, true
104105

105106
config :bcrypt_elixir,
106107
log_rounds: 4

apps/astarte_pairing/lib/astarte_pairing/fdo/owner_onboarding.ex

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ defmodule Astarte.Pairing.FDO.OwnerOnboarding do
4949

5050
@max_owner_message_size 65_535
5151
@rsa_public_exponent 65_537
52+
@one_week 604_800
5253

5354
def hello_device(realm_name, cbor_hello_device) do
5455
with {:ok, hello_device} <- HelloDevice.decode(cbor_hello_device),
@@ -62,7 +63,8 @@ defmodule Astarte.Pairing.FDO.OwnerOnboarding do
6263
realm_name,
6364
hello_device,
6465
ownership_voucher,
65-
owner_private_key
66+
owner_private_key,
67+
ownership_voucher.hmac
6668
) do
6769
encoded_pub_key = PublicKey.encode(pub_key)
6870
num_ov_entries = Enum.count(ownership_voucher.entries)
@@ -136,6 +138,9 @@ defmodule Astarte.Pairing.FDO.OwnerOnboarding do
136138
def prove_device(realm_name, body, session) do
137139
guid = session.guid
138140

141+
# TODO credential reuse requires also Owner2Key and/or rv info to be changed for credential reuse
142+
# so far, there is no API to do so, so it-s limited to the guid
143+
139144
with {:ok, ownership_voucher} <- OwnershipVoucher.fetch(realm_name, guid),
140145
{:ok, private_key} <- Queries.get_owner_private_key(realm_name, guid),
141146
{:ok, owner_public_key} <- OwnershipVoucher.owner_public_key(ownership_voucher) do
@@ -157,6 +162,15 @@ defmodule Astarte.Pairing.FDO.OwnerOnboarding do
157162
session,
158163
body,
159164
connection_credentials
165+
),
166+
{:ok, session} <-
167+
Session.add_replacement_info(
168+
session,
169+
realm_name,
170+
guid,
171+
rendezvous_info,
172+
owner_public_key,
173+
nil
160174
) do
161175
{:ok, session, resp_msg}
162176
end
@@ -244,19 +258,23 @@ defmodule Astarte.Pairing.FDO.OwnerOnboarding do
244258
max_owner_service_info_sz: max_owner_service_info_sz
245259
}
246260
) do
247-
with {:ok, old_voucher} <-
248-
OwnershipVoucher.fetch(realm_name, session.guid),
249-
{:ok, _new_voucher} <-
250-
OwnershipVoucher.generate_replacement_voucher(old_voucher, replacement_hmac),
251-
{:ok, _session} <-
252-
Session.add_max_owner_service_info_size(session, realm_name, max_owner_service_info_sz) do
253-
# TODO: Store `new_voucher` into DB.
254-
261+
with {:ok, _} <- Queries.fetch_session(realm_name, session.guid),
262+
{:ok, session} <-
263+
Session.add_max_owner_service_info_size(session, realm_name, max_owner_service_info_sz),
264+
{:ok, session} <-
265+
Session.add_replacement_info(
266+
session,
267+
realm_name,
268+
session.replacement_guid,
269+
session.replacement_rv_info,
270+
session.replacement_pub_key,
271+
replacement_hmac || session.hmac
272+
) do
255273
response =
256274
OwnerServiceInfoReady.new()
257275
|> OwnerServiceInfoReady.to_cbor_list()
258276

259-
{:ok, response}
277+
{:ok, session, response}
260278
else
261279
_ ->
262280
{:error, :failed_66}
@@ -272,6 +290,27 @@ defmodule Astarte.Pairing.FDO.OwnerOnboarding do
272290
check_prove_dv_nonces_equality(prove_dv_nonce_challenge, to2_session.prove_dv_nonce),
273291
{:ok, _device} <- Queries.remove_device_ttl(realm_name, to2_session.device_id) do
274292
done2_message = build_done2_message(to2_session.setup_dv_nonce)
293+
294+
if not OwnershipVoucher.credential_reuse?(to2_session) do
295+
with {:ok, old_voucher} <-
296+
OwnershipVoucher.fetch(realm_name, to2_session.guid),
297+
{:ok, new_voucher} <-
298+
OwnershipVoucher.generate_replacement_voucher(old_voucher, to2_session),
299+
# TODO change this line to ensure the retrival of latest private key after exposing an API to do so
300+
{:ok, private_key} <-
301+
Queries.get_owner_private_key(realm_name, to2_session.guid) do
302+
cbor_voucher = OwnershipVoucher.cbor_encode(new_voucher)
303+
304+
Queries.replace_ownership_voucher(
305+
realm_name,
306+
to2_session.guid,
307+
cbor_voucher,
308+
private_key,
309+
@one_week
310+
)
311+
end
312+
end
313+
275314
{:ok, done2_message}
276315
end
277316
end

apps/astarte_pairing/lib/astarte_pairing/fdo/owner_onboarding/session.ex

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@ defmodule Astarte.Pairing.FDO.OwnerOnboarding.Session do
2626
alias Astarte.Pairing.FDO.OwnerOnboarding.Session
2727
alias Astarte.Pairing.FDO.OwnerOnboarding.SessionKey
2828
alias Astarte.Pairing.FDO.OwnerOnboarding.SessionToken
29+
alias Astarte.Pairing.FDO.Rendezvous.RvTO2Addr
30+
alias Astarte.Pairing.FDO.Types.Hash
2931
alias Astarte.Pairing.Queries
3032
alias COSE.Messages.Encrypt0
3133

3234
typedstruct do
3335
field :guid, binary()
36+
field :hmac, Hash.t()
3437
field :device_id, Astarte.DataAccess.UUID, default: nil
3538
field :nonce, binary()
3639
field :device_signature, SignatureInfo.device_signature()
@@ -48,9 +51,13 @@ defmodule Astarte.Pairing.FDO.OwnerOnboarding.Session do
4851
field :device_service_info, map() | nil
4952
field :owner_service_info, [binary()] | nil
5053
field :last_chunk_sent, non_neg_integer() | nil
54+
field :replacement_guid, binary() | nil
55+
field :replacement_rv_info, [RvTO2Addr.t()] | nil
56+
field :replacement_pub_key, struct() | nil
57+
field :replacement_hmac, Hash.t() | nil
5158
end
5259

53-
def new(realm_name, hello_device, ownership_voucher, owner_key) do
60+
def new(realm_name, hello_device, ownership_voucher, owner_key, hmac) do
5461
prove_dv_nonce = :crypto.strong_rand_bytes(16)
5562
nonce = :crypto.strong_rand_bytes(16)
5663

@@ -70,6 +77,7 @@ defmodule Astarte.Pairing.FDO.OwnerOnboarding.Session do
7077
%TO2Session{
7178
guid: guid,
7279
device_id: nil,
80+
hmac: Hash.encode_cbor(hmac),
7381
nonce: nonce,
7482
prove_dv_nonce: prove_dv_nonce,
7583
kex_suite_name: kex_name,
@@ -88,6 +96,7 @@ defmodule Astarte.Pairing.FDO.OwnerOnboarding.Session do
8896
session =
8997
%Session{
9098
guid: guid,
99+
hmac: hmac,
91100
device_id: nil,
92101
nonce: nonce,
93102
prove_dv_nonce: prove_dv_nonce,
@@ -175,13 +184,45 @@ defmodule Astarte.Pairing.FDO.OwnerOnboarding.Session do
175184
end
176185
end
177186

187+
defp decode_hash(binary) do
188+
with {:ok, cbor_list, ""} <- CBOR.decode(binary),
189+
{:ok, hash} <- Hash.decode(cbor_list) do
190+
{:ok, hash}
191+
end
192+
end
193+
178194
defp encode_values_to_cbor(map) when is_map(map) do
179195
Map.new(map, fn
180196
{key, value} ->
181197
{key, CBOR.encode(value)}
182198
end)
183199
end
184200

201+
def add_replacement_info(session, realm_name, replacement_guid, rv_info, pub_key, hmac) do
202+
serialized_hmac = if hmac, do: Hash.encode_cbor(hmac), else: nil
203+
serialized_rv_info = if rv_info, do: :erlang.term_to_binary(rv_info), else: nil
204+
serialized_pub_key = if pub_key, do: :erlang.term_to_binary(pub_key), else: nil
205+
206+
with :ok <-
207+
Queries.session_add_replacement_info(
208+
realm_name,
209+
session.guid,
210+
replacement_guid,
211+
serialized_rv_info,
212+
serialized_pub_key,
213+
serialized_hmac
214+
) do
215+
{:ok,
216+
%{
217+
session
218+
| replacement_guid: replacement_guid,
219+
replacement_rv_info: rv_info,
220+
replacement_pub_key: pub_key,
221+
replacement_hmac: hmac
222+
}}
223+
end
224+
end
225+
185226
def build_session_secret(session, realm_name, owner_key, xb) do
186227
%Session{kex_suite_name: kex, owner_random: owner_random, guid: guid} = session
187228

@@ -237,6 +278,7 @@ defmodule Astarte.Pairing.FDO.OwnerOnboarding.Session do
237278
%TO2Session{
238279
guid: guid,
239280
device_id: device_id,
281+
hmac: db_hmac,
240282
nonce: db_nonce,
241283
prove_dv_nonce: prove_dv_nonce,
242284
setup_dv_nonce: setup_dv_nonce,
@@ -250,12 +292,31 @@ defmodule Astarte.Pairing.FDO.OwnerOnboarding.Session do
250292
max_owner_service_info_size: max_owner_service_info_size,
251293
device_service_info: device_service_info,
252294
owner_service_info: owner_service_info,
253-
last_chunk_sent: last_chunk_sent
295+
last_chunk_sent: last_chunk_sent,
296+
replacement_guid: replacement_guid,
297+
replacement_rv_info: db_replacement_rv_info,
298+
replacement_pub_key: db_replacement_pub_key,
299+
replacement_hmac: db_replacement_hmac
254300
} = database_session
255301

302+
{:ok, hmac} = decode_hash(db_hmac)
303+
304+
replacement_hmac =
305+
if db_replacement_hmac do
306+
{:ok, h} = decode_hash(db_replacement_hmac)
307+
h
308+
end
309+
310+
replacement_rv_info =
311+
if db_replacement_rv_info, do: :erlang.binary_to_term(db_replacement_rv_info), else: nil
312+
313+
replacement_pub_key =
314+
if db_replacement_pub_key, do: :erlang.binary_to_term(db_replacement_pub_key), else: nil
315+
256316
session = %Session{
257317
guid: guid,
258318
device_id: device_id,
319+
hmac: hmac,
259320
nonce: db_nonce,
260321
prove_dv_nonce: prove_dv_nonce,
261322
setup_dv_nonce: setup_dv_nonce,
@@ -270,7 +331,11 @@ defmodule Astarte.Pairing.FDO.OwnerOnboarding.Session do
270331
max_owner_service_info_size: max_owner_service_info_size,
271332
device_service_info: device_service_info,
272333
owner_service_info: owner_service_info,
273-
last_chunk_sent: last_chunk_sent
334+
last_chunk_sent: last_chunk_sent,
335+
replacement_guid: replacement_guid,
336+
replacement_rv_info: replacement_rv_info,
337+
replacement_pub_key: replacement_pub_key,
338+
replacement_hmac: replacement_hmac
274339
}
275340

276341
{:ok, session}

apps/astarte_pairing/lib/astarte_pairing/fdo/ownership_voucher/ownership_voucher.ex

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,19 @@ defmodule Astarte.Pairing.FDO.OwnershipVoucher do
115115
Enum.find(extracted, {:ok, extracted}, &(&1 == :error))
116116
end
117117

118-
def generate_replacement_voucher(_ownership_voucher, nil) do
119-
# If ReplacementHMac is null, we don't create a new voucher (Credential Reuse)
120-
{:ok, nil}
121-
end
118+
def generate_replacement_voucher(ownership_voucher, session) do
119+
new_header =
120+
ownership_voucher.header
121+
|> Map.put(:guid, session.replacement_guid)
122+
|> Map.put(:rendezvous_info, session.replacement_rv_info)
123+
|> Map.put(:public_key, session.replacement_pub_key)
124+
125+
new_voucher =
126+
ownership_voucher
127+
|> Map.put(:hmac, session.replacement_hmac)
128+
|> Map.put(:header, new_header)
129+
|> Map.put(:entries, [])
122130

123-
def generate_replacement_voucher(ownership_voucher, new_dev_id_hmac) do
124-
new_voucher = ownership_voucher |> Map.put(:hmac, new_dev_id_hmac) |> Map.put(:entries, [])
125131
{:ok, new_voucher}
126132
end
127133

@@ -142,4 +148,18 @@ defmodule Astarte.Pairing.FDO.OwnershipVoucher do
142148
def cbor_encode(voucher) do
143149
encode(voucher) |> CBOR.encode()
144150
end
151+
152+
def credential_reuse_config_enabled?() do
153+
true
154+
end
155+
156+
def credential_reuse?(session) do
157+
# TODO credential reuse requires also Owner2Key and/or rv info to be changed for credential reuse
158+
# so far, there is no API to do so, so it-s limited to the guid
159+
160+
case session.replacement_hmac == session.hmac && session.guid == session.replacement_guid do
161+
false -> false
162+
true -> credential_reuse_config_enabled?()
163+
end
164+
end
145165
end

0 commit comments

Comments
 (0)