spice下通道OpenSSL加密的过程和加密数据传输过程解密

本文主要讲解Spice内部是如何使用OpenSSl对各大通道进行加密的。
转载注明本网址:http://blog.csdn.net/hubbybob1/article/details/53787455
转载清注明出处
一,环境配置
1.首先要了解OpenSSL的过程是什么样子的,首先需要在你的Linux环境下安装openssl
地址:https://www.openssl.org/下载openssl,下载完成后安装:

./configure
make
make install 
which openssl 查看安装
结果为:/usr/bin/openssl 表示安装成功

2.生成openssl的ca证书
可以参考网址https://www.spice-space.org/page/SSLConnection
通过一下脚本就可以生成CA证书如下:

#!/bin/bash
SERVER_KEY=server-key.pem
# creating a key for our ca 生成证书的加密长度为1024
if [ ! -e ca-key.pem ]; then
    openssl genrsa -des3 -out ca-key.pem 1024
fi
# creating a ca  生成证书的有效时长为1095天,关键字为 -subj "/C=IL/L=Raanana/O=Red Hat/CN=my CA"

if [ ! -e ca-cert.pem ]; then
    openssl req -new -x509 -days 1095 -key ca-key.pem -out ca-cert.pem -utf8 -subj "/C=IL/L=Raanana/O=Red Hat/CN=my CA"
fi
# create server key
if [ ! -e $SERVER_KEY ]; then
    openssl genrsa -out $SERVER_KEY 1024
fi
# create a certificate signing request (csr)
if [ ! -e server-key.csr ]; then
    openssl req -new -key $SERVER_KEY -out server-key.csr -utf8 -subj "/C=IL/L=Raanana/O=Red Hat/CN=my server"
fi
# signing our server certificate with this ca
if [ ! -e server-cert.pem ]; then
    openssl x509 -req -days 1095 -in server-key.csr -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem
fi
# now create a key that doesn't require a passphrase
openssl rsa -in $SERVER_KEY -out $SERVER_KEY.insecure
mv $SERVER_KEY $SERVER_KEY.secure
mv $SERVER_KEY.insecure $SERVER_KEY
# show the results (no other effect)
openssl rsa -noout -text -in $SERVER_KEY
openssl rsa -noout -text -in ca-key.pem
openssl req -noout -text -in server-key.csr
openssl x509 -noout -text -in server-cert.pem
openssl x509 -noout -text -in ca-cert.pem

将以上脚本复制到key.sh文件中并使用命令 ./key.sh 运行,如下图所示:
这里写图片描述
生成6个文件,其中第一个文件ca-cert.pem就是认证文件,其证书如下所示:
这里写图片描述
3.配置
把以上所生成的文档目录整体copy到服务器下面的一个固定目录下例如放到/data下,并且在启动qemu脚本时例如:

sudo -i qemu-system-x86_64 -machine pc -name testnames -drive file=/data/win7_sp4_bhw1.img -soundhw hda -m 1024 -smp cores=2,threads=2,sockets=1 -device qxl-vga,vgamem_mb=128 \
-monitor tcp:0.0.0.0:44000,server,nowait \
-spice port=48001,tls-port=47001,disable-ticketing,x509-dir=/data/test,tls-channel=main,tls-channel=inputs,streaming-video=filter,playback-compression=off,seamless-migration=on \
-enable-kvm -full-screen -cpu host -device virtio-serial -chardev spicevmc,id=vdagent,debug=0,name=vdagent -usb -device usb-tablet,id=input0 -device usb-ehci,id=ehci -chardev spicevmc,name=usbredir,id=usbredirchardev0 -device usb-redir,chardev=usbredirchardev0,id=usbredirdev0,debug=3 -localtime 

其中关于openssl的相关参数为:

tls-port=47001,disable-ticketing,x509-dir=/data/CD02,tls-channel=main,tls-channel=inputs,
//所使用TLS端口号为47001,x509-dir表示存储证书的位置,tls-channel为加密通道,在次加密的是main通道和inputs通道。

那么客户端只需要文件ca-cert.pem就够了,如果是linux端的客户端,使用一下命令:

spicy --spice-ca-file=/你的ca-cert.pem路径 spice://服务器IP地址?tls-port=47000 --spice-host-subject="C=IL, L=Raanana, O=Red Hat, CN=my server"
或者
remote-view --spice-ca-file=/你的ca-cert.pem路径 spice://服务器IP地址?tls-port=47000 --spice-host-subject="C=IL, L=Raanana, O=Red Hat, CN=my server"

使用第一条spicy命令会出现下图:
这里写图片描述
填写IP地址和TLS Port,点击connect就可以连接到服务器虚拟机了
这里写图片描述
使用remote-view命令就会直接链接完成。
针对android的客户端操作如下:
安装spice客户端软件比bVNC,下载地址为:http://download.csdn.net/detail/hubbybob1/9738931
这个工程的源码下载为:https://github.com/iiordanov/remote-desktop-clients
安装和加载如下图所示:
这里写图片描述
这里写图片描述
这里写图片描述
到此所有的认证经过就完成了。
二、代码分析
主要从客户端和服务器端进行分析
A.客户端代码分析
分析代码可以为Android下的代码也可以为spice-gtk代码
spice-gtk代码下载地址为:http://download.csdn.net/detail/hubbybob1/9738967
spice内部openssl相关的编程主要在/spice-gk-0.30/src/spice-channel.c,需要了解基本的openssl编程基础。
1.在spice-channel.c文件内对spice类进行初始化的函数的最后,对openssl进行了初始化和启动

即函数:
static void spice_channel_class_int(SpiceChannelClass *klass)内最后嗲用
SSL_library_init();//初始化opennssl库  
SSL_load_error_strings();//加载错误码信息

2.设置主支持协议的方法
在spice携程内部,定义了协议类型所不支持的类型:

即在函数:
static void *spice_channel_coroutine(void *data)内部定义:
long ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;//不支持sslv2和sslv3
c->ctx = SSL_CTX_new(SSLv23_method());//设置协议方法
SSL_CTX_set_options(c->ctx, ssl_options);//设置协议类型

3.加载证书
在设置完协议类型后就会加载证书:

rc = spice_channel_load_ca(channel);
//在这个函数中就可以调用下面的函数
rc = SSL_CTX_load_verify_locations(c->ctx, ca_file, NULL);//加载本地的认证证书
而这个证书的位置就是ca_file在文件spice-option.c内有定义
ca_file = g_build_filename(homedir,".spicec","spice_truststore.pem",NULL);//如果在参数行不指定证书位置,/root/.spicec/spice_truststore.pem,就是默认的证书

4.获取服务器的加密算法并设置客户端的加密
在认证通过后就是开始数据的机密传输:

const gchar *ciphers = spice_session_get_ciphers(c->session);//获取服务器的加密算法
rc = SSL_CTX_set_cipher_list(c->ctx, ciphers);//设置加密套件

5.建立SSL域字段并创建BIO套接字流

c->ssl = SSL_new(c->ctx);
BIO *bio = bio_new_gsocket(c->sock);//创建BIO套接字
SSL_set_bio(c->ssl, bio, bio);

6.链接服务器

rc = SSL_connect(c->ssl);//该函数只在客户端调用,对应socket的链接,用来进行SSL握手,而在服务器端调用的是SSL_recieve函数,与之对应

7.数据的加密读写
但是spice的某个通道使用了openssl进行认证后,其数据的传输就不再走原来的spice方法传输了,而是通过openssl的加密方法进行传输,其传输的函数如下:

//spice的写函数在函数内
static void spice_channel_flush_wire(SpiceChannel *channel,
                                     const void *data,
                                     size_t datalen);//这个函数内部存在写操作如下
if (c->tls) {//当设置openssl数据认证时调用
            ret = SSL_write(c->ssl, ptr+offset, datalen-offset);//数据写
            if (ret < 0) {
                ret = SSL_get_error(c->ssl, ret);//获取错误码
                if (ret == SSL_ERROR_WANT_READ)
                    cond |= G_IO_IN;
                if (ret == SSL_ERROR_WANT_WRITE)
                    cond |= G_IO_OUT;
                ret = -1;
            }
        } else //没有设置认证时默认数据为普通写入
//同样在spice客户端读取数据的函数内,即
static int spice_channel_read_wire(SpiceChannel *channel, void *data, size_t len);//spice内部读取数据接口
if (c->tls) {//当设置openssl数据认证时调用
        ret = SSL_read(c->ssl, data, len);
        if (ret < 0) {
            ret = SSL_get_error(c->ssl, ret);//openssl数据读取
            if (ret == SSL_ERROR_WANT_READ)
                cond |= G_IO_IN;
            if (ret == SSL_ERROR_WANT_WRITE)
                cond |= G_IO_OUT;
            ret = -1;
        }
    } else //正常读取

8.openssl客户端编程的结束
其结束是需要与上面的函数进行对应的释放

SSL_free(c->ssl);//针对函数SSL_new进行释放
SSL_CTX_free(c->ctx); //针对函数SSL_CTX_new进行释放

到此客户端的代码分析完毕,其实spice内的openssl编程是最基本的方法,没有太多的步骤。
B.服务器端openssl代码分析
服务器的openssl代码与客户端的代码类似
可以下载spice-server源码尽心分析跟踪,下载地址为:http://download.csdn.net/detail/hubbybob1/9739910
1.openssl的服务器端编程在spice-server下的./server/reds.c文件内:

static int reds_init_ssl(void)
{
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
    const SSL_METHOD *ssl_method;
#else
    SSL_METHOD *ssl_method;
#endif
    int return_code;
    /* When some other SSL/TLS version becomes obsolete, add it to this
     * variable. */
    long ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;//ssl不支持的协议

    /* Global system initialization*/
    SSL_library_init();//初始化openssl算法库
    SSL_load_error_strings();//加载错误信息码

    /* Create our context*/
    /* SSLv23_method() handles TLSv1.x in addition to SSLv2/v3 */
    ssl_method = SSLv23_method();//设置协议链接方法
    reds->ctx = SSL_CTX_new(ssl_method);//申请SSL会话环境
    if (!reds->ctx) {
        spice_warning("Could not allocate new SSL context");
        return -1;
    }

    /* Limit connection to TLSv1 only */
#ifdef SSL_OP_NO_COMPRESSION
    ssl_options |= SSL_OP_NO_COMPRESSION;
#endif
    SSL_CTX_set_options(reds->ctx, ssl_options);//通过掩码给ctx设置操作

    /* Load our keys and certificates*/
    return_code = SSL_CTX_use_certificate_chain_file(reds->ctx, ssl_parameters.certs_file);//为ssl会话加载证书所属的证书链
    if (return_code == 1) {
        spice_info("Loaded certificates from %s", ssl_parameters.certs_file);
    } else {
        spice_warning("Could not load certificates from %s", ssl_parameters.certs_file);
        return -1;
    }

    SSL_CTX_set_default_passwd_cb(reds->ctx, ssl_password_cb);//设置读取私钥密码

    return_code = SSL_CTX_use_PrivateKey_file(reds->ctx, ssl_parameters.private_key_file,
                                              SSL_FILETYPE_PEM);//为SSL会话加载私钥
    if (return_code == 1) {
        spice_info("Using private key from %s", ssl_parameters.private_key_file);
    } else {
        spice_warning("Could not use private key file");
        return -1;
    }

    /* Load the CAs we trust*/
    return_code = SSL_CTX_load_verify_locations(reds->ctx, ssl_parameters.ca_certificate_file, 0);//为ssl会话加载所受信用证书列表
    if (return_code == 1) {
        spice_info("Loaded CA certificates from %s", ssl_parameters.ca_certificate_file);
    } else {
        spice_warning("Could not use CA file %s", ssl_parameters.ca_certificate_file);
        return -1;
    }

#if (OPENSSL_VERSION_NUMBER < 0x00905100L)
    SSL_CTX_set_verify_depth(reds->ctx, 1);//为证书链设置限度
#endif

    if (strlen(ssl_parameters.dh_key_file) > 0) {
        if (load_dh_params(reds->ctx, ssl_parameters.dh_key_file) < 0) {
            return -1;
        }
    }

    SSL_CTX_set_session_id_context(reds->ctx, (const unsigned char *)"SPICE", 5);//设置可以被ssl重复利用
    if (strlen(ssl_parameters.ciphersuite) > 0) {
        if (!SSL_CTX_set_cipher_list(reds->ctx, ssl_parameters.ciphersuite)) {
            return -1;
        }
    }

    openssl_thread_setup();

#ifndef SSL_OP_NO_COMPRESSION
    STACK *cmp_stack = SSL_COMP_get_compression_methods();
    sk_zero(cmp_stack);
#endif

    return 0;
}

那么我们可以看到有一个结构体定义,对应了上一节使用openssl脚本所生成的6个文件

typedef struct RedSSLParameters {//ssl参数文件
    char keyfile_password[256];
    char certs_file[256];
    char private_key_file[256];
    char ca_certificate_file[256];
    char dh_key_file[256];
    char ciphersuite[256];
} RedSSLParameters;

2.判断数据流格式为ssl
在reds.c 有一个关键的函数就是判断是否为ssl数据流,这个函数在文件reds_stream.c内定义为

bool reds_stream_is_ssl(RedsStream *stream)
{
    return (stream->priv->ssl != NULL);
}
//在此spice需要初始化ssl数据流

3.在初始化完毕后需要和客户端openssl进行握手

int reds_stream_enable_ssl(RedsStream *stream, SSL_CTX *ctx)
{
    BIO *sbio;

    // Handle SSL handshaking
    if (!(sbio = BIO_new_socket(stream->socket, BIO_NOCLOSE))) {//创建BIOsocket
        spice_warning("could not allocate ssl bio socket");
        return REDS_STREAM_SSL_STATUS_ERROR;
    }

    stream->priv->ssl = SSL_new(ctx);//创建对话环境
    if (!stream->priv->ssl) {
        spice_warning("could not allocate ssl context");
        BIO_free(sbio);
        return REDS_STREAM_SSL_STATUS_ERROR;
    }

    SSL_set_bio(stream->priv->ssl, sbio, sbio);

    stream->priv->write = stream_ssl_write_cb;//设置stream写数据回到函数,内部调用SSL_write函数
    stream->priv->read = stream_ssl_read_cb;//设置stream读数据回到函数,内部调用SSL_read函数

    stream->priv->writev = NULL;

    return reds_stream_ssl_accept(stream);//与客户端进行握手,内部调用SSL_accept函数
}

到此spice的客户端与服务器端的openssl的代码分析完毕,详细的内容可以参考openssl编程说明,网上有很多资料,如果想要自己了解spice的openssl的过程可以下载代码进行跟踪学习。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值