Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Streamline SSL experience #677

Merged
merged 13 commits into from
May 18, 2024
2 changes: 1 addition & 1 deletion lib/postgrex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ defmodule Postgrex do
| {:connect_timeout, timeout}
| {:handshake_timeout, timeout}
| {:ping_timeout, timeout}
| {:ssl, boolean}
| {:ssl, boolean | :verify_full}
| {:ssl_opts, [:ssl.tls_client_option()]}
| {:socket_options, [:gen_tcp.connect_option()]}
| {:prepare, :named | :unnamed}
Expand Down
51 changes: 41 additions & 10 deletions lib/postgrex/protocol.ex
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,19 @@ defmodule Postgrex.Protocol do
timeout = opts[:timeout] || @timeout
ping_timeout = Keyword.get(opts, :ping_timeout, timeout)
sock_opts = [send_timeout: timeout] ++ (opts[:socket_options] || [])
ssl? = opts[:ssl] || false
types_mod = Keyword.fetch!(opts, :types)
disconnect_on_error_codes = opts[:disconnect_on_error_codes] || []
target_server_type = opts[:target_server_type] || :any
disable_composite_types = opts[:disable_composite_types] || false
{ssl, opts} = Keyword.pop(opts, :ssl, false)

{ssl_opts, opts} =
if defaults = default_ssl_opts(ssl, opts) do
{ssl_opts, opts} = Keyword.pop(opts, :ssl_opts, [])
{Config.Reader.merge(defaults, ssl_opts), opts}
else
{nil, opts}
end

transactions =
case opts[:transactions] || :naive do
Expand Down Expand Up @@ -117,14 +125,37 @@ defmodule Postgrex.Protocol do
types_lock: nil,
prepare: prepare,
messages: [],
ssl: ssl?,
ssl: ssl_opts,
target_server_type: target_server_type,
search_path: opts[:search_path]
}

connect_endpoints(endpoints, sock_opts ++ @sock_opts, connect_timeout, s, status, [])
end

defp default_ssl_opts(:verify_full, opts) do
case Keyword.fetch(opts, :hostname) do
josevalim marked this conversation as resolved.
Show resolved Hide resolved
{:ok, hostname} ->
[
server_name_indication: hostname,
josevalim marked this conversation as resolved.
Show resolved Hide resolved
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
josevalim marked this conversation as resolved.
Show resolved Hide resolved
]

:error ->
raise ArgumentError, "expected :hostname on ssl: :verify_full"
end
end

defp default_ssl_opts(true, _opts), do: []
defp default_ssl_opts(false, _opts), do: nil

defp default_ssl_opts(unknown, _opts) do
raise ArgumentError,
"invalid :ssl option, expected a boolean or :verify_full, got: #{inspect(unknown)}"
end

defp endpoints(opts) do
port = opts[:port] || 5432

Expand Down Expand Up @@ -733,22 +764,22 @@ defmodule Postgrex.Protocol do
:ok
end

defp do_handshake(s, %{ssl: true} = status), do: ssl(s, status)
defp do_handshake(s, %{ssl: false} = status), do: startup(s, status)
defp do_handshake(s, %{ssl: nil} = status), do: startup(s, status)
defp do_handshake(s, %{ssl: ssl_opts} = status), do: ssl(s, status, ssl_opts)

## ssl

defp ssl(s, status) do
defp ssl(s, status, ssl_opts) do
case msg_send(s, msg_ssl_request(), "") do
:ok -> ssl_recv(s, status)
:ok -> ssl_recv(s, status, ssl_opts)
{:disconnect, _, _} = dis -> dis
end
end

defp ssl_recv(%{sock: {:gen_tcp, sock}} = s, status) do
defp ssl_recv(%{sock: {:gen_tcp, sock}} = s, status, ssl_opts) do
case :gen_tcp.recv(sock, 1, :infinity) do
{:ok, <<?S>>} ->
ssl_connect(s, status)
ssl_connect(s, status, ssl_opts)

{:ok, <<?N>>} ->
disconnect(s, %Postgrex.Error{message: "ssl not available"}, "")
Expand All @@ -770,8 +801,8 @@ defmodule Postgrex.Protocol do
end
end

defp ssl_connect(%{sock: {:gen_tcp, sock}, timeout: timeout} = s, status) do
case :ssl.connect(sock, status.opts[:ssl_opts] || [], timeout) do
defp ssl_connect(%{sock: {:gen_tcp, sock}, timeout: timeout} = s, status, ssl_opts) do
case :ssl.connect(sock, ssl_opts, timeout) do
{:ok, ssl_sock} ->
startup(%{s | sock: {:ssl, ssl_sock}}, status)

Expand Down
Loading