Packages

macula

11.0.0
11.3.1 11.3.0 11.2.0 11.1.0 11.0.0 10.25.0 10.24.0 10.23.0 10.22.0 10.21.0 10.20.3 10.20.2 10.20.0 10.19.2 10.19.1 10.19.0 10.18.0 10.17.0 10.16.0 10.15.0 10.14.5 10.14.4 10.14.2 10.14.1 10.14.0 10.13.2 10.13.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 10.9.0 10.8.0 10.7.0 10.5.8 10.5.7 10.5.6 10.5.5 10.5.4 10.5.3 10.5.2 10.5.1 10.5.0 10.4.0 10.2.0 10.1.1 10.1.0 10.0.2 10.0.1 10.0.0 9.13.8 9.8.2 9.8.1 9.8.0 9.5.0 9.4.0 9.3.1 9.3.0 9.2.0 9.1.1 9.1.0 9.0.0 8.7.0 8.6.0 8.5.0 8.4.1 8.4.0 8.3.0 8.2.0 8.1.0 8.0.2 8.0.1 8.0.0 7.1.0 7.0.0 6.0.0 5.2.2 5.2.1 5.2.0 5.1.0 5.0.0 4.8.0 4.7.1 4.7.0 4.6.0 4.5.0 4.4.10 4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.1 4.3.0 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.1 4.1.0 4.0.0 3.16.0 3.15.3 3.15.2 3.15.1 3.14.0 3.13.0 3.12.1 3.12.0 3.11.1 3.11.0 3.10.3 3.10.2 3.10.1 3.9.0 3.8.0 3.7.0 3.5.0 3.4.0 3.3.0 3.2.0 3.1.0 3.0.0 2.1.1 2.1.0 2.0.0 1.5.2 1.5.1 1.4.30 1.4.29 1.4.28 1.4.27 1.4.26 1.4.25 1.4.24 1.4.23 1.4.22 1.4.21 1.4.20 1.4.19 1.4.18 1.4.17 1.4.16 1.4.15 1.4.14 1.4.13 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.1 1.3.0 1.2.0 1.1.0 1.0.10 1.0.9 1.0.8 1.0.7 1.0.6 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 1.0.0 0.48.6 0.48.5 0.48.4 0.48.3 0.48.2 0.48.1 0.48.0 0.47.1 0.47.0 0.46.3 0.46.1 0.46.0 0.45.3 0.45.2 0.45.1 0.45.0 0.44.2 0.44.1 0.44.0 0.43.3 0.43.2 0.43.1 0.43.0 0.42.9 0.42.8 0.42.7 0.42.6 0.42.5 0.42.4 0.42.3 0.42.2 0.42.1 0.42.0 0.41.1 0.41.0 0.40.1 0.40.0 0.39.9 0.39.8 0.39.7 0.39.6 0.39.5 0.39.4 0.39.3 0.39.2 0.39.1 0.39.0 0.38.8 0.38.7 0.38.6 0.38.5 0.38.4 0.38.3 0.38.2 0.38.1 0.38.0 0.37.7 0.37.6 0.37.5 0.37.4 0.37.3 0.37.2 0.37.1 0.37.0 0.36.6 0.36.5 0.36.4 0.36.3 0.36.2 0.36.1 0.36.0 0.35.4 0.35.3 0.35.2 0.35.1 0.35.0 0.34.1 0.34.0 0.33.1 0.33.0 0.32.5 0.32.4 0.32.3 0.32.2 0.32.1 0.32.0 0.31.9 0.31.8 0.31.7 0.31.6 0.31.5 0.31.4 0.31.3 0.31.2 0.31.1 0.31.0 0.30.10 0.30.9 0.30.8 0.30.7 0.30.6 0.30.5 0.30.4 0.30.3 0.30.2 0.30.1 0.30.0 0.29.0 0.28.3 0.28.2 0.28.1 0.28.0 0.27.1 0.27.0 0.26.1 0.26.0 0.25.6 0.25.5 0.25.4 0.25.3 0.25.2 0.25.1 0.25.0 0.24.6 0.24.5 0.24.4 0.24.3 0.24.2 0.24.1 0.24.0 0.23.3 0.23.2 0.23.1 0.23.0 0.22.12 0.22.11 0.22.10 0.22.9 0.22.8 0.22.7 0.22.6 0.22.5 0.22.4 0.22.3 0.22.2 0.22.1 0.22.0 0.21.7 0.21.6 0.21.5 0.21.4 0.21.2 0.21.1 0.21.0 0.20.25 0.20.24 0.20.23 0.20.22 0.20.21 0.20.20 0.20.19 0.20.18 0.20.17 0.20.16 0.20.15 0.20.14 0.20.13 0.20.12 0.20.11 0.20.10 0.20.9 0.20.8 0.20.7 0.20.6 0.20.5 0.20.3 0.20.2 0.20.1 0.20.0 0.19.2 0.19.1 0.19.0 0.18.1 0.18.0 0.17.4 0.17.3 0.17.2 0.17.1 0.17.0 0.16.6 0.16.5 0.16.4 0.16.3 0.16.2 0.16.1 0.16.0 0.15.1 0.15.0 0.14.3 0.14.2 0.14.1 0.14.0 0.12.6 0.12.5 0.12.3 0.11.3 0.10.2 0.10.1 0.10.0 0.9.2 0.9.1 0.9.0 0.8.25 0.8.24 0.8.23 0.8.22 0.8.21 0.8.20 0.8.19 0.8.18 0.8.17 0.8.16 0.8.15 0.8.14 0.8.13 0.8.12 0.8.11 0.8.10 0.8.9 0.8.8 0.8.7 0.8.6 0.8.5 0.8.4 0.8.3 0.8.2 0.8.1 0.8.0 0.7.30 0.7.29 0.7.28 0.7.27 0.7.26 0.7.25 0.7.24 0.7.23 0.7.22 0.7.21 0.7.20 0.7.19 0.7.18 0.7.17 0.7.16 0.7.15 0.7.14 0.7.13 0.7.12 0.7.11 0.7.10 0.7.9 0.7.8 0.7.7 0.7.6 0.7.5 0.7.4 0.7.3 0.7.2 0.7.1 0.7.0 0.6.7 0.6.6 0.6.5 0.6.4 0.6.3 0.6.2 0.6.1 0.6.0 0.5.0 0.4.4 0.4.3 0.4.2 0.4.1 0.4.0 0.3.4 0.3.3 0.3.2 0.3.1

Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise

Current section

Files

Jump to
macula src macula_foundation.erl
Raw

src/macula_foundation.erl

%% @doc Foundation trust anchor: the key ids of the foundation keys a node trusts, and the check of a foundation record
%% against them.
%%
%% The Foundation signs Tier A seed lists, protocol parameters, realm trust lists and T3 attestations (Part 6 §9.14 to
%% §9.17). A foundation record carries one foundation signature in the signed-object format, by a key whose purpose is
%% foundation. A node trusts foundation keys by key id: SHA-256 over MACULA-KEY-ID-V1, the profile and the key as
%% carried (macula_node_keys:key_id/2). The trusted key ids are the root of trust for the Tier A bootstrap path
%% (Part 5 §4).
%%
%% == Configuration ==
%%
%% Production deployments set the trusted key ids in the macula application environment, under foundation_key_ids,
%% before any Tier A record is trusted. Replacing a key id takes a configuration or firmware update; records signed by
%% a retired key stay valid until they expire.
%%
%% == Placeholders ==
%%
%% Without that setting, key_ids/0 returns five deterministic placeholders, SHA-256 digests of fixed labels. No key
%% derives to a placeholder, so no record verifies as signed by one. Callers that must work only against configured
%% keys use live_key_ids/0, which returns the configured key ids or an empty list, never the placeholders.
%%
%% Reference: plans/PLAN_MACULA_V2_PART5_BOOTSTRAP.md §4, §12; plans/PLAN_MACULA_V2_PART6_PROTOCOL.md §9.14 to §9.17;
%% plans/PLAN_POST_QUANTUM_SECURITY.md.
-module(macula_foundation).
-export([
key_ids/0,
live_key_ids/0,
is_foundation/1,
verify_record/2,
verify_record/3,
placeholder_key_ids/0,
placeholder_mode/0
]).
-export_type([key_id/0, verify_error/0]).
-type key_id() :: <<_:256>>.
-type verify_error() :: record_too_large | malformed | signature_invalid | alg_mismatch | not_yet_valid | expired
| key_id_mismatch | wrong_type | not_foundation_signed.
-define(ENV_KEY, foundation_key_ids).
-define(PLACEHOLDER_COUNT, 5).
-define(PLACEHOLDER_LABEL_PREFIX, "macula-v2-foundation-placeholder-").
-define(FOUNDATION_TYPES, [16#0D, 16#0E, 16#0F, 16#10]).
%%------------------------------------------------------------------
%% Trusted key ids
%%------------------------------------------------------------------
%% @doc The trusted foundation key ids: the configured ones when foundation_key_ids is set and not empty, the
%% placeholders otherwise, for development and tests only.
-spec key_ids() -> [key_id()].
key_ids() ->
configured_or_placeholders(live_key_ids()).
configured_or_placeholders([]) -> placeholder_key_ids();
configured_or_placeholders(KeyIds) -> KeyIds.
%% @doc The configured foundation key ids, or an empty list; never the placeholders. Production bootstrap paths use it
%% to refuse to trust a record that only a placeholder would admit.
-spec live_key_ids() -> [key_id()].
live_key_ids() ->
configured(application:get_env(macula, ?ENV_KEY)).
configured({ok, KeyIds}) when is_list(KeyIds) -> KeyIds;
configured(_Unset) -> [].
%% @doc Whether a value is one of the trusted foundation key ids.
-spec is_foundation(term()) -> boolean().
is_foundation(<<_:256>> = KeyId) ->
lists:member(KeyId, key_ids());
is_foundation(_NotAKeyId) ->
false.
%%------------------------------------------------------------------
%% Foundation records
%%------------------------------------------------------------------
%% @doc Verify a foundation record, as its wire form or its {key, tbs, signature} map, under the verifier's profile.
%% The record is accepted when it verifies (macula_record:verify/3), its type is a foundation type, and its signer's
%% key id is trusted. Refusals are returned in that order, never raised.
-spec verify_record(binary() | map(), macula_crypto_profile:profile()) ->
{ok, macula_record:m_record()} | {error, verify_error()}.
verify_record(Signed, Profile) ->
verify_record(Signed, Profile, erlang:system_time(millisecond)).
%% @doc As verify_record/2, at the verifier's clock Now, in milliseconds.
-spec verify_record(binary() | map(), macula_crypto_profile:profile(), integer()) ->
{ok, macula_record:m_record()} | {error, verify_error()}.
verify_record(Signed, Profile, Now) ->
foundation_record(macula_record:verify(Signed, Profile, Now)).
foundation_record({ok, #{type := Type} = Record}) ->
typed(lists:member(Type, ?FOUNDATION_TYPES), Record);
foundation_record({error, _} = Refusal) ->
Refusal.
typed(true, #{key_id := KeyId} = Record) -> signed_by_foundation(is_foundation(KeyId), Record);
typed(false, _Record) -> {error, wrong_type}.
signed_by_foundation(true, Record) -> {ok, Record};
signed_by_foundation(false, _Record) -> {error, not_foundation_signed}.
%%------------------------------------------------------------------
%% Placeholders
%%------------------------------------------------------------------
%% @doc The placeholder key ids: SHA-256 digests of fixed labels, which no key derives to.
-spec placeholder_key_ids() -> [key_id()].
placeholder_key_ids() ->
[crypto:hash(sha256, [?PLACEHOLDER_LABEL_PREFIX, integer_to_list(I)]) || I <- lists:seq(1, ?PLACEHOLDER_COUNT)].
%% @doc Whether the node trusts the placeholder key ids, because no foundation key id is configured. Production code
%% refuses to bootstrap while this is true.
-spec placeholder_mode() -> boolean().
placeholder_mode() ->
live_key_ids() =:= [].