我正在参加「掘金·启航计划」
——————————————————————————————
源起
会点进来的同学应该对国密不会陌生,这里就不多赘述。现在市面上的浏览器大多都不兼容国密HTTPS,专门做兼容国密的厂商一般都不开源甚至不免费,linux下国密浏览器更是没有,而我的主力机系统是manjaro。所以我萌生了实现一个linux下简单国密浏览器的想法,也有想过做国密正代,更具有普适性,但感觉没浏览器“高级”。
开发方向和准备
现在的国密浏览器基本都是基于chromium进行二开,chromium对于我来说还是太重了,我也没有足够的计算资源来进行编译。事实证明我的决定是正确的,我的电脑编译webkit都需要很久的时间。所以我选择了更为小巧的webkit,或许它还不能被称作一个完整的浏览器,仅仅是一个引擎,但可以进行二次封装,官方就有简单实现MiniBrowser。
主要准备就是webkit源码和利用TASSL和nginx搭建一个国密HTTPS服务器。
国密协议浅析
从wirshark抓包来看,主要区别就在于TLS版本以及加密套件。TLS原理我在这也不再赘述,从抓包分析来看,普通浏览器无法访问国密站点就主要是这两个地方。一不认识TLS版本,二也没有此国密加密套件。
其实我不是很明白国密HTTPS的设计为什么要新定一个TLS版本,如果仅仅是缺少加密套件,后续兼容国密和非国密会轻松很多。
webkit网络模块浅析
虽说比不上chromium,但webkit的代码量也决不能小看。https主要涉及到网络模块,webkit是跨平台的,它的网络模块也有多种实现,在我linux下默认是借用libsoup库进行网络传输交互。
- libsoup
我们去下载soup的源码看其原理,发现
static void
soup_connection_event (SoupConnection *conn,
GSocketClientEvent event,
GIOStream *connection)
{
SoupConnectionPrivate *priv = soup_connection_get_instance_private (conn);
if (!connection && priv->socket)
connection = soup_socket_get_connection (priv->socket);
g_signal_emit (conn, signals[EVENT], 0,
event, connection);
}
看到GIOStream,就有头绪了,对常用lib比较了解的童鞋直接ldd看下依赖就知道了。
- glib-gio 大致浏览一下gio,应是一个io抽象接口库,直接看其tls的实现
/**
* g_tls_backend_get_default:
*
* Gets the default #GTlsBackend for the system.
*
* Returns: (not nullable) (transfer none): a #GTlsBackend, which will be a
* dummy object if no TLS backend is available
*
* Since: 2.28
*/
GTlsBackend *
g_tls_backend_get_default (void)
{
if (g_once_init_enter (&tls_backend_default_singleton))
{
GTlsBackend *singleton;
singleton = _g_io_module_get_default (G_TLS_BACKEND_EXTENSION_POINT_NAME,
"GIO_USE_TLS",
NULL);
g_once_init_leave (&tls_backend_default_singleton, singleton);
}
return tls_backend_default_singleton;
}
由于gio对IO高度抽象,代码中不容易体现,主要就是gio会去动态加载指定的lib库实现tls。
……真是一层套一层啊……(┙>∧<)┙へ┻┻
- glib-networking
观其文件结构,发现tls的实现依赖于两个tls库……
- openssl/guntls
终于到了最终的tls实现的地方。openssl比较常用,guntls貌似在开发中使用的并不是很多。开源的国密ssl的实现基本都基于openssl进行二开。所以我们也从openssl进行下手。
一行代码实现国密HTTPS
我们引入tassl替代openssl,并参考其国密实现,我们在glib-networking的openssl封装模块中进行修改。
//修改前
priv->ssl_ctx = SSL_CTX_new (SSLv23_client_method ());
//修改后
priv->ssl_ctx = SSL_CTX_new (CNTLS_client_method());
接下来重新编译即可,你看真的只修改了一行……
因为强制进行国密交互,正常的https都将无法通信……