在libuv中使用openssl建立ssl连接

在libuv中使用openssl建立ssl连接


@(blogs)
使用openssl进行加密通信时,通常是先建立socket连接,然后使用SSL_XXX系列函数在普通socket之上建立安全连接,然后发送和接收数据。openssl的这些函数可以支持底层的socket是非阻塞模式的。但当将openssl和libuv进行结合时,会遇到一些问题:
1.
openssl在进行数据读写之前,需要进行若干次“握手”。“握手”中会有若干次的数据读写。这个在普通的socket连接中是没有的,在libuv的回调函数中需要进行处理。
2.
由于openssl需要对数据进行加密和解密,当openssl读数据的时候,有可能会出现虽然加密的数据已经全部接收到本地了,但仍需要和远端进行通信来进一步确认如何解密数据。(会不会出现这个过程不太确定。。。网上有文章说可能会出现,我也没有仔细研究过openssl的实现细节,所有宁可信其有,不可信其无吧。。。)

解决这两个问题的思路是一样的,将openssl看做是一个数据过滤器,可参考这篇文章

在和libuv结合时,openssl不能直接对socket进行读写,因为对socket的读写操作已经被libuv完全封装了。不过openssl可以通过BIO进行读写数据。也就是说,需要准备两个BIO,一个用于存储openssl加密好的数据,一个用于存储接收到的加密数据以备openssl解密。这个操作直接调用下面这个函数即可完成:

?





1

void SSL_set_bio(SSL *ssl, BIO *rbio, BIO *wbio);

设置好这两个BIO之后,SSL_XXX系列函数的所有操作都是针对这两个BIO,不再直接和socket打交道。这样对socket的操作就可以委托给libuv了。

对于写数据到socket,直接将数据丢给libuv就可以了。但读数据的时候会略微麻烦一些。在创建安全连接的时候openssl需要多次“握手”操作,也就是需要朝socket读写几次数据。这个过程需要在libuv的read_cb函数里处理。也就是说在libuv的read_cb函数需要区分要读的数据是“握手”时的数据还是真正通信读取的数据。这个判断通过

?





1

int SSL_is_init_finished(SSL *ssl);

函数实现,也就是判断openssl是否完成了安全连接的初始化。

对于前面提到的第二个问题,openssl提供了解决这个问题的机制。SSL_XXX系列函数的返回值可以通过

?





1

int SSL_get_error(const
SSL *ssl, int
ret);

来获取其具体的含义,其中两个重要的返回结果是SSLERRORWANTREAD和SSLERRORWANTWRITE。在调用SSL_connect,SSL_read和SSL_write时,openssl可能需要读取更多的数据或者发送数据,这两个返回值表明openssl的意图。注意:这三个函数都有可能返回这两个值。也就是说在读数据的时候可能需要写数据,在写数据的时候可能需要读数据。

啰啰嗦嗦说了这么多,上代码才是王道。以下代码只是示意,并不能直接编译运行^v^。首先,声明变量:

?





1

2

3

4

5

SSL *ssl;

SSL_ctx *ssl_ctx;

BIO *read_bio;

BIO *write_bio;

uv_tcp_t *con

在libuv的on_connect_cb函数中初始化openssl并开始“握手”。

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

void
on_connect_cb(uv_connect_t *req, int
status)

{

    //设置数据读取的回调函数<br>

    uv_read_start((uv_stream_t*)con, on_alloc_cb, on_read_cb);

    ssl = SSL_new(ssl_ctx);

    read_bio = BIO_new(BIO_s_mem());

    write_bio = BIO_new(BIO_s_mem());

    SSL_set_bio(ssl, read_bio, write_bio);

    SSL_set_connect_state(ssl);     // 这是个客户端连接

    int
ret = SSL_connect(ssl);     // 开始握手。这个函数仅仅是将数据写如了BIO缓存,并没有发送到socket上。

    write_bio_to_socket();          // 如果有,将wirte BIO中的数据写入socket。(具体定义见后面代码)

    if
(ret != 1) {

        // connect出错了,看看具体什么问题。

        int
err = SSL_get_error(ssl, ret);

        if
(err == SSL_ERROR_WANT_READ) {

            // 在read回调函数中读取数据

        } else
if (err == SSL_ERROR_WANT_WRITE) {

            write_bio_to_socket();    // 将write BIO中的数据发送出去

        }

    }

}

真正的重头戏是在on_read_cb中。

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

void
read_cb(uv_stream_t* stream, ssize_t nread, const
uv_buf_t *buf)

{

    if
(nread == UV_EOF) {

        // 已经读完了所有的数据

        read_data_after_handshake();

        return;

    } else
{

        // 读取数据到BIO中。buf中的数据是加密数据,将其放到BIO中,让openssl将其解码。

        BIO_write(read_bio, buf -> base, nread);

        if
(!SSL_is_init_finished(ssl)) {

            // 我们还没有完成ssl的初始化,继续进行握手。

            int
ret = SSL_connect(ssl);

            write_bio_to_socket();

            if
(ret != 1) {

                int
err = SSL_get_error(ssl, ret);

                if
(err == SSL_ERROR_WANT_READ) {

                    // 在read回调函数中读取数据

                } else
if (err == SSL_ERROR_WANT_WRITE) {

                    write_bio_to_socket();

                }

            } else
{

                // 握手完成,发送数据。

                send_data_after_handshake();

            }

        } else
{

            // ssl已经初始化好了, 我们可以从BIO中读取已经解密的数据。

            read_data_after_handshake();

        }

    }

    free(buf -> base);

}

下面来看看write_bio_to_socket()的实现,这个函数很简单,就是将write_bio中的数据丢给libuv进行发送。

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

void write_bio_to_socket()

{

    char
buf[1024];

    int
hasread = BIO_read(write_bio, buf, sizeof(buf));

    if
(hasread <= 0) {

        // 无数据可写。

        return;

    }

    uv_write_t *wreq = (uv_write_t*)malloc(sizeof(uv_write_t));

    char
*tmp = malloc(hasread);

    memcpy(tmp, buf, hasread);

    uv_buf_t *bufs = (uv_buf_t*)malloc(sizeof(uv_buf_t) * 1);

    bufs[0].base = tmp;

    bufs[0].len = hasread;

    uv_write(wreq, (uv_stream_t*)con, bufs, 1, on_write_cb); // 记得在on_write_cb中释放这里分配的内存。

}

BIO_read有可能一次读取不完write_bio中的数据,所以这个地方需要一个循环多次调用BIO_read直到数据全部读完。这里为了简单就只读一次了^v^。

send_data_after_handshake函数也很简单,就是将需要发送的数据写入wirte_bio中然后丢给libuv发送,还需要处理有数据要读取的情况。

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

void send_data_after_handshake()

{

    int
ret = SSL_write(ssl, data, data_len);   // data中存放了要发送的数据

    if
(ret > 0) {

        // 写入socket

        write_bio_to_socket();

    } else
if (ret == 0) {

        // 连接关闭了??

        uv_close((uv_handle_t*)con, on_close_cb);

    } else
{

        // 需要读取或写入数据。

        int
err = SSL_get_error(client -> ssl, ret);

        if
(err == SSL_ERROR_WANT_READ) {

            // 在read回调中处理(其实如果有数据要读时什么都不要,等read回调就行了。。。)

        } else
if (err == SSL_ERROR_WANT_WRITE) {

            write_bio_to_socket();

        }

    }

}

最后是read_data_after_handshake,这个函数将openlls解密好的数据读取出来,同时还需要处理在读取数据的时候需要写入数据的问题。

?





1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

void read_data_after_handshake()

{

    char
buf[1024];

    memset(buf, ‘\0‘, sizeof(buf));

    int
ret = SSL_read(ssl, buf, sizeof(buf));

    if
(ret < 0) {

        int
err = SSL_get_error(client -> ssl, ret);

        if
(err == SSL_ERROR_WANT_READ) {

            // 在read回调函数中读取数据

        } else
if (err == SSL_ERROR_WANT_WRITE) {

            // 有数据要写,将write BIO中的数据发送出去

            write_bio_to_socket();

        }

    }

    // 解密好的数据就存放在buf中了。当然,这个地方也可能需要多次调用SSL_read来讲所有数据都读出来。

}

以上就是全部的示例代码了。

关于这个openssl和libuv结合使用的思路还没有进行严格的测试,我也只是在工程中初步测试了一下可以走通。对于一些细节的处理还不是很到位。这里只是提供了一个libuv和openssl结合的思路,如果有任何问题,欢迎指正。^v^~

在libuv中使用openssl建立ssl连接,布布扣,bubuko.com

时间: 2024-11-03 01:33:52

在libuv中使用openssl建立ssl连接的相关文章

一个实际问题分析及解决之四:通过IBM的JDK建立SSL连接

接前面内容,这里主要谈我们实现一开始描述的实际的技术需求的过程. 我们在建立与服务器的SonicMQ的SSL连接时,用了第三方jar去完成握手,而该第三方包采用的是标准的Java接口方式,即SSLContext.getInstance().这也很容易理解,因为第三方jar的提供方并不能假设用户一定是在websphere环境下使用该第三方jar,所以采用标准方式更为可取. 我们的碰到的最大问题就是该jar在Oracle的jdk上可以工作,而在IBM的jdk都不能工作,出现SSLHandShake

使用wget提示无法建立SSL连接

wget 下载URL 提示无法建立SSL连接 解决方法: 原命令上加上" --no-check-certificate" 这是因为wget在使用HTTPS协议时,默认会去验证网站的证书,而这个证书验证经常会失败.加上"--no-check-certificate"选项,就能排除掉这个错误. 下载成功!

wget时无法建立ssl连接

[[email protected] ~]# wget https://bootstrap.pypa.io/ez_setup.py -O - | python - --user --2014-09-15 22:06:51--  https://bootstrap.pypa.io/ez_setup.py 正在解析主机 bootstrap.pypa.io... 103.245.222.175 Connecting to bootstrap.pypa.io|103.245.222.175|:443..

MySQL建立SSL连接警告

WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with e

java连接数据库报了ssl连接的警告

警告内容:Establishing SSL connection without server's identity verification is not recommended(不建议在没有服务器身份验证的情况下建立SSL连接) 解决方法:在连接数据库的时候加上:useSSL=true例子如下 jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&useSSL=true(mysql例子) jdbc:oracle:thin:@127.0

驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接

由于项目中必须得用JDK6来作为Java环境,于是连接SQLServer时出现了com.microsoft.sqlserver.jdbc.SQLServerException: 驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接.错误:"Java.lang.RuntimeException: Could not generate DH keypair".这样的错误. 这个在JDK7中是修复了的,所以如果项目允许的话就换成JDK7吧,但有些只能用JDK6

利用openssl实现私有CA以及mysql服务器的ssl连接的配置

利用openssl实现私有CA以及mysql服务器的ssl连接的配置 一.CA简介 CA 也拥有一个证书(内含公钥和私钥).网上的公众用户通过验证 CA 的签字从而信任 CA ,任何人都可以得到 CA 的证书(含公钥),用以验证它所签发的证书. 如果用户想得到一份属于自己的证书,他应先向 CA 提出申请.在 CA 判明申请者的身份后,便为他分配一个公钥,并且 CA 将该公钥与申请者的身份信息绑在一起,并为之签字后,便形成证书发给申请者. 如果一个用户想鉴别另一个证书的真伪,他就用 CA 的公钥对

System.getProperty()引起的悲剧--您的主机中的软件中止了一个已建立的连接

我已无法形容此刻我的心情.. 本来是已经写好的netty5的demo程序,server和client之间创建tcp长连接的..然后随便传点数据的简单demo..然后今天试了一下tcp粘包的例子,用到了System.getProperty()方法,悲剧就从此产生了..改好了程序一运行,服务器端正常启动,然后client端怎么也起不来,一启动就自动退出,完善了一下client端的异常捕获,然后就能偶尔报出些异常信息: 您的主机中的软件中止了一个已建立的连接 恰巧今天装了一些新软件和插件,挨个全都卸载

Caused by: java.io.IOException: 您的主机中的软件中止了一个已建立的连接。

异常详情 2017-07-16 10:55:26,218 ERROR [500.jsp] - java.io.IOException: 你的主机中的软件中止了一个已建立的连接. org.apache.catalina.connector.ClientAbortException: java.io.IOException: 你的主机中的软件中止了一个已建立的连接. at org.apache.catalina.connector.OutputBuffer.realWriteBytes(Output