news 2026/9/7 4:46:59

Electron Certificate 对象详解:X.509 证书在 JS 侧的数据结构与实战用法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Electron Certificate 对象详解:X.509 证书在 JS 侧的数据结构与实战用法

Electron Certificate 对象详解:X.509 证书在 JS 侧的数据结构与实战用法

【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron

本文围绕 Electron 官方结构文档 Certificate 展开,逐一解析Certificate对象的 10 个字段(PEM 数据、签发者/主题主体、序列号、有效期、指纹)在 Chromium 底层是如何生成的,并说明它在app的证书错误事件、客户端证书选择、session.setCertificateVerifyProc与 macOS 证书信任对话框这四个典型场景中的实际用法,帮助读者在 Electron 应用中正确读取、校验和信任 X.509 证书。

1. Certificate 对象字段总览

Certificate对象是 Electron 中所有涉及 TLS/X.509 证书的 API 共用的数据结构。按照 docs/api/structures/certificate.md 的定义,它包含以下字段:

字段类型说明
datastringPEM 编码的证书数据
issuerCertificatePrincipal签发者(Issuer)主体
issuerNamestring签发者的 Common Name
issuerCertCertificate签发者证书(当证书非自签名时存在)
subjectCertificatePrincipal主题(Subject)主体
subjectNamestring主题的 Common Name
serialNumberstring十六进制字符串表示的序列号
validStartnumber证书生效时间,Unix 秒级时间戳
validExpirynumber证书过期时间,Unix 秒级时间戳
fingerprintstring证书指纹

其中issuersubject两个字段是嵌套的CertificatePrincipal对象,其结构定义在 docs/api/structures/certificate-principal.md:

  • commonNamestring - 通用名称(CN)
  • organizationsstring[] - 组织名称列表
  • organizationUnitsstring[] - 组织单位列表
  • localitystring - 城市(Locality)
  • statestring - 州或省
  • countrystring - 国家或地区

2. 源码实现:字段如何从 Chromium 的 X509Certificate 生成

Electron 中 JS 侧的Certificate对象并非手工拼装,而是由 gin 类型转换器统一生成。核心实现在 shell/common/gin_converters/net_converter.cc,它将 Chromium 网络栈的net::X509Certificate转换为 V8 对象:

// shell/common/gin_converters/net_converter.cc // static v8::Local<v8::Value> Converter<scoped_refptr<net::X509Certificate>>::ToV8( v8::Isolate* isolate, const scoped_refptr<net::X509Certificate>& val) { std::string encoded_data; net::X509Certificate::GetPEMEncoded(val->cert_buffer(), &encoded_data); gin::DataObjectBuilder builder(isolate); builder.Set("data", encoded_data) .Set("issuer", val->issuer()) .Set("issuerName", val->issuer().GetDisplayName()) .Set("subject", val->subject()) .Set("subjectName", val->subject().GetDisplayName()) .Set("serialNumber", base::HexEncode(val->serial_number())) .Set("validStart", val->valid_start().InSecondsFSinceUnixEpoch()) .Set("validExpiry", val->valid_expiry().InSecondsFSinceUnixEpoch()) .Set("fingerprint", net::HashValue(net::HASH_VALUE_SHA256, val->CalculateFingerprint256(val->cert_buffer())) .ToString()); // ... issuerCert 处理 }

从这段实现可以确认原文档中每个字段的确切含义:

  • data:由net::X509Certificate::GetPEMEncoded对原始证书字节缓冲做 PEM 编码得到,是标准-----BEGIN CERTIFICATE-----格式字符串,可直接交给crypto模块或命令行工具处理;
  • issuerName/subjectName:调用CertPrincipal::GetDisplayName(),即主体中用于展示的 Common Name;
  • serialNumber:对原始序列号字节做base::HexEncode十六进制编码后的字符串,注意这是小写十六进制且不加分隔符,与部分工具输出带冒号的形式不同,比较时需要归一化;
  • validStart/validExpiry:通过InSecondsFSinceUnixEpoch()得到的 Unix 秒级时间戳(1970-01-01 起的秒数),JS 中需乘以 1000 才能传给Date
  • fingerprint:对整张 DER 编码证书计算 SHA-256 摘要(CalculateFingerprint256),并用net::HashValue格式化为指纹字符串,可用于在日志、缓存或白名单中唯一标识一张证书;
  • issuerCert:仅当证书携带中间证书链(intermediate_buffers()非空)时才被设置——代码取第一张中间证书作为直接签发者,并把它后面的其余中间证书挂到该签发者证书下,从而递归形成证书链。这解释了为什么issuerCert只有在“非自签名”时才有值:自签名根证书没有中间证书缓冲,字段即为空。

2.1 反向转换:Certificate 也能传回 C++

同一文件还实现了FromV8方向(net_converter.cc):JS 传入的Certificate对象会被CertFromData读取其中的data字段,用CreateCertificateListFromBytesFORMAT_SINGLE_CERTIFICATE模式解析出叶子证书,若还携带issuerCert则把两者重新组装成带中间链的X509Certificate。这正是app.on('select-client-certificate')中能把回调选出的证书对象直接交还给网络栈的原因。

CertificatePrincipal的转换同样在 net_converter.cc 中实现,六个字段与 Chromium 的net::CertPrincipal一一对应:

return gin::DataObjectBuilder(isolate) .Set("commonName", val.common_name) .Set("organizations", val.organization_names) .Set("organizationUnits", val.organization_unit_names) .Set("locality", val.locality_name) .Set("state", val.state_or_province_name) .Set("country", val.country_name) .Build();

3. 实战场景:Certificate 对象出现在哪些 API 中

Certificate是只读的结构对象,本身没有方法,它的价值体现在被以下 Electron API 作为参数返回或接收。

3.1 处理服务器证书校验失败:app/webContentscertificate-error

当 URL 的服务器证书校验失败时,app会发出certificate-error事件(见 docs/api/app.md),回调参数中的certificateCertificate对象,error为错误码字符串:

const { app } = require('electron') app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { if (url === 'https://github.com') { // 校验逻辑。 event.preventDefault() callback(true) } else { callback(false) } })

要信任该证书,需调用event.preventDefault()并执行callback(true)。此时certificate对象可直接用于审计:例如打印certificate.subjectNamecertificate.validExpiry判断是否因过期被拒,或把certificate.fingerprint记入日志。docs/api/web-contents.md 中webContentscertificate-error事件语义相同,并额外提供isTrusted布尔量指示证书是否可被视为可信。

3.2 选择客户端证书:select-client-certificate

当服务器要求双向 TLS(mTLS)客户端证书时,Electron 从平台证书存储中取出候选列表,以Certificate[]形式通过select-client-certificate事件交给应用(见 docs/api/app.md):

const { app } = require('electron') app.on('select-client-certificate', (event, webContents, url, list, callback) => { event.preventDefault() callback(list[0]) })

list中每一项都是完整的Certificate对象,可以在callback(list[i])之前依据subject.commonNameissuerName等字段挑选合适的证书。注意两点(同样记录在 app.md 中):

  • webContents可能为null——当请求来自主进程的net.request/net.fetch,或来自设置了respondToAuthRequestsFromMainProcess: true的 utility process 时,请求并不源于某个渲染进程;
  • 未调用event.preventDefault()时,Electron 默认使用证书存储中的第一张证书。

如 2.1 节所述,回调传入的证书对象会经由FromV8转回 C++ 侧的X509Certificate,所以传给callback的必须是事件给定的list中的条目(保留issuerCert链信息),而不是自行用data字段重新构造的对象。

3.3 自定义验证流程:session.setCertificateVerifyProc

sessionsetCertificateVerifyProc允许用 JS 完全接管证书验证(见 docs/api/session.md)。回调中的request同时携带certificate(服务器证书)与validatedCertificate(经过验证的那张,两者可能不同),以及isIssuedByKnownRootverificationResulterrorCode等字段:

const { BrowserWindow } = require('electron') const win = new BrowserWindow() win.webContents.session.setCertificateVerifyProc((request, callback) => { const { hostname } = request if (hostname === 'github.com') { callback(0) } else { callback(-2) } })

约定行为:

  • callback(0):接受证书,并禁用 Certificate Transparency 校验;
  • callback(-2):拒绝证书;
  • 其他取值为 Chromium 网络栈的证书错误码;
  • 调用setCertificateVerifyProc(null)恢复默认验证逻辑;
  • 文档明确提示:该回调结果会被网络服务缓存,测试时不要假设每次请求都会重新触发。

isIssuedByKnownRootfalse通常意味着证书来自本地安装了根 CA 的 MITM 代理(如企业代理),此时若verificationResult不是OK,官方文档建议不要信任——这是安全审计时直接可用的判据。

3.4 macOS 证书信任对话框:dialog.showCertificateTrustDialog

Certificate对象也可作为输入参数:dialog.showCertificateTrustDialog(见 docs/api/dialog.md)接收options.certificateoptions.message,在 macOS 上弹出模态对话框让用户决定是否信任/导入该证书:

  • 提供window参数时,对话框会挂载为父窗口的 sheet(模态);
  • Windows 上能力受限:系统自带确认对话框,message不生效,window参数被忽略。

典型流程是:先在certificate-error事件里拿到未受信任的certificate对象,展示subjectName/issuerName给用户确认,再调用信任对话框完成导入——全程不需要离开 JS 层解析 PEM。

3.5 相关但形态不同的证书 API

除上述以Certificate对象为中心的场景外,仓库文档中还有一组证书相关但不返回Certificate对象的 API,注意区分:

  • app.importCertificate(docs/api/app.md):以 PKCS12 文件路径和密码导入平台证书存储,输入是文件而非Certificate对象;
  • app.on('select-client-certificate')配套的密码解锁请求(app.md 中的tokenName/hostname参数):当解锁客户端证书需要密码时触发;
  • 命令行开关--ignore-certificate-errors(docs/api/command-line-switches.md):全局忽略证书错误,属于调试手段,不应在生产应用中使用。

4. 使用 Certificate 字段时的注意事项

  1. 时间戳单位validStart/validExpiry是秒不是毫秒,new Date(cert.validExpiry * 1000)才是可读日期;
  2. 指纹算法fingerprint固定为整张 DER 证书的 SHA-256,且经由net::HashValue格式化输出。若要与系统工具(如openssl x509 -fingerprint -sha256)的输出比对,注意两边分隔符与大小写可能不同,建议归一化后再比较;
  3. issuerCert的可选项语义:字段缺失(自签名或服务器未提供中间链)不代表证书无效,仅表示Certificate链信息不完整;从FromV8实现看,跨 API 传递证书时应原样透传整个对象;
  4. issuerNameissuer.commonName的取舍:两者在常规情况下相同,但GetDisplayName()是展示用名称,做机器判断时建议优先使用结构化的issuer.commonName/subject.commonName
  5. 只读对象Certificate是 ginDataObjectBuilder生成的纯数据对象,没有方法,不应被修改后重复传入其他 API。

5. 小结

Certificate对象是 Electron 把 Chromiumnet::X509Certificate暴露给 JS 的统一窗口:data提供可再加工的 PEM 原文,issuer/subject提供结构化主体信息,serialNumbervalidStartvalidExpiryfingerprint提供可程序化比对的唯一标识与有效期,issuerCert则递归承载中间证书链。理解 net_converter.cc 中的双向转换逻辑后,就能在certificate-errorselect-client-certificatesetCertificateVerifyProcshowCertificateTrustDialog等场景中正确地消费和回传证书数据,实现从“绕过错误”到“真正审计证书”的工程化升级。

【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 4:46:44

GitHub PR自动合并:让网站内容修改全流程自动化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 4:42:02

Unity字体渲染与TextMeshPro实战:从缺字卡顿到性能优化

1. 从“缺字”到“卡顿”——字体问题为什么值得单独写一整篇做 Unity 项目越久越会发现一个规律&#xff1a;字体问题永远不会出现在开发前三天&#xff0c;但一定会在你准备提测、上线、或者包体优化时集中爆发。而且它的表现形式极其迷惑——有时候是某些机型上中文变成方框…

作者头像 李华
网站建设 2026/9/7 4:42:00

JS在线录音导出MP3:从麦克风到音频文件的完整方案

简介&#xff1a;面向网页应用的实时录音工具代码&#xff0c;利用浏览器自带的音频处理接口与脚本语言实现麦克风声音采集&#xff0c;并把录音转成通用的压缩音频格式&#xff0c;支持下载到本地或提交到服务器&#xff0c;适合在线课堂、语音留言、录音笔记等场景。压缩包内…

作者头像 李华
网站建设 2026/9/7 4:40:33

SolidWorks Flow Simulation流体分析实操:从边界条件到网格划分全流程

简介&#xff1a;《SOLIDWORKS Flow Simulation 流体力学分析官方中文教程》是一套面向工程师和技术人员的官方教学资源&#xff0c;定位于帮助用户系统掌握三维设计环境下的流体流动、热传递与化学反应分析。资源共17个文件&#xff0c;以14个PDF分章教程为主&#xff0c;从界…

作者头像 李华