Spice-GTK

https://github.com/spice-gm/spice-gtk-gm

加密套件选择

对于客户端加密传输改造主要通过实现get_x509_from_PEM_file函数读取当前使用证书,使用check_x509_use_rsa函数通过其签名算法以此判断是否指定国密套件(国密套件的默认优先级低于原有的TLS1.3算法套件)。

根据证书详情选择套件核心函数
X509 *get_x509_from_PEM_file(const char* file_path) {
    X509 *cert = NULL;
    BIO *cert_bio = BIO_new(BIO_s_file());
    int ret = BIO_read_filename(cert_bio, file_path);
    if (! (cert = PEM_read_bio_X509(cert_bio, NULL, 0, NULL))) {
        g_warning("loading ca certs from default location failed");
        return NULL;
    }
    return cert;
}

int check_x509_use_rsa(X509 *cert) {
    EVP_PKEY *pk;
    pk = X509_get_pubkey(cert);
    if (EVP_PKEY_RSA == EVP_PKEY_base_id(pk)) {
        return 1;
    }
    return 0;
}

static int spice_channel_load_ca(SpiceChannel *channel)
{
    SpiceChannelPrivate *c = channel->priv;
    int i, count = 0;
    guint8 *ca;
    guint size;
    const gchar *ca_file;
    int rc;

    g_return_val_if_fail(c->ctx != NULL, 0);

    ca_file = spice_session_get_ca_file(c->session);
    spice_session_get_ca(c->session, &ca, &size);

    CHANNEL_DEBUG(channel, "Load CA, file: %s, data: %p", ca_file, ca);

    if (ca != NULL) {
        STACK_OF(X509_INFO) *inf;
        X509_STORE *store;
        BIO *in;

        store = SSL_CTX_get_cert_store(c->ctx);
        in = BIO_new_mem_buf(ca, size);
        inf = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL);
        BIO_free(in);
        for (i = 0; i < sk_X509_INFO_num(inf); i++) {
            X509_INFO *itmp;
            itmp = sk_X509_INFO_value(inf, i);
            if (itmp->x509) {
                if (check_x509_use_rsa(itmp->x509)) ca_use_SM2 = FALSE;
                count++;
            }
            if (itmp->crl) {
                X509_STORE_add_crl(store, itmp->crl);
                count++;
            }
        }

        sk_X509_INFO_pop_free(inf, X509_INFO_free);
    }

    if (ca_file != NULL) {
        X509 *tmp_cert = get_x509_from_PEM_file(ca_file);
        if (tmp_cert) {
            if (check_x509_use_rsa(tmp_cert)) ca_use_SM2 = FALSE;
        }
        rc = SSL_CTX_load_verify_locations(c->ctx, ca_file, NULL);
        if (rc != 1)
            g_warning("loading ca certs from %s failed", ca_file);
        else
            count++;
    }

    if (count == 0) {
        X509 *tmp_cert = get_x509_from_PEM_file(ca_file);
        if (tmp_cert) {
            if (check_x509_use_rsa(tmp_cert)) ca_use_SM2 = FALSE;
        }
        rc = SSL_CTX_set_default_verify_paths(c->ctx);
        if (rc != 1)
            g_warning("loading ca certs from default location failed");
        else
            count++;
    }

    return count;
}

static void *spice_channel_coroutine(void *data) {
    ... ...
    if (c->tls) {
        c->ctx = SSL_CTX_new(TLS_method());
        if (c->ctx == NULL) {
            g_critical("SSL_CTX_new failed");
            c->event = SPICE_CHANNEL_ERROR_TLS;
            goto cleanup;
        }

        // SSL_CTX_set_options(c->ctx, ssl_options);
        SSL_CTX_set_min_proto_version(c->ctx, TLS1_3_VERSION);
        verify = spice_session_get_verify(c->session);
        if (verify &
            (SPICE_SESSION_VERIFY_SUBJECT | SPICE_SESSION_VERIFY_HOSTNAME)) {
            rc = spice_channel_load_ca(channel);
            if (ca_use_SM2 == TRUE) SSL_CTX_set_ciphersuites(c->ctx, "TLS_SM4_GCM_SM3");
            if (rc == 0) {
                g_warning("no cert loaded");
                if (verify & SPICE_SESSION_VERIFY_PUBKEY) {
                    g_warning("only pubkey active");
                    verify = SPICE_SESSION_VERIFY_PUBKEY;
                } else {
                    c->event = SPICE_CHANNEL_ERROR_TLS;
                    goto cleanup;
                }
            }
        }
    ... ...
}
口令验证

Spice-gtk对应完成spice_channel_send_spice_ticket的逻辑拆分,利用sm2-EVP的C语言版本进行实现。使用spice-cmdline.c读取命令行参数,针对ticket-handler参数在列表中新增描述如下表所示。

字段
long_name "ticket-handler"
short_name 't'
arg G_OPTION_ARG_STRING
arg_data &ticket_handler
description N_("Algorithm to handle ticket")
arg_description N_("")

表 4-2 ticket-handler参数字段设置

客户端连接时配置大都保存在spice-session.c的_SpiceSessionPrivate结构体中,新增ticket_handler字段,并增加枚举类型PROP_TICKET_HANDLER。将set/get处理逻辑添加至spice_session_set_property、spice_session_get_property中。利用g_object_class_install_property进行参数注册。

spice_channel_recv_link_msg通过ticket-handler值选择spice_channel_send_spice_ticket处理逻辑。

Password 口令验证 SM2 实现 --- GTK客户端

使用 openssl EVP 接口实现 SM2 工具库,GTK 需要 C 语言版本

sm2-EVP 项目见 https://github.com/Sovea/sm2-EVP

Ticket 口令验证核心函数
static SpiceChannelEvent spice_channel_send_spice_ticket_sm2(SpiceChannel *channel)
{
    SpiceChannelPrivate *c = channel->priv;
    EVP_PKEY *pubkey;
    int en_password_len;
    BIO *bioKey, *bioKey_pem;
    char *password, *en_password;
    uint8_t *encrypted;
    char *sm2_pubkey;
    int rc;
    SpiceChannelEvent ret = SPICE_CHANNEL_ERROR_LINK;
    spice_warning("Send Ticket With SM2.");
    bioKey = BIO_new(BIO_s_mem());
    g_return_val_if_fail(bioKey != NULL, ret);
    // wirte pub_key into biokey
    BIO_write(bioKey, c->peer_msg->pub_key, SPICE_TICKET_PUBKEY_BYTES);
    // Use BIO to get DER pubKey
    pubkey = d2i_PUBKEY_bio(bioKey, NULL);
    encrypted = g_alloca(128);

    // SM2 handle password
    bioKey_pem = BIO_new(BIO_s_mem());
    PEM_write_bio_PUBKEY(bioKey_pem, pubkey);
    bio_to_string(bioKey_pem, &sm2_pubkey);
    g_object_get(c->session, "password", &password, NULL);
    if (password == NULL) password = g_strdup("");
    if (strlen(password) > SPICE_MAX_PASSWORD_LENGTH) {
        spice_channel_failed_spice_authentication(channel, TRUE);
        ret = SPICE_CHANNEL_ERROR_AUTH;
        goto cleanup;
    }
    rc = Encrypt(password, strlen(password) + 1, &en_password, &en_password_len, sm2_pubkey);
    g_warn_if_fail(rc > 0);
    encrypted = (uint8_t *)en_password;
    // printf("Debug: encrypted: %s \n", encrypted);
    spice_channel_write(channel, encrypted, 128);
    ret = SPICE_CHANNEL_NONE;
    return ret;
cleanup:
    memset(encrypted, 0, 128);
    EVP_PKEY_free(pubkey);
    BIO_free(bioKey);
    g_free(password);
    return ret;
}

void bio_to_string_with_maxlen(BIO *bio, int max_len, char **str) {
    char buffer[max_len];
    memset(buffer, 0, max_len);
    BIO_read(bio, buffer, max_len - 1);
    *str = buffer;
}

void bio_to_string(BIO *bio, char **str) {
    char *temp;
    int readSize = (int)BIO_get_mem_data(bio, &temp);
    *str = (char *)malloc(readSize);
    BIO_read(bio, *str, readSize);
}
改造后增加SM2工具类,编译

meson.build 修改

spice_client_glib_headers = [
  spice_version_h,
  'channel-cursor.h',
  'channel-display.h',
  'channel-inputs.h',
  'channel-main.h',
   ... ...
  'spice-uri.h',
  'spice-util.h',
  'usb-device-manager.h',
  'sm2.h', # Add
]

spice_client_glib_introspection_sources = [
  spice_client_glib_headers,
  spice_client_glib_enums,
  'channel-cursor.c',
  'channel-display.c',
  'channel-inputs.c',
  'channel-main.c',
  ... ...
  'sm2.c', # Add
]

spice_client_glib_sources = [
  spice_marshals,
  spice_client_glib_introspection_sources,
  'bio-gio.c',
  'bio-gio.h',
  'channel-base.c',
  'channel-display-gst.c',
  'channel-display-priv.h',
  'channel-playback-priv.h',
  'channel-usbredir-priv.h',
   ... ...
  'vmcstream.c',
  'vmcstream.h',
  'sm2.h', # Add
  'sm2.c', # Add
]

results matching ""

    No results matching ""