简单的Elixir SSL服务器-客户端连接

huangapple go评论61阅读模式
英文:

Simple SSL server-client connection in Elixir

问题

我正在研究一个玩具网络项目,想在服务器和客户端之间添加TLS层。我遇到了握手错误,正在尝试找出如何调试。

太长不看(TL;DR)可能是:“我应该向 :ssl.listen/2 传递哪些参数”,但这里有一个最小的示例。

首先,我使用 mix new tls_question 创建了一个新项目。

我在mix.exs中添加了 :crypto:ssl,如下所示:

def application do
    [
      extra_applications: [:logger, :crypto, :ssl]
    ]
end

然后,我使用以下命令生成了SSL证书:

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365

并将 key.pem 和 cert.pem 移到了项目文件夹中。

接下来,我有以下最小的程序:

defmodule TlsQuestion do
  @ip {127,0,0,1}
  @port 4343
  def main do
    :ssl.start()
    {:ok, listen_socket} = :ssl.listen(@port,
      [ certs_keys: [
          keyfile: "key.pem",
          certfile: "cert.pem",
          password: "CorrectHorseBatteryStaple"
        ],
        reuseaddr: true
      ])
    spawn(fn -> client() end)
    {:ok, accept_socket} = :ssl.transport_accept(listen_socket)
    {:ok, accept_socket} = :ssl.handshake(accept_socket)
    :ssl.send(accept_socket, "Hello World")
  end
  def client() do
    {:ok, connect_socket} = :ssl.connect(@ip, @port,
                              [verify: :verify_peer,
                              cacertfile: "cert.pem",
                              active: :once], :infinity)
    message = :ssl.recv(connect_socket, 0)
    IO.puts(message)
  end
end
TlsQuestion.main()

然后我调用 mix run

错误消息可能对某些人有所启发,但对我没有帮助

== Compilation error in file lib/tls_question.ex ==
** (exit) exited in: :gen_statem.call(#PID<0.164.0>, {:start, :infinity}, :infinity)
    ** (EXIT) an exception was raised:
        ** (FunctionClauseError) no function clause matching in :ssl_config.key_conf/1
            (ssl 10.8.7) ssl_config.erl:181: :ssl_config.key_conf({:keyfile, "key.pem"})
            (ssl 10.8.7) ssl_config.erl:72: :ssl_config.cert_key_pair/3
            (stdlib 4.2) lists.erl:1315: :lists.map/2
            (ssl 10.8.7) ssl_config.erl:56: :ssl_config.init_certs_keys/3
            (ssl 10.8.7) ssl_config.erl:51: :ssl_config.init/2
            (ssl 10.8.7) ssl_gen_statem.erl:164: :ssl_gen_statem.ssl_config/3
            (ssl 10.8.7) tls_connection.erl:150: :tls_connection.init/1
            (stdlib 4.2) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
    (stdlib 4.2) gen.erl:243: :gen.do_call/4
    (stdlib 4.2) gen_statem.erl:900: :gen_statem.call_dirty/4
    (ssl 10.8.7) ssl_gen_statem.erl:1239: :ssl_gen_statem.call/2
    (ssl 10.8.7) ssl_gen_statem.erl:234: :ssl_gen_statem.handshake/2
    lib/tls_question.ex:16: TlsQuestion.main/0

看起来是在证书和密钥文件方面出了问题?

我已将证书传递给客户端作为CA证书链(因为自签名证书是自身的证书链)。可能是这个问题吗?

英文:

I'm working on a toy networking project and I want to add a TLS layer between the server and the client. I'm getting handshake errors that I'm trying to figure out how to debug.

The TL;DR is probably: 'what arguments do I pass to :ssl.listen/2' but here is the minimal example.

First I create a new project with mix new tls_question.

I have added :crypto and :ssl to mix.exs like so:

def application do
    [
      extra_applications: [:logger, :crypto, :ssl]
    ]
end

I have then generated an SSL certificate with

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365

and moved key.pem and cert.pem into the project folder.

I then have the following minimal program

defmodule TlsQuestion do
  @ip {127,0,0,1}
  @port 4343
  def main do
    :ssl.start()
    {:ok, listen_socket} = :ssl.listen(@port,
      [ certs_keys: [
          keyfile: "key.pem",
          certfile: "cert.pem",
          password: "CorrectHorseBatteryStaple"
        ],
        reuseaddr: true
      ])
    spawn(fn -> client() end)
    {:ok, accept_socket} = :ssl.transport_accept(listen_socket)
    {:ok, accept_socket} = :ssl.handshake(accept_socket)
    :ssl.send(accept_socket, "Hello World")
  end
  def client() do
    {:ok, connect_socket} = :ssl.connect(@ip, @port,
                              [verify: :verify_peer,
                              cacertfile: "cert.pem",
                              active: :once], :infinity)
    message = :ssl.recv(connect_socket, 0)
    IO.puts(message)
  end
end
TlsQuestion.main()

From which I call mix run.

The error message might be enlightening for some but hasn't helped me

== Compilation error in file lib/tls_question.ex ==
** (exit) exited in: :gen_statem.call(#PID<0.164.0>, {:start, :infinity}, :infinity)
    ** (EXIT) an exception was raised:
        ** (FunctionClauseError) no function clause matching in :ssl_config.key_conf/1
            (ssl 10.8.7) ssl_config.erl:181: :ssl_config.key_conf({:keyfile, "key.pem"})
            (ssl 10.8.7) ssl_config.erl:72: :ssl_config.cert_key_pair/3
            (stdlib 4.2) lists.erl:1315: :lists.map/2
            (ssl 10.8.7) ssl_config.erl:56: :ssl_config.init_certs_keys/3
            (ssl 10.8.7) ssl_config.erl:51: :ssl_config.init/2
            (ssl 10.8.7) ssl_gen_statem.erl:164: :ssl_gen_statem.ssl_config/3
            (ssl 10.8.7) tls_connection.erl:150: :tls_connection.init/1
            (stdlib 4.2) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
    (stdlib 4.2) gen.erl:243: :gen.do_call/4
    (stdlib 4.2) gen_statem.erl:900: :gen_statem.call_dirty/4
    (ssl 10.8.7) ssl_gen_statem.erl:1239: :ssl_gen_statem.call/2
    (ssl 10.8.7) ssl_gen_statem.erl:234: :ssl_gen_statem.handshake/2
    lib/tls_question.ex:16: TlsQuestion.main/0

It looks like it's complaining about something I'm doing with the certificate and key files?

I've passed the certificate to the client as the CA certificate chain (since a self-signed certificate is its own certificate chain). Could that be the issue?

答案1

得分: 1

> TL;DR 大概是:'我应该传递给 :ssl.listen/2 的参数是什么'

listen(Port, Options) -> {ok, ListenSocket} | {error, reason()}

`Port` 被定义为整数:
 
0...65535

`Options` 被定义为列表: 

Options = [tls_server_option()]

tls_server_option() = 
    server_option() |
    common_option() |
    socket_option() |
    transport_option()

common_option() = ...| {certs_keys, certs_keys()} | ...

certs_keys() = [cert_key_conf()]

cert_key_conf() = 
    #{cert => cert(),
      key => key(),
      certfile => cert_pem(),
      keyfile => key_pem(),
      password => key_pem_password()}

注意,`cert_key_conf()` 是一个 Erlang 映射,在 Elixir 中会有如下结构:

{:ok, listen_socket} = :ssl.listen(@port,
    [ certs_keys: [%{
     
        }],
        reuseaddr: true
      ]) 

继续类型描述:

cert_pem() = file:filename() = string() => 整数列表
key_pem() = filename() = string() => 整数列表
key_pem_password() = io_list() => 可能包含整数和/或二进制的嵌套列表

在 Erlang 中,string() 类型是整数列表。

                    Elixir             Erlang
                    ------             ------
整数列表:           单引号            双引号
二进制:             双引号,          语法 <<1,34,97>> 或 <<97,98,99>>

从上面列出的类型规范的底部开始,逐渐替代,你会得到:

{:ok, listen_socket} = :ssl.listen(@port,
    [ certs_keys: [%{
        keyfile: 'key.pem',
        certfile: 'cert.pem',
        password: 'CorrectHorseBatteryStaple'
      }],
      reuseaddr: true
    ])
我不确定你是否可以省略映射中的 `cert:` 和 `key:`。另外,文档中没有将 `reuseaddr` 列为 `Options` 列表中的有效 2 元组。
英文:

> The TL;DR is probably: 'what arguments do I pass to :ssl.listen/2'

listen(Port, Options) -&gt; {ok, ListenSocket} | {error, reason()}

Port is defined to be an integer:

0...65535

Options is defined to be a list:

Options = [tls_server_option()]

tls_server_option() = 
    server_option() |
    common_option() |
    socket_option() |
    transport_option()

common_option() = ...| {certs_keys, certs_keys()} | ...

certs_keys() = [cert_key_conf()]

cert_key_conf() = 
    #{cert =&gt; cert(),
      key =&gt; key(),
      certfile =&gt; cert_pem(),
      keyfile =&gt; key_pem(),
      password =&gt; key_pem_password()}

Note that cert_key_conf() is an erlang map, giving you this structure in elixir:

  {:ok, listen_socket} = :ssl.listen(@port,
      [ certs_keys: [%{

         
        }],
        reuseaddr: true
      ]) 

Continuing with the type descriptions:

 cert_pem() = file:filename() = string() =&gt; list of integers
 key_pem() = filename() = string() =&gt; list of integers
 key_pem_pasword() = io_list() =&gt; possibly nested list of integers and/or binaries

In erlang, the string() type is a list of integers.

                       elixir             erlang
                       ------             ------
 list of itegers:      single quotes      double quotes
 binaries:             double quotes,     the syntax &lt;&lt;1,34,97&gt;&gt;
                       or &lt;&lt;97,98,99&gt;&gt;

Starting at the bottom of the type specifications listed above and substituting upwards, gives you:

  {:ok, listen_socket} = :ssl.listen(@port,
      [ certs_keys: [%{
          keyfile: &#39;key.pem&#39;,
          certfile: &#39;cert.pem&#39;,
          password: &#39;CorrectHorseBatteryStaple&#39;
        }],
        reuseaddr: true
      ])

I'm not sure whether you can omit the keys cert: and key: in the map. Also, the docs don't list reuseaddr as a valid 2-tuple in the Options list.

huangapple
  • 本文由 发表于 2023年2月24日 17:26:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/75554773.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定