defmodule ExMobileDevice.Ssl do @moduledoc """ SSL/TLS helper functions. This module contains a helper function, `connect/4` that can be used to authenticate an _existing_ `:gen_tcp` socket. """ @legacy_sign_algos [ sha: :rsa, sha: :ecdsa, sha256: :rsa, sha256: :ecdsa, sha384: :rsa, sha384: :ecdsa, sha512: :rsa, sha512: :ecdsa ] require Logger @doc """ Authenticate an existing connection, using a pair-record. On success, returns an `:ssl` socket. """ @spec connect(port(), map(), String.t(), timeout()) :: {:ok, :ssl.sslsocket()} | {:error, any()} def connect(socket, pair_record, udid, timeout \\ :infinity) when is_port(socket) and is_map(pair_record) do host_cert = pair_record["HostCertificate"] host_key = pair_record["HostPrivateKey"] [{_, cert, _}] = :public_key.pem_decode(host_cert) [{key_type, key, _}] = :public_key.pem_decode(host_key) debug_ssl? = case :ets.lookup(__MODULE__, udid) do [{_, prev_cert, prev_key, debug}] -> if prev_cert != host_cert do Logger.error( "#{__MODULE__}: #{udid} HostCertificate mismatch: Prev: #{prev_cert}. New: #{host_cert}" ) end if prev_key != host_key do Logger.error("#{__MODULE__}: #{udid} HostPrivateKey mismatch") end debug _ -> false end result = :ssl.connect( socket, [ cert: cert, key: {key_type, key}, verify: :verify_none, signature_algs: signature_algs(), log_level: if(debug_ssl?, do: :debug, else: :notice) ], timeout ) # Insert if new otherwise just update the pair record if !:ets.insert_new(__MODULE__, {udid, host_cert, host_key, false}) do :ets.update_element(__MODULE__, udid, [{2, host_cert}, {3, host_key}]) end result end def init do :ets.new(__MODULE__, [:public, :named_table]) :ok end defp signature_algs do :ssl.signature_algs(:default, :"tlsv1.3") ++ @legacy_sign_algos end end