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)
|
||||
```
|
||||
|
||||
服务端在 `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(),
|
||||
token_hash :: binary(),
|
||||
salt :: binary(),
|
||||
heartbeat_secret :: binary(),
|
||||
timestamp = 0 :: integer()
|
||||
}).
|
||||
}).
|
||||
|
||||
@ -45,8 +45,8 @@ verify_heartbeat(UUID, Timestamp, Payload, Mac)
|
||||
case mnesia:dirty_read(efka_client, UUID) of
|
||||
[] ->
|
||||
false;
|
||||
[#efka_client{token_hash = TokenHash}] ->
|
||||
ExpectedMac = crypto:mac(hmac, sha256, TokenHash, Payload),
|
||||
[#efka_client{heartbeat_secret = HeartbeatSecret}] ->
|
||||
ExpectedMac = crypto:mac(hmac, sha256, HeartbeatSecret, Payload),
|
||||
ExpectedMac =:= Mac
|
||||
end;
|
||||
false ->
|
||||
@ -61,7 +61,14 @@ register(UUID, Token) when is_binary(UUID), UUID =/= <<>>, is_binary(Token), byt
|
||||
%% 1. 生成 16 字节随机盐
|
||||
Salt = crypto:strong_rand_bytes(16),
|
||||
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;
|
||||
[_] ->
|
||||
{error, already_exists}
|
||||
@ -137,7 +144,11 @@ list() ->
|
||||
hash_token(Token, Salt) when is_binary(Token), is_binary(Salt) ->
|
||||
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().
|
||||
valid_heartbeat_timestamp(Timestamp) ->
|
||||
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{}}).
|
||||
handle_info({udp, Socket, Ip, Port, Packet}, State = #state{socket = Socket}) ->
|
||||
handle_heartbeat_packet(Packet, {Ip, Port}),
|
||||
{noreply, State};
|
||||
handle_info(Info, State = #state{}) ->
|
||||
logger:warning("[udp_server] ignore unknown info: ~p", [Info]),
|
||||
{noreply, State}.
|
||||
|
||||
%% @private
|
||||
@ -91,7 +94,8 @@ handle_info({udp, Socket, Ip, Port, Packet}, State = #state{socket = Socket}) ->
|
||||
%% with Reason. The return value is ignored.
|
||||
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | term()),
|
||||
State :: #state{}) -> term()).
|
||||
terminate(_Reason, _State = #state{}) ->
|
||||
terminate(_Reason, _State = #state{socket = Socket}) ->
|
||||
gen_udp:close(Socket),
|
||||
ok.
|
||||
|
||||
%% @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,
|
||||
{ok, UUID, Timestamp, Payload, Mac};
|
||||
decode_heartbeat_packet(_Packet) ->
|
||||
error.
|
||||
error.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user