SPICE源码分析(十二):网络传输与安全机制

本文分析SPICE的网络传输层实现,包括SSL/TLS加密、SASL认证和WebSocket支持。

背景与安全需求

SPICE传输敏感的桌面数据,安全性至关重要:

  1. 数据加密:防止网络窃听
  2. 身份认证:防止未授权访问
  3. 传输灵活性:支持多种网络环境

SPICE通过分层设计支持多种安全机制的组合。

整体架构

RedStream 网络流

核心结构

RedStream封装了底层socket,提供统一的读写接口,支持多种传输层。

cpp 复制代码
// red-stream.h
struct RedStream {
    int socket;           // 底层socket文件描述符
    SpiceWatch *watch;    // 事件监视器
    RedStreamPrivate *priv;  // 私有数据
};

// red-stream.cpp
struct RedStreamPrivate {
    // ===== SSL支持 =====
    SSL *ssl;             // OpenSSL连接对象
    
    // ===== SASL支持 =====
#if HAVE_SASL
    RedSASL sasl;         // SASL认证状态
#endif
    
    // ===== WebSocket支持 =====
    RedsWebSocket *ws;    // WebSocket状态
    
    // ===== 异步读取 =====
    AsyncRead async_read; // 异步读取状态机
    
    // ===== 连接信息 =====
    SpiceChannelEventInfo* info;  // 连接事件信息
    
    // ===== Cork优化 =====
    bool use_cork;        // 是否使用TCP_CORK
    bool corked;          // 当前cork状态
    
    // ===== 函数指针(传输层切换) =====
    ssize_t (*read)(RedStream *s, void *buf, size_t nbyte);
    ssize_t (*write)(RedStream *s, const void *buf, size_t nbyte);
    ssize_t (*writev)(RedStream *s, const struct iovec *iov, int iovcnt);
    
    // ===== 关联 =====
    RedsState *reds;
    SpiceCoreInterfaceInternal *core;
};

传输层切换机制:

通过函数指针动态切换传输层:

函数指针 传输层 说明
stream_socket_read/write 普通TCP 默认传输
stream_ssl_read/write SSL加密 TLS加密传输
stream_sasl_read/write SASL层 SASL安全层
stream_websocket_read/write WebSocket 浏览器支持

SSL/TLS 加密

SSL 初始化

cpp 复制代码
// reds.cpp
static int reds_init_ssl(RedsState *reds)
{
    // ===== 安全选项配置 =====
    long ssl_options = 
        SSL_OP_NO_SSLv2 |      // 禁用SSLv2(已废弃)
        SSL_OP_NO_SSLv3 |      // 禁用SSLv3(POODLE漏洞)
        SSL_OP_NO_TLSv1 |      // 禁用TLSv1.0(弱)
        SSL_OP_NO_COMPRESSION; // 禁用压缩(CRIME攻击)
    
#ifdef SSL_OP_NO_RENEGOTIATION
    // 禁用重协商(防止DoS和降级攻击)
    ssl_options |= SSL_OP_NO_RENEGOTIATION;
#endif
    
    // ===== 全局初始化 =====
    openssl_global_init();
    
    // ===== 创建SSL上下文 =====
    const SSL_METHOD *ssl_method = TLS_method();
    reds->ctx = SSL_CTX_new(ssl_method);
    
    // 应用安全选项
    SSL_CTX_set_options(reds->ctx, ssl_options);
    
    // ===== ECDH自动选择 =====
#if HAVE_DECL_SSL_CTX_SET_ECDH_AUTO
    SSL_CTX_set_ecdh_auto(reds->ctx, 1);
#endif
    
    // ===== 加载证书 =====
    // 证书链文件
    SSL_CTX_use_certificate_chain_file(
        reds->ctx, 
        reds->config->ssl_parameters.certs_file);
    
    // ===== 加载私钥 =====
    SSL_CTX_set_default_passwd_cb(reds->ctx, ssl_password_cb);
    SSL_CTX_use_PrivateKey_file(
        reds->ctx, 
        reds->config->ssl_parameters.private_key_file,
        SSL_FILETYPE_PEM);
    
    // ===== 加载CA证书(用于验证客户端) =====
    SSL_CTX_load_verify_locations(
        reds->ctx, 
        reds->config->ssl_parameters.ca_certificate_file, 
        nullptr);
    
    // ===== 加载DH参数(密钥交换) =====
    if (strlen(reds->config->ssl_parameters.dh_key_file) > 0) {
        load_dh_params(reds->ctx, 
                       reds->config->ssl_parameters.dh_key_file);
    }
    
    // ===== 设置会话ID上下文 =====
    SSL_CTX_set_session_id_context(
        reds->ctx, 
        (const unsigned char *)"SPICE", 5);
    
    // ===== 自定义密码套件(可选) =====
    if (strlen(reds->config->ssl_parameters.ciphersuite) > 0) {
        SSL_CTX_set_cipher_list(
            reds->ctx, 
            reds->config->ssl_parameters.ciphersuite);
    }
    
    return 0;
}

安全配置说明:

配置 目的
禁用SSLv2/v3 已知存在安全漏洞
禁用TLSv1.0 不够安全,推荐TLS1.2+
禁用压缩 防止CRIME攻击
禁用重协商 防止DoS和降级攻击
ECDH自动 自动选择最佳椭圆曲线

SSL 握手

cpp 复制代码
// red-stream.cpp
// 启用SSL加密
RedStreamSslStatus red_stream_enable_ssl(RedStream *stream, SSL_CTX *ctx)
{
    BIO *sbio;
    
    // ===== 创建BIO(I/O抽象) =====
    sbio = BIO_new_socket(stream->socket, BIO_NOCLOSE);
    if (!sbio) {
        return RED_STREAM_SSL_STATUS_ERROR;
    }
    
    // ===== 创建SSL对象 =====
    stream->priv->ssl = SSL_new(ctx);
    if (!stream->priv->ssl) {
        BIO_free(sbio);
        return RED_STREAM_SSL_STATUS_ERROR;
    }
    
    // ===== 绑定BIO =====
    SSL_set_bio(stream->priv->ssl, sbio, sbio);
    
    // ===== 切换读写函数 =====
    stream->priv->write = stream_ssl_write_cb;
    stream->priv->read = stream_ssl_read_cb;
    red_stream_disable_writev(stream);  // SSL不支持writev
    
    // ===== 开始握手 =====
    return red_stream_ssl_accept(stream);
}

// SSL握手(服务器端)
RedStreamSslStatus red_stream_ssl_accept(RedStream *stream)
{
    int return_code = SSL_accept(stream->priv->ssl);
    
    if (return_code == 1) {
        // 握手成功
        return RED_STREAM_SSL_STATUS_OK;
    }
    
    // ===== 处理非阻塞情况 =====
    int ssl_error = SSL_get_error(stream->priv->ssl, return_code);
    
    if (return_code == -1 && 
        (ssl_error == SSL_ERROR_WANT_READ || 
         ssl_error == SSL_ERROR_WANT_WRITE)) {
        // 需要更多数据,稍后重试
        return ssl_error == SSL_ERROR_WANT_READ 
               ? RED_STREAM_SSL_STATUS_WAIT_FOR_READ
               : RED_STREAM_SSL_STATUS_WAIT_FOR_WRITE;
    }
    
    // 握手失败
    SSL_free(stream->priv->ssl);
    stream->priv->ssl = nullptr;
    return RED_STREAM_SSL_STATUS_ERROR;
}

SSL握手时序:

SASL 认证

SASL 结构

cpp 复制代码
// red-stream.cpp
struct RedSASL {
    sasl_conn_t *conn;        // SASL连接
    
    // ===== SSF协商 =====
    int wantSSF :1;           // 是否需要安全层
    int runSSF :1;            // 是否正在运行安全层
    
    // ===== 编码缓冲 =====
    const uint8_t *encoded;   // 编码后的数据
    unsigned int encodedLength;
    unsigned int encodedOffset;
    
    // ===== 接收缓冲 =====
    SpiceBuffer inbuffer;
};

SASL 认证流程

cpp 复制代码
// red-stream.cpp
// 启动SASL认证
bool red_sasl_start_auth(RedStream *stream, 
                         RedSaslResult result_cb, 
                         void *result_opaque)
{
    RedSASL *sasl = &stream->priv->sasl;
    
    // ===== 获取地址信息 =====
    char *localAddr = red_stream_get_local_address(stream);
    char *remoteAddr = red_stream_get_remote_address(stream);
    
    // ===== 创建SASL服务器 =====
    int err = sasl_server_new(
        "spice",              // 服务名
        NULL,                 // FQDN(自动检测)
        NULL,                 // 用户realm
        localAddr,            // 本地地址
        remoteAddr,           // 远程地址
        NULL,                 // 回调(不需要)
        SASL_SUCCESS_DATA,    // 标志
        &sasl->conn);         // 输出连接
    
    // ===== 配置外部SSF(如果有TLS) =====
    if (stream->priv->ssl) {
        sasl_ssf_t ssf = SSL_get_cipher_bits(stream->priv->ssl, NULL);
        sasl_setprop(sasl->conn, SASL_SSF_EXTERNAL, &ssf);
    } else {
        // 无TLS时需要SASL提供安全层
        sasl->wantSSF = 1;
    }
    
    // ===== 设置安全属性 =====
    sasl_security_properties_t secprops;
    memset(&secprops, 0, sizeof(secprops));
    
    if (stream->priv->ssl) {
        // 有TLS,不需要SASL SSF
        secprops.min_ssf = 0;
        secprops.max_ssf = 0;
        secprops.maxbufsize = 8192;
    } else {
        // 无TLS,要求SASL提供加密
        secprops.min_ssf = 56;      // 最小56位(Kerberos级别)
        secprops.max_ssf = 100000;  // 最大无限制
        secprops.maxbufsize = 8192;
        // 禁止匿名和明文
        secprops.security_flags = 
            SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
    }
    
    sasl_setprop(sasl->conn, SASL_SEC_PROPS, &secprops);
    
    // ===== 获取可用机制 =====
    const char *mechlist;
    sasl_listmech(sasl->conn, NULL, ",", ",", ",", 
                  &mechlist, NULL, NULL);
    
    // ===== 发送机制列表给客户端 =====
    red_stream_write_u32_le(stream, strlen(mechlist));
    red_stream_write_all(stream, mechlist, strlen(mechlist));
    
    // ===== 等待客户端选择机制 =====
    // ... 异步读取处理
    
    return true;
}

SSF 检查

cpp 复制代码
// red-stream.cpp
// 检查协商的安全强度因子
static int auth_sasl_check_ssf(RedSASL *sasl, int *runSSF)
{
    *runSSF = 0;
    
    if (!sasl->wantSSF) {
        // 不需要SSF(已有TLS)
        return 1;
    }
    
    // ===== 获取协商的SSF =====
    const void *val;
    sasl_getprop(sasl->conn, SASL_SSF, &val);
    int ssf = *(const int *)val;
    
    spice_debug("negotiated an SSF of %d", ssf);
    
    // ===== 检查强度 =====
    if (ssf < 56) {
        // SSF不够(56位是Kerberos最低要求)
        return 0;
    }
    
    *runSSF = 1;  // 启用SASL安全层
    return 1;
}

SASL认证时序:

WebSocket 支持

WebSocket 检测

cpp 复制代码
// red-stream.cpp
// 检测是否是WebSocket连接
bool red_stream_is_websocket(RedStream *stream, 
                             const void *buf, 
                             size_t len)
{
    if (stream->priv->ws) {
        return false;  // 已经是WebSocket
    }
    
    // ===== 尝试创建WebSocket =====
    stream->priv->ws = websocket_new(
        buf, len,                    // 初始数据
        stream,                      // 关联的stream
        (websocket_read_cb_t)stream->priv->read,   // 原read
        (websocket_write_cb_t)stream->priv->write, // 原write
        (websocket_writev_cb_t)stream->priv->writev // 原writev
    );
    
    if (stream->priv->ws) {
        // ===== 切换到WebSocket读写 =====
        stream->priv->read = stream_websocket_read;
        stream->priv->write = stream_websocket_write;
        
        if (stream->priv->writev) {
            stream->priv->writev = stream_websocket_writev;
        }
        
        return true;
    }
    
    return false;
}

WebSocket 握手

cpp 复制代码
// websocket.c
// 创建WebSocket连接
RedsWebSocket *websocket_new(const void *buf, size_t len, 
                             void *stream,
                             websocket_read_cb_t read_cb,
                             websocket_write_cb_t write_cb,
                             websocket_writev_cb_t writev_cb)
{
    char rbuf[4096];
    
    // ===== 读取HTTP请求 =====
    memcpy(rbuf, buf, len);
    int rc = read_cb(stream, rbuf + len, sizeof(rbuf) - len - 1);
    len += rc;
    rbuf[len] = 0;
    
    // ===== 验证是否是WebSocket升级请求 =====
    bool has_protocol;
    if (!websocket_is_start(rbuf, &has_protocol)) {
        return NULL;  // 不是WebSocket
    }
    
    // ===== 生成响应 =====
    char outbuf[1024];
    websocket_create_reply(rbuf, outbuf, has_protocol);
    
    // ===== 发送握手响应 =====
    rc = write_cb(stream, outbuf, strlen(outbuf));
    if (rc != strlen(outbuf)) {
        return NULL;
    }
    
    // ===== 创建WebSocket状态 =====
    RedsWebSocket *ws = g_new0(RedsWebSocket, 1);
    ws->raw_stream = stream;
    ws->raw_read = read_cb;
    ws->raw_write = write_cb;
    ws->raw_writev = writev_cb;
    
    pong_init(&ws->pong);
    pong_init(&ws->pending_pong);
    
    return ws;
}

WebSocket 密钥生成

cpp 复制代码
// websocket.c
// 生成WebSocket接受密钥
static char *generate_reply_key(char *buf)
{
    // ===== 提取客户端密钥 =====
    const char *key = find_str(buf, "\nSec-WebSocket-Key:");
    
    if (key) {
        const char *p = strchr(key, '\r');
        char *k = g_strndup(key, p - key);
        k = g_strstrip(k);
        
        // ===== 计算SHA1哈希 =====
        GChecksum *checksum = g_checksum_new(G_CHECKSUM_SHA1);
        
        // 客户端密钥 + 固定GUID
        g_checksum_update(checksum, (uint8_t *)k, strlen(k));
        g_checksum_update(checksum, 
                         (uint8_t *)WEBSOCKET_GUID, 
                         strlen(WEBSOCKET_GUID));
        // WEBSOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
        
        // ===== 获取摘要 =====
        size_t sha1_size = g_checksum_type_get_length(G_CHECKSUM_SHA1);
        uint8_t *sha1 = g_malloc(sha1_size);
        g_checksum_get_digest(checksum, sha1, &sha1_size);
        
        // ===== Base64编码 =====
        char *b64 = g_base64_encode(sha1, sha1_size);
        
        g_checksum_free(checksum);
        g_free(sha1);
        g_free(k);
        
        return b64;
    }
    
    return NULL;
}

安全连接流程

完整流程

cpp 复制代码
// reds.cpp
// 初始化SSL连接
static RedLinkInfo *reds_init_client_ssl_connection(
    RedsState *reds, 
    int socket)
{
    // 1. 创建基础连接
    RedLinkInfo *link = reds_init_client_connection(reds, socket);
    
    // 2. 启用SSL
    RedStreamSslStatus ssl_status = 
        red_stream_enable_ssl(link->stream, reds->ctx);
    
    switch (ssl_status) {
    case RED_STREAM_SSL_STATUS_OK:
        // 握手完成,继续协议处理
        reds_handle_new_link(link);
        return link;
        
    case RED_STREAM_SSL_STATUS_WAIT_FOR_READ:
        // 需要更多数据
        link->stream->watch = reds_core_watch_add(
            reds, link->stream->socket,
            SPICE_WATCH_EVENT_READ,
            reds_handle_ssl_accept, link);
        break;
        
    case RED_STREAM_SSL_STATUS_WAIT_FOR_WRITE:
        // 需要发送数据
        link->stream->watch = reds_core_watch_add(
            reds, link->stream->socket,
            SPICE_WATCH_EVENT_WRITE,
            reds_handle_ssl_accept, link);
        break;
        
    case RED_STREAM_SSL_STATUS_ERROR:
        reds_link_free(link);
        return nullptr;
    }
    
    return link;
}

认证机制选择

cpp 复制代码
// reds.cpp
static void reds_handle_auth_mechanism(void *opaque)
{
    RedLinkInfo *link = (RedLinkInfo *)opaque;
    RedsState *reds = link->reds;
    
    switch (link->auth_mechanism.auth_mechanism) {
    
    case SPICE_COMMON_CAP_AUTH_SPICE:
        // ===== SPICE原生认证 =====
        if (!reds->config->sasl_enabled) {
            // 使用简单密码认证
            reds_get_spice_ticket(link);
        }
        break;
        
#if HAVE_SASL
    case SPICE_COMMON_CAP_AUTH_SASL:
        // ===== SASL认证 =====
        spice_debug("Starting SASL");
        reds_start_auth_sasl(link);
        break;
#endif
        
    default:
        // 未知机制
        spice_warning("Unknown auth method");
        reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
        reds_link_free(link);
    }
}

安全检查

cpp 复制代码
// reds.cpp
// 检查通道是否满足安全要求
static bool reds_security_check(RedLinkInfo *link)
{
    // 获取通道的安全要求
    ChannelSecurityOptions *security = 
        find_channel_security(link->link_mess->channel_type);
    
    if (security) {
        if (red_stream_is_ssl(link->stream)) {
            // 当前是加密连接
            if (security->options & SPICE_CHANNEL_SECURITY_SSL) {
                return true;  // 需要加密,满足
            }
            // 此通道不应加密
            return false;
        } else {
            // 当前是非加密连接
            if (security->options & SPICE_CHANNEL_SECURITY_NONE) {
                return true;  // 允许非加密
            }
            // 此通道需要加密
            return false;
        }
    }
    
    // 无特殊要求,默认允许
    return true;
}

传输层组合

SPICE支持多种传输层的组合:

组合 协议栈 使用场景
组合1 Socket → SPICE协议 内网信任环境
组合2 Socket → SSL → SPICE协议 标准加密连接
组合3 Socket → SSL → SASL → SPICE协议 企业级认证
组合4 Socket → WebSocket → SPICE协议 浏览器客户端
组合5 Socket → SSL → WebSocket → SPICE协议 安全浏览器客户端
组合6 Socket → SSL → WebSocket → SASL → SPICE协议 完整安全堆栈

函数指针切换:

cpp 复制代码
// 初始状态(普通socket)
stream->priv->read = stream_socket_read;
stream->priv->write = stream_socket_write;

// 启用SSL后
stream->priv->read = stream_ssl_read_cb;
stream->priv->write = stream_ssl_write_cb;

// 再启用WebSocket
stream->priv->read = stream_websocket_read;
stream->priv->write = stream_websocket_write;
// WebSocket内部调用之前的SSL函数

总结

机制 功能 配置
SSL/TLS 传输加密 证书、私钥、CA
SASL 身份认证 机制列表、SSF
WebSocket 浏览器支持 自动检测

安全建议:

  1. 始终启用TLS:使用TLS 1.2或更高版本
  2. 配置强密码套件:禁用弱密码
  3. 客户端证书:高安全环境使用双向认证
  4. 定期更新:保持OpenSSL库更新
相关推荐
@hdd7 天前
SPICE源码分析(一):整体架构与实现框架
spice·远程桌面协议
云雾J视界1 个月前
SPICE仿真进阶:AI芯片低功耗设计中的瞬态/AC分析实战
低功耗·仿真·spice·ai芯片·ac·均值估算
冰山一脚20132 年前
libusb注意事项笔记
spice
冰山一脚20132 年前
libspice显示命令调用流程分析
spice
冰山一脚20132 年前
spice VDAgent简介
spice