はじめに
この記事は Elixirアドベントカレンダーのシリーズ4の15日目の記事です
前回のクラサバ構成をiOSで動かそうとしたら、起動後にWebSocketのCSRFチェックで延々とエラーが出続けるという不具合踏んだので、その解消方法を解説します
環境
OS macOS Sonoma 14.2
CPU M1
Xcode 15.1
以下の組み合わせでためしたがErlangとElixirのバージョンは関係なかった
Erlang 25.0.4, Elixir 1.13.4
Erlang 26.2.0, Elixir 1.15.7
opensslを以下のバージョンで試したが関係はなかった
1.1.1k
3.2.0
Webサーバーを以下で試したが関係はなかった
cowboy
bandit
エラー内容
起動すると以下の画面でひたすらロードが行われる
Xcodeのログを見ると以下のエラーが出ている
** (ErlangError) Erlang error: {:notsup, %{c_file_line_num: 49, c_file_name: 'hash_equals.c', c_function_arg_num: -1}, 'Unsupported CRYPTO_memcmp'}
(crypto 5.1.1) :crypto.hash_equals_nif("3Ah3klaDRN7MXPETRYeMsUry", "3Ah3klaDRN7MXPETRYeMsUry")
(phoenix 1.7.10) lib/phoenix/socket/transport.ex:498: Phoenix.Socket.Transport.connect_session/3
(phoenix 1.7.10) lib/phoenix/socket/transport.ex:481: anonymous fn/4 in Phoenix.Socket.Transport.connect_info/3
(elixir 1.13.4) lib/enum.ex:2396: Enum."-reduce/3-lists^foldl/2-0-"/3
(phoenix 1.7.10) lib/phoenix/socket/transport.ex:463: Phoenix.Socket.Transport.connect_info/3
(phoenix 1.7.10) lib/phoenix/transports/websocket.ex:48:
どうやら:crypto.hash_equals_nif
の関数を実行した時に何かしらのエラーが発生したようだ
一番上のエラーメッセージで調べても情報は皆無だった
該当箇所の追跡
一つ前のこれを追跡する
(phoenix 1.7.10) lib/phoenix/socket/transport.ex:498: Phoenix.Socket.Transport.connect_session/3
defp connect_session(conn, endpoint, {key, store, {csrf_token_key, init}}) do
conn = Plug.Conn.fetch_cookies(conn)
with csrf_token when is_binary(csrf_token) <- conn.params["_csrf_token"],
cookie when is_binary(cookie) <- conn.cookies[key],
conn = put_in(conn.secret_key_base, endpoint.config(:secret_key_base)),
{_, session} <- store.get(conn, cookie, init),
csrf_state when is_binary(csrf_state) <-
Plug.CSRFProtection.dump_state_from_session(session[csrf_token_key]),
# ここ true <- Plug.CSRFProtection.valid_state_and_csrf_token?(csrf_state, csrf_token) do
session
else
_ -> nil
end
end
Plug.CSRFProtection.valid_state_and_csrf_token?
という次の手掛かりが得られた、どうやら CSRFトークンのチェックらしい
def valid_state_and_csrf_token?(state, csrf_token) do
with <<state_token::@encoded_token_size-binary>> <-
state,
<<user_token::@double_encoded_token_size-binary, mask::@encoded_token_size-binary>> <-
csrf_token do
valid_masked_token?(state_token, user_token, mask)
else
_ -> false
end
end
次はvalid_masked_token?
defp valid_masked_token?(csrf_token, user_token, mask) do
case Base.url_decode64(user_token) do
{:ok, user_token} -> Plug.Crypto.masked_compare(csrf_token, user_token, mask)
:error -> false
end
end
次はこちら、別のライブラリへ飛ぶCryptoがついたのでそろそろか?
Plug.Crypto.masked_compare
def masked_compare(left, right, mask)
when is_binary(left) and is_binary(right) and is_binary(mask) do
byte_size(left) == byte_size(right) and byte_size(right) == byte_size(mask) and
crypto_exor_hash_equals(left, right, mask)
end
defp crypto_exor_hash_equals(x, y, z) do
crypto_hash_equals(mask(x, y), z)
end
エラーに出てきた関数とほぼ同名の関数がでてきましたね
# TODO: remove when we require OTP 25.0
if Code.ensure_loaded?(:crypto) and function_exported?(:crypto, :hash_equals, 2) do
defp crypto_hash_equals(x, y) do
:crypto.hash_equals(x, y)
end
else
defp crypto_hash_equals(x, y) do
legacy_secure_compare(x, y, 0)
end
defp legacy_secure_compare(<<x, left::binary>>, <<y, right::binary>>, acc) do
import Bitwise
xorred = bxor(x, y)
legacy_secure_compare(left, right, acc ||| xorred)
end
defp legacy_secure_compare(<<>>, <<>>, acc) do
acc === 0
end
end
見つけた! ここで :cryptoを使ってるのでここが原因の箇所っぽい
解決策
上のcrypto_hash_equals
はerlangのを使ってて、下のはElixirだけて実装してるっぽいので
下の関数を呼び出せるようにしたい
ライブラリに手を加えるのはわりと簡単で
depsにライブラリのファイルがあるので手を加えて
# TODO: remove when we require OTP 25.0
- if Code.ensure_loaded?(:crypto) and function_exported?(:crypto, :hash_equals, 2) do
+ if false do
defp crypto_hash_equals(x, y) do
:crypto.hash_equals(x, y)
end
else
defp crypto_hash_equals(x, y) do
legacy_secure_compare(x, y, 0)
end
defp legacy_secure_compare(<<x, left::binary>>, <<y, right::binary>>, acc) do
import Bitwise
xorred = bxor(x, y)
legacy_secure_compare(left, right, acc ||| xorred)
end
defp legacy_secure_compare(<<>>, <<>>, acc) do
acc === 0
end
end
_buildの ios_prodを削除して再度ビルドを実行します
動作確認
ちゃんと起動して、TODOを追加できました! やったぜ!
最後に
iOSアプリで起動した時にエラーを解消した手順を解説しました
前回予告した起動時のローカスホスト通信は最新版のXcodeだと以下のような画面からポチポチすれば問題なく動きました
次回はこの不具合を解消する過程の副産物で スマホアプリで動かすErlangバイナリのビルド方法について解説します
本記事は以上になりますありがとうございました