fix heartbeat
This commit is contained in:
parent
e46dc8d2bb
commit
7e49d2df0c
@ -41,4 +41,4 @@ Payload = <<Version:8, UuidLen:16, UUID:UuidLen/binary, Timestamp:64/unsigned-bi
|
|||||||
Mac = crypto:mac(hmac, sha256, HeartbeatSecret, Payload)
|
Mac = crypto:mac(hmac, sha256, HeartbeatSecret, Payload)
|
||||||
```
|
```
|
||||||
|
|
||||||
服务端在 `efka_client_store:verify_heartbeat/4` 中校验时间窗和 HMAC。当前服务端使用 `efka_client_store` 中保存的 `token_hash` 作为 `HeartbeatSecret`,避免在 iot 侧保存明文 auth token。
|
服务端在 `efka_client_store:verify_heartbeat/4` 中校验时间窗和 HMAC。`HeartbeatSecret` 使用 `SHA256(Token)`,其中 `Token` 和 TLS 鉴权使用同一个 auth token。`iot` 侧只保存派生后的 `heartbeat_secret`,不保存明文 token。
|
||||||
|
|||||||
@ -12,5 +12,6 @@
|
|||||||
uuid :: binary(),
|
uuid :: binary(),
|
||||||
token_hash :: binary(),
|
token_hash :: binary(),
|
||||||
salt :: binary(),
|
salt :: binary(),
|
||||||
|
heartbeat_secret :: binary(),
|
||||||
timestamp = 0 :: integer()
|
timestamp = 0 :: integer()
|
||||||
}).
|
}).
|
||||||
|
|||||||
@ -45,8 +45,8 @@ verify_heartbeat(UUID, Timestamp, Payload, Mac)
|
|||||||
case mnesia:dirty_read(efka_client, UUID) of
|
case mnesia:dirty_read(efka_client, UUID) of
|
||||||
[] ->
|
[] ->
|
||||||
false;
|
false;
|
||||||
[#efka_client{token_hash = TokenHash}] ->
|
[#efka_client{heartbeat_secret = HeartbeatSecret}] ->
|
||||||
ExpectedMac = crypto:mac(hmac, sha256, TokenHash, Payload),
|
ExpectedMac = crypto:mac(hmac, sha256, HeartbeatSecret, Payload),
|
||||||
ExpectedMac =:= Mac
|
ExpectedMac =:= Mac
|
||||||
end;
|
end;
|
||||||
false ->
|
false ->
|
||||||
@ -61,7 +61,14 @@ register(UUID, Token) when is_binary(UUID), UUID =/= <<>>, is_binary(Token), byt
|
|||||||
%% 1. 生成 16 字节随机盐
|
%% 1. 生成 16 字节随机盐
|
||||||
Salt = crypto:strong_rand_bytes(16),
|
Salt = crypto:strong_rand_bytes(16),
|
||||||
TokenHash = hash_token(Token, Salt),
|
TokenHash = hash_token(Token, Salt),
|
||||||
ok = mnesia:write(#efka_client{uuid = UUID, token_hash = TokenHash, salt = Salt, timestamp = iot_util:timestamp()}),
|
HeartbeatSecret = heartbeat_secret(Token),
|
||||||
|
ok = mnesia:write(#efka_client{
|
||||||
|
uuid = UUID,
|
||||||
|
token_hash = TokenHash,
|
||||||
|
salt = Salt,
|
||||||
|
heartbeat_secret = HeartbeatSecret,
|
||||||
|
timestamp = iot_util:timestamp()
|
||||||
|
}),
|
||||||
ok;
|
ok;
|
||||||
[_] ->
|
[_] ->
|
||||||
{error, already_exists}
|
{error, already_exists}
|
||||||
@ -137,7 +144,11 @@ list() ->
|
|||||||
hash_token(Token, Salt) when is_binary(Token), is_binary(Salt) ->
|
hash_token(Token, Salt) when is_binary(Token), is_binary(Salt) ->
|
||||||
crypto:pbkdf2_hmac(sha256, Token, Salt, ?TOKEN_HASH_ITERATIONS, ?TOKEN_HASH_BYTES).
|
crypto:pbkdf2_hmac(sha256, Token, Salt, ?TOKEN_HASH_ITERATIONS, ?TOKEN_HASH_BYTES).
|
||||||
|
|
||||||
|
-spec heartbeat_secret(binary()) -> binary().
|
||||||
|
heartbeat_secret(Token) when is_binary(Token) ->
|
||||||
|
crypto:hash(sha256, Token).
|
||||||
|
|
||||||
-spec valid_heartbeat_timestamp(integer()) -> boolean().
|
-spec valid_heartbeat_timestamp(integer()) -> boolean().
|
||||||
valid_heartbeat_timestamp(Timestamp) ->
|
valid_heartbeat_timestamp(Timestamp) ->
|
||||||
Now = iot_util:current_time(),
|
Now = iot_util:current_time(),
|
||||||
Timestamp =< Now andalso Now - Timestamp =< ?HEARTBEAT_TIMESTAMP_WINDOW.
|
Timestamp =< Now andalso Now - Timestamp =< ?HEARTBEAT_TIMESTAMP_WINDOW.
|
||||||
|
|||||||
@ -82,6 +82,9 @@ handle_cast(_Request, State = #state{}) ->
|
|||||||
{stop, Reason :: term(), NewState :: #state{}}).
|
{stop, Reason :: term(), NewState :: #state{}}).
|
||||||
handle_info({udp, Socket, Ip, Port, Packet}, State = #state{socket = Socket}) ->
|
handle_info({udp, Socket, Ip, Port, Packet}, State = #state{socket = Socket}) ->
|
||||||
handle_heartbeat_packet(Packet, {Ip, Port}),
|
handle_heartbeat_packet(Packet, {Ip, Port}),
|
||||||
|
{noreply, State};
|
||||||
|
handle_info(Info, State = #state{}) ->
|
||||||
|
logger:warning("[udp_server] ignore unknown info: ~p", [Info]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
@ -91,7 +94,8 @@ handle_info({udp, Socket, Ip, Port, Packet}, State = #state{socket = Socket}) ->
|
|||||||
%% with Reason. The return value is ignored.
|
%% with Reason. The return value is ignored.
|
||||||
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
|
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
|
||||||
State :: #state{}) -> term()).
|
State :: #state{}) -> term()).
|
||||||
terminate(_Reason, _State = #state{}) ->
|
terminate(_Reason, _State = #state{socket = Socket}) ->
|
||||||
|
gen_udp:close(Socket),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
@ -132,4 +136,4 @@ decode_heartbeat_packet(Packet = <<?HEARTBEAT_VERSION:8, UUIDLen:16, UUID:UUIDLe
|
|||||||
<<Payload:PayloadSize/binary, Mac:?HEARTBEAT_MAC_BYTES/binary>> = Packet,
|
<<Payload:PayloadSize/binary, Mac:?HEARTBEAT_MAC_BYTES/binary>> = Packet,
|
||||||
{ok, UUID, Timestamp, Payload, Mac};
|
{ok, UUID, Timestamp, Payload, Mac};
|
||||||
decode_heartbeat_packet(_Packet) ->
|
decode_heartbeat_packet(_Packet) ->
|
||||||
error.
|
error.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user