Current section

Files

Jump to
amarula lib amarula protocol auth device_identity.ex
Raw

lib/amarula/protocol/auth/device_identity.ex

defmodule Amarula.Protocol.Auth.DeviceIdentity do
@moduledoc """
The pairing device-identity crypto: verify the primary's signed device identity,
counter-sign it with our key, and derive the companion's signal identity. Pure
(takes auth creds + the received identity, returns values) — extracted from
`Connection` so the connection process keeps only socket/state concerns.
Ported from Baileys' pair-success handling (`src/Socket/socket.ts`).
"""
alias Amarula.Protocol.Crypto.{Constants, Crypto}
alias Amarula.Protocol.Proto
@doc """
Verify the received `identity_hmac` (an `ADVSignedDeviceIdentityHMAC`) against
our `auth_creds`, then return the account with our device signature added.
`{:ok, signed_account}` or `{:error, reason}`.
"""
@spec verify_and_sign(struct(), map()) :: {:ok, struct()} | {:error, term()}
def verify_and_sign(identity_hmac, auth_creds) do
account = Proto.ADVSignedDeviceIdentity.decode(identity_hmac.details)
with :ok <- verify_hmac(identity_hmac, auth_creds.adv_secret_key),
:ok <- verify_account_signature(account, auth_creds.signed_identity_key.public),
device_signature <- device_signature(account, auth_creds.signed_identity_key) do
{:ok, %{account | deviceSignature: device_signature}}
end
end
@doc "The companion signal identity `%{identifier, identifierKey}` for `lid`."
@spec signal_identity(String.t(), binary()) :: map()
def signal_identity(lid, account_signature_key) do
%{
identifier: %{name: lid, deviceId: 0},
identifierKey: Crypto.generate_signal_pub_key(account_signature_key)
}
end
@doc """
Encode an `ADVSignedDeviceIdentity` for the wire. Drops the account signature
key unless `include_signature_key?` (or it's already empty).
"""
@spec encode(struct(), boolean()) :: binary()
def encode(account, include_signature_key?) do
account =
if include_signature_key? or byte_size(account.accountSignatureKey || <<>>) == 0 do
account
else
%{account | accountSignatureKey: nil}
end
Proto.ADVSignedDeviceIdentity.encode(account)
end
# --- internals ---
defp verify_hmac(identity_hmac, adv_secret_key_b64) do
hmac_prefix =
case identity_hmac.accountType do
:HOSTED -> Constants.wa_adv_hosted_account_sig_prefix()
_ -> <<>>
end
adv_secret_key = Base.decode64!(adv_secret_key_b64)
expected = Crypto.hmac_sign(hmac_prefix <> identity_hmac.details, adv_secret_key)
if byte_size(identity_hmac.hmac) == byte_size(expected) and
:crypto.hash_equals(identity_hmac.hmac, expected) do
:ok
else
{:error, :invalid_hmac}
end
end
defp verify_account_signature(account, signed_identity_public_key) do
device_identity = Proto.ADVDeviceIdentity.decode(account.details)
prefix =
case device_identity.deviceType do
:HOSTED -> Constants.wa_adv_hosted_account_sig_prefix()
_ -> Constants.wa_adv_account_sig_prefix()
end
msg = prefix <> account.details <> signed_identity_public_key
if Crypto.verify(msg, account.accountSignature, account.accountSignatureKey) do
:ok
else
{:error, :invalid_account_signature}
end
end
defp device_signature(account, signed_identity_key) do
msg =
Constants.wa_adv_device_sig_prefix() <>
account.details <> signed_identity_key.public <> account.accountSignatureKey
Crypto.sign(msg, signed_identity_key.private)
end
end