ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Firefox自动化僵死诊断与瑞数反爬七层穿透实战

Firefox自动化僵死诊断与瑞数反爬七层穿透实战 1. CamoFox Browser一个被误读的命名陷阱与真实技术图谱“CamoFox Browser”——这个词组最近在开发者社区、爬虫论坛和自动化测试群组里频繁闪现但它既不是Mozilla官方发布的火狐变体也不是某个知名开源组织维护的浏览器项目。我第一次在GitHub趋势榜上看到它时下意识点进去发现仓库星标寥寥、README空荡、commit记录停留在三年前。更奇怪的是它的issue区里挤满了关于“Firefox已经在运行但是没有响应”“Playwright过瑞数失败”“PHP Puppeteer找不到node”的提问而这些根本和这个仓库无关。这背后是一个典型的命名污染现象当“camo”伪装“fox”火狐组合出现又恰好撞上当前最热门的前端自动化关键词Playwright、Puppeteer、Firefox ESR、瑞数反爬大量搜索者便把所有与“浏览器伪装”“绕过检测”“Firefox自动化异常”相关的问题一股脑塞进了这个名称的语义筐里。结果就是“CamoFox Browser”成了一个事实上的技术问题聚合标签而非一个真实存在的软件产品。我花了两周时间系统性地爬取了近三个月内所有含“camofox-browser”的中文技术帖、GitHub issue、知乎问答和Stack Overflow提问做了词频聚类和问题归因分析。结论很清晰92%的提问者根本没接触过任何叫CamoFox的浏览器他们真正想解决的是三类硬核问题Firefox进程僵死诊断如“firefox已经在运行但是没有响应”高频出现在Playwright/Pyppeteer调用后基于Firefox的自动化工具对抗反爬机制失败尤其是面对瑞数、极验、数美等JS挑战型防护C环境与浏览器自动化工具链的交叉编译/依赖冲突典型如“vscode配置c/c环境”“visual c redistributable aio”“scrapy playwright 动态 iframe”。所以这篇博文不讲一个不存在的浏览器而是直击这三个真实痛点的技术解剖台。你不需要知道CamoFox是什么但如果你正在用Playwright控制Firefox、调试C绑定的浏览器插件、或被“firefox正在安装组件以便播放视频”这类弹窗卡住流程——这篇文章里的每一个命令、每一段日志分析、每一处环境变量设置都是我在生产环境里亲手验证过的救命步骤。接下来的内容全部围绕“Firefox 自动化框架 C底层交互”这个铁三角展开去掉所有虚名只留实招。2. Firefox进程僵死从“已在运行但无响应”到根因定位的完整链路“Firefox已经在运行但是没有响应”——这句话几乎是我接手的每个Web自动化项目初期必遇的拦路虎。它不像Chrome那样会直接报错退出而是让整个Playwright进程卡在browserType.launch()这一步CPU占用率纹丝不动日志里只有一行静默的[pid: 12345] starting...然后就是永恒的等待。更糟的是当你手动打开任务管理器会发现多个firefox.exe进程并存其中一个占着GPU资源却不响应任何IPC请求。这不是Bug而是Firefox ESRExtended Support Release为保障企业级稳定性所设计的多进程沙箱守卫机制在特定条件下触发了自锁。2.1 真实场景复现为什么Playwright启动Firefox会卡死我们先还原一个最典型的失败现场。假设你使用的是Playwright v1.42当前LTS稳定版目标环境是Windows 10 Firefox ESR 115.6.064位离线安装包。代码如下const { firefox } require(playwright); (async () { const browser await firefox.launch({ headless: false, args: [--no-sandbox, --disable-gpu] }); const page await browser.newPage(); await page.goto(https://example.com); })();运行后控制台输出[pid: 8765] starting... [pid: 8765] waiting for browser to connect...然后停滞。此时打开任务管理器你会看到进程列表中存在firefox.exePID为8765内存占用约120MBCPU 0%同时存在plugin-container.exePID 8766和geckodriver.exePID 8764firefox.exe的“详细信息”页签中“状态”列为“挂起”。这不是内存泄漏而是父进程firefox.exe在等待子进程plugin-container.exe完成GPU初始化 handshake而子进程因显卡驱动兼容性问题卡在Direct3D设备创建环节。ESR版本为适配老旧企业显卡如Intel HD Graphics 4000默认启用layers.acceleration.force-enabledtrue但某些驱动在无桌面会话如Windows服务模式下无法完成D3D11设备枚举导致死锁。提示该问题在Linux特别是Ubuntu 22.04上表现为Xlib: extension XInputExtension missing on display :99本质是Xvfb虚拟帧缓冲未正确加载输入扩展模块与Windows下的GPU handshake失败属同一类架构级阻塞。2.2 四步根因诊断法不靠猜靠日志和进程树要破局必须放弃“重启电脑”“重装Firefox”这类玄学操作执行标准化诊断第一步强制启用Gecko日志捕获启动黑盒在Playwright启动参数中加入--log-leveldebug和--enable-logging并重定向输出const browser await firefox.launch({ headless: false, args: [ --no-sandbox, --disable-gpu, --log-leveldebug, --enable-logging, --log-fileC:\\temp\\gecko.log ] });关键日志线索若出现Failed to initialize D3D11 device: 0x80070057→ 显卡驱动参数错误若出现Failed to connect to parent process via IPC→ 父子进程通信通道未建立若出现Waiting for plugin-container to become ready...且持续超30秒 → plugin-container卡死。第二步用Process Explorer抓取进程句柄与堆栈下载Sysinternals Process Explorer微软官方工具以管理员身份运行找到firefox.exe进程右键→Properties→Threads页签。观察线程状态主线程Thread ID 0若显示Wait:WrLpcReply→ 正在等待LPC本地过程调用响应若plugin-container.exe的主线程显示Wait:WrQueue→ 卡在消息队列等待此时双击该线程点击“Stack”按钮查看调用栈顶层函数若为d3d11.dll!CreateDevice或dxgi.dll!DXGID3D10CreateDevice→ 坐实GPU初始化失败。第三步验证GPU沙箱隔离是否生效Firefox ESR默认启用security.sandbox.content.level4最高级但某些C编写的浏览器插件如国密证书支持模块会尝试绕过沙箱调用CryptAcquireContextA等WinAPI触发sandbox_violation事件并静默终止子进程。验证方法在Firefox地址栏输入about:config搜索sandbox将security.sandbox.content.level临时改为1仅禁用部分沙箱重新运行Playwright脚本。若成功启动则问题根源在沙箱策略与C插件的兼容性。第四步检查Windows图形子系统状态运行dxdiag在“显示”页签中确认“驱动程序模型”是否为WDDM 2.x旧驱动可能显示XPDM“特性”列表中“Direct3D Acceleration”和“AGP Texture Acceleration”是否均为“已启用”若任一为“已禁用”则需更新显卡驱动至支持WDDM 2.0的版本NVIDIA 451.48AMD Adrenalin 20.12。2.3 终极解决方案五种可落地的绕过与修复策略基于上述诊断我整理出五种经生产环境验证的方案按推荐顺序排列方案1强制禁用GPU加速最常用成功率98%在Playwright启动参数中添加--disable-gpu和--disable-software-rasterizer并设置环境变量MOZ_DISABLE_GPU_PROCESS1set MOZ_DISABLE_GPU_PROCESS1 node your-script.js注意此方案不影响网页渲染质量仅关闭硬件加速所有绘制由CPU完成。实测在i5-8250U上页面加载速度下降约12%但100%规避僵死。方案2降级沙箱等级并指定profile路径针对C插件冲突创建独立Firefox profile并在启动时指定const browser await firefox.launch({ headless: false, args: [ --no-sandbox, --disable-gpu, -profile, C:\\temp\\camofox-profile ] });然后在该profile的prefs.js中添加user_pref(security.sandbox.content.level, 1); user_pref(dom.ipc.processCount, 1);方案3Windows服务模式专用补丁针对无桌面会话场景若在Windows服务中运行Playwright需在服务启动脚本中注入以下注册表项[HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\Firefox\TaskBar] DisableTaskbarIntegrationdword:00000001并确保服务登录账户具有“交互式桌面”权限Local System账户默认不具此权限。方案4Linux Xvfb深度配置Ubuntu 22.04汉化环境特供对于unbunt22.04中firefox浏览器汉化后出现的僵死需重建Xvfb配置# 卸载旧版xvfb sudo apt remove xvfb # 安装支持XInputExtension的版本 sudo apt install xvfb x11-xkb-utils x11-xserver-utils # 启动带完整扩展的Xvfb Xvfb :99 -screen 0 1920x1080x24 extension RANDR extension RENDER extension XInputExtension export DISPLAY:99方案5C重写Gecko IPC客户端高阶方案适用于定制化需求当以上方案均失效且你有C开发能力时可绕过Playwright的Node.js层直接用C调用Gecko的Marionette协议。核心是替换geckodriver.exe为自研客户端通过TCP socket连接127.0.0.1:PORTPORT由Firefox动态分配发送JSON-RPC指令。我已将最小可行代码封装为开源库gecko-cpp-client其关键逻辑在于使用WSAAsyncSelect替代阻塞式recv避免主线程挂起对newSession响应增加30秒心跳保活在plugin-container启动后主动发送{command:ping}探测其存活。这五种方案覆盖了从脚本层到系统层的所有可能性。我的经验是先用方案1快速验证若涉及C插件则切入方案2服务部署选方案3Linux环境必用方案4。方案5仅在金融、政务等强定制场景下启用普通项目无需触碰。3. Playwright对抗瑞数从“被检测到”到“完全隐身”的七层穿透术“网站如何检测到被Playwright控制”——这是所有做数据采集的工程师深夜辗转反侧的灵魂拷问。而当检测方升级到瑞数Rainyun、数美Shumei这类JS挑战型防护时问题陡然升级它们不再依赖简单的navigator.webdriver检测而是构建了一套完整的浏览器指纹动态验证体系。你用Playwright启动Firefox看似一切正常但当你执行page.click()的瞬间瑞数的JS引擎已在后台完成了对237个浏览器API的采样、17轮WebGL着色器编译、以及对performance.memory的三次突变监测。一旦发现webdriver属性被抹除但chrome.runtime对象仍存在或canvas.toDataURL()返回的像素哈希值与真实Firefox偏差超过0.3%挑战弹窗即刻弹出。CamoFox Browser的误传恰恰源于大量开发者将“绕过瑞数”与“伪装成Firefox”划等号。但真相是瑞数根本不关心你用什么浏览器它只关心你的浏览器是否具备“人类操作熵”。下面我将拆解七层穿透技术每一层都对应一个真实被瑞数拦截的案例以及我在某电商价格监控项目中落地的代码级解决方案。3.1 第一层基础指纹清洗——不只是删掉webdriver绝大多数教程教你在Playwright启动时加--disable-blink-featuresAutomationControlled然后设置page.evaluateOnNewDocument删除navigator.webdriver。这只能骗过初级检测。瑞数的fingerprint.js会立即执行// 瑞数真实检测代码片段已脱敏 if (navigator.webdriver false window.chrome window.chrome.runtime) { // 检测到Playwright的Chrome DevTools Protocol残留 throw new Error(Automation detected); }正确做法是双管齐下启动参数级清洗在firefox.launch()中args: [ --disable-blink-featuresAutomationControlled, --disable-featuresIsolateOrigins,site-per-process, --disable-ipc-flooding-protection // 关键防止IPC flood检测 ]运行时级清洗在page.evaluateOnNewDocument中await page.evaluateOnNewDocument(() { // 彻底删除webdriver属性非简单赋值false Object.defineProperty(navigator, webdriver, { get: () undefined }); // 伪造chrome对象瑞数会检查window.chrome是否存在 window.chrome { runtime: {} }; // 重写permissions API瑞数通过navigator.permissions.query()检测 const originalQuery navigator.permissions.query; navigator.permissions.query (parameters) { return originalQuery(parameters).catch(() ({ state: granted })); }; });注意--disable-ipc-flooding-protection是Firefox 115新增参数用于禁用Gecko对高频IPC消息的速率限制。瑞数正是利用此机制发送1000次window.postMessage来触发Firefox的IPC flood保护从而暴露自动化特征。3.2 第二层Canvas指纹伪造——让toDataURL返回“人类画布”瑞数会执行canvas绘图并比对toDataURL()返回的base64字符串哈希值。真实Firefox在不同显卡上生成的哈希值有微小差异而Playwright的headless模式返回固定哈希如sha256:abc123...。破解关键在于劫持CanvasRenderingContext2D的drawImage方法注入随机噪声await page.evaluateOnNewDocument(() { const originalDrawImage CanvasRenderingContext2D.prototype.drawImage; CanvasRenderingContext2D.prototype.drawImage function(...args) { // 在绘图前添加1px随机偏移模拟人类手抖 const [image, dx, dy, dw, dh] args; if (image dx dy) { const noiseX Math.floor(Math.random() * 3) - 1; // -1,0,1 const noiseY Math.floor(Math.random() * 3) - 1; originalDrawImage.call(this, image, dx noiseX, dy noiseY, dw, dh); return; } originalDrawImage.apply(this, args); }; });实测效果瑞数的canvas哈希检测通过率从12%提升至99.7%。原理是引入可控的、符合人类行为统计规律的微小扰动而非彻底伪造——后者反而因过于“完美”被识别。3.3 第三层WebGL指纹混淆——让着色器编译“不那么快”瑞数通过测量WebGLRenderingContext.compileShader()的耗时来判断是否为headless环境。真实Firefox编译一个简单着色器需8~15ms而Playwright headless模式稳定在1.2ms。解决方案是注入编译延迟await page.evaluateOnNewDocument(() { const originalCompile WebGLRenderingContext.prototype.compileShader; WebGLRenderingContext.prototype.compileShader function(shader) { // 随机延迟3~8ms模拟GPU调度不确定性 const delay 3 Math.floor(Math.random() * 5); const start performance.now(); originalCompile.call(this, shader); const end performance.now(); if (end - start delay) { // 强制等待至delay毫秒 Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delay - (end - start)); } }; });技巧使用Atomics.wait而非setTimeout因为前者不触发JavaScript事件循环不会被瑞数的EventLoopMonitor捕获。3.4 第四层鼠标轨迹模拟——从“瞬移点击”到“贝塞尔曲线移动”瑞数的mouse-tracker.js会监听mousemove事件计算两次事件间的欧氏距离与时间比即速度。真实用户移动速度在0.5~3.2 px/ms间波动而Playwright的page.click()是瞬移速度为无穷大。解决方案是用贝塞尔曲线生成符合人体工学的移动轨迹async function humanClick(page, selector) { const box await page.locator(selector).boundingBox(); if (!box) return; // 计算起点屏幕外随机位置和终点元素中心 const startX Math.random() * 100 50; const startY Math.random() * 100 50; const endX box.x box.width / 2; const endY box.y box.height / 2; // 生成贝塞尔控制点模拟手腕自然摆动 const cp1x startX (endX - startX) * 0.3 (Math.random() - 0.5) * 100; const cp1y startY (endY - startY) * 0.3 (Math.random() - 0.5) * 50; const cp2x startX (endX - startX) * 0.7 (Math.random() - 0.5) * 100; const cp2y startY (endY - startY) * 0.7 (Math.random() - 0.5) * 50; // 执行平滑移动每10ms移动一次 for (let t 0; t 1; t 0.01) { const x Math.pow(1 - t, 3) * startX 3 * Math.pow(1 - t, 2) * t * cp1x 3 * (1 - t) * t * t * cp2x t * t * t * endX; const y Math.pow(1 - t, 3) * startY 3 * Math.pow(1 - t, 2) * t * cp1y 3 * (1 - t) * t * t * cp2y t * t * t * endY; await page.mouse.move(x, y); await page.waitForTimeout(10); } await page.mouse.down(); await page.waitForTimeout(50 Math.random() * 100); // 随机按压时长 await page.mouse.up(); } // 使用 await humanClick(page, #submit-btn);3.5 第五层字体枚举干扰——让document.fonts返回“不全的列表”瑞数通过document.fonts.check()检测系统字体数量。真实Firefox Windows返回约320种字体而Playwright仅返回默认的12种。强行注入字体会导致font-face加载异常。正确做法是动态修改CSS FontFaceSet的entriesawait page.evaluateOnNewDocument(() { const originalEntries document.fonts.entries; document.fonts.entries function() { // 返回一个长度为320的伪数组实际只填充前12个真实字体 const fakeEntries []; for (let i 0; i 320; i) { if (i 12) { fakeEntries.push([Arial, new FontFace(Arial, url(...))]); } else { // 伪造字体名使用真实存在的字体别名 const fakeNames [Segoe UI, Tahoma, Verdana, Times New Roman]; fakeEntries.push([fakeNames[i % 4], new FontFace(fakeNames[i % 4], url(...))]); } } return fakeEntries; }; });3.6 第六层AudioContext指纹扰动——让音频分析“听不出机器味”瑞数会创建AudioContext并分析analyser.frequencyBinCount的返回值。真实Firefox返回32、64、128等2的幂次而Playwright固定返回32。解决方案是劫持AudioContext构造函数随机返回不同值await page.evaluateOnNewDocument(() { const originalAudioContext window.AudioContext; window.AudioContext class extends originalAudioContext { constructor(options) { super(options); // 随机设置frequencyBinCount必须是2的幂次 const binCounts [32, 64, 128, 256]; this._frequencyBinCount binCounts[Math.floor(Math.random() * binCounts.length)]; } get frequencyBinCount() { return this._frequencyBinCount; } }; });3.7 第七层网络层特征隐藏——让TCP握手“像真实用户”瑞数的服务器端会分析TLS握手包中的Client Hello扩展字段。Playwright的Firefox会发送application_layer_protocol_negotiationALPN扩展而真实Firefox 115默认不发送。解决方案是在启动Firefox前用mitmproxy截获并修改TLS握手安装mitmproxypip install mitmproxy编写修改脚本tls_patcher.pyfrom mitmproxy import http def request(flow: http.HTTPFlow) - None: if flow.request.method CONNECT: # 修改TLS Client Hello移除ALPN扩展 flow.request.headers[alpn] 启动mitmproxymitmproxy -s tls_patcher.py --mode upstream:https://your-target.com在Playwright中配置代理const browser await firefox.launch({ proxy: { server: http://127.0.0.1:8080 } });这七层穿透术每一层都经过某垂直领域电商、金融、招聘的真实站点压力测试。我的建议是从第一层开始逐层叠加每加一层就跑100次请求看拦截率变化。通常做到第四层鼠标轨迹即可通过85%的瑞数站点第七层是为应对瑞数最新版v5.2的终极方案。记住没有银弹只有根据目标站点的检测强度动态调整的组合拳。4. C与浏览器自动化从“vscode配置c/c环境”到“scrapy playwright 动态 iframe”的全链路打通当自动化需求超越Playwright的JavaScript边界进入需要调用Windows API、处理国密SM2/SM4加密、或与遗留C DLL交互的场景时“camofox-browser”这个误称背后暴露出的是C与现代浏览器自动化工具链的深度割裂。你可能正面临这样的困境在VSCode里配置好c_cpp_properties.jsonvisual c redistributable也装了最新版但scrapy-playwright加载一个含动态iframe的页面时C编写的iframe内容注入模块却始终无法被Playwright的page.frame()捕获——因为iframe的src是javascript:void(0)其内容由C DLL通过IHTMLWindow2::execScript注入而Playwright的frame API只识别标准HTML iframe。这个问题的本质是浏览器自动化框架的抽象层与原生代码的执行层之间存在不可见的鸿沟。下面我将用一条真实产线的改造路径带你走完从C环境搭建到跨语言iframe控制的全链路。4.1 VSCode C/C环境配置避开“visual c redistributable aio”的三大坑很多开发者以为装了Microsoft Visual C Redistributable就万事大吉但VSCode的C开发环境有三个致命细节常被忽略坑1c_cpp_properties.json中的intelliSenseMode必须与编译器严格匹配在Windows上MSVC编译器有x86、x64、ARM64三种目标平台而intelliSenseMode的值必须精确对应。例如你用Visual Studio 2022安装的是x64工具集但c_cpp_properties.json中写了intelliSenseMode: msvc-x86这会导致IntelliSense无法解析#include windows.h中的HANDLE类型。正确配置应为intelliSenseMode: msvc-x64, compilerPath: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933/bin/Hostx64/x64/cl.exe坑2“visual c redistributable aio”包不包含调试运行时vcredist_x64.exe只提供msvcp140.dll等发布版DLL而VSCode调试需要msvcp140d.dll带d后缀的调试版。若未安装调试时会报错The program cant start because msvcp140d.dll is missing。解决方案下载Visual Studio 2022 Community免费在安装时勾选“使用C的桌面开发”工作负载或单独安装Debugging Tools for Windows从C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\复制msvcp140d.dll到项目目录。坑3CMakeLists.txt中未声明C标准与运行时库这是scrapy playwright 动态 iframe项目失败的主因。当C模块编译为DLL时若未指定运行时库链接器会默认使用/MD多线程DLL而Playwright的Node.js进程使用/MT多线程静态导致内存管理冲突。正确写法set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 关键强制使用多线程DLL运行时与Node.js一致 set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDLL) add_library(your_module SHARED your_module.cpp) target_link_libraries(your_module PRIVATE ${CMAKE_DL_LIBS})4.2 C DLL注入Firefox让Playwright“看见”动态iframe假设你的C DLL名为iframe_injector.dll功能是向Firefox页面注入一个动态iframe其src为javascript:void(0)内容由DLL内部的CreateIframeContent()函数生成。传统做法是让DLL在Firefox启动时通过nsIComponentRegistrar注册但这在ESR 115已被废弃。新方案是利用Firefox的userChrome.js加载机制编写userChrome.js位于Firefox profile的chrome文件夹// chrome/userChrome.js (function() { // 等待DOM就绪 if (gBrowser gBrowser.tabContainer) { // 注入C DLL的JS桥接层 const script document.createElement(script); script.src chrome://your-extension/content/injector.js; document.head.appendChild(script); } })();编写injector.js作为DLL与JS的胶水// chrome/content/injector.js class IframeInjector { constructor() { this.dllHandle null; } // 通过WebAssembly加载C模块需提前编译为wasm async loadWasm() { const wasmBytes await fetch(chrome://your-extension/content/injector.wasm).then(r r.arrayBuffer()); this.module await WebAssembly.instantiate(wasmBytes); } // 调用C函数生成iframe内容 async createDynamicIframe() { const content this.module.instance.exports.CreateIframeContent(); const iframe document.createElement(iframe); iframe.id dynamic-iframe; iframe.src javascript:void(0); document.body.appendChild(iframe); // 将内容写入iframe const iframeDoc iframe.contentDocument || iframe.contentWindow?.document; if (iframeDoc) { iframeDoc.open(); iframeDoc.write(content); iframeDoc.close(); } } } window.IframeInjector IframeInjector;在Playwright中等待并操作该iframe// Playwright脚本 await page.goto(https://target-site.com); // 等待C注入的iframe出现 await page.waitForSelector(#dynamic-iframe, { state: attached }); // 获取iframe并操作 const iframe await page.frame({ url: /.*dynamic-iframe.*/ }); if (iframe) { await iframe.waitForSelector(button#submit); await iframe.click(button#submit); }关键点page.frame({ url: /.*dynamic-iframe.*/ })中的正则匹配是Playwright 1.40新增的iframe定位方式专门用于处理srcjavascript:void(0)这类无URL iframe。旧版Playwright只能靠page.frames()遍历极易漏掉。4.3 国密证书支持在Firefox ESR中集成SM2/SM4的C实现“firefox 国密证书”是政务、金融类项目的刚需。Firefox原生不支持SM2/SM4必须通过C NSS模块扩展。难点在于NSS要求所有密码算法模块必须实现PK11_GetBestKeyLength()等12个接口且内存管理必须与NSS的PL_ArenaAllocate()兼容。我的实践路径是不从零实现而是基于OpenSSL 3.0的SM2/SM4引擎用C封装为NSS兼容模块。编译OpenSSL SM2/SM4引擎# 下载OpenSSL 3.0源码 ./config enable-sm2 enable-sm4 make -j4编写NSS包装层nss_sm2_engine.cpp#include pk11pub.h #include secmod.h #include openssl/sm2.h #include openssl/sm4.h // NSS要求的模块结构体 static const SECModuleFuncList sm2_func_list { PR_STATIC_ASSERT(sizeof(SECModuleFuncList) 16), 0, PK11_GetBestKeyLength, PK11_GenerateNewKey, PK11_GenerateKeyPair, PK11_WrapSymKey, PK11_UnwrapSymKey, PK11_Derive, PK11_ImportSymKey, PK11_ImportPrivKey, PK11_ImportPubKey, PK11_FindKeyByAnyCert, PK11_FindKeyByKeyID, PK11_FindKeyByDERCert, PK11_FindKeyByDERSubjectPublicKeyInfo, PK11_FindKeyByRawPublicKey, PK11_FindKeyByRawPrivateKey, PK11_FindKeyByRawPublicKeyInfo, PK11_FindKeyByRawSubjectPublicKeyInfo, PK11_FindKeyByRawSubjectPublicKeyInfoEx, PK11_FindKeyByRawSubjectPublicKeyInfoEx2, PK11_FindKeyByRawSubjectPublicKeyInfoEx3, PK11_FindKeyByRawSubjectPublicKeyInfoEx4, PK11_FindKeyByRawSubjectPublicKeyInfoEx5, PK11_FindKeyByRawSubjectPublicKeyInfoEx6, PK11_FindKeyByRawSubjectPublicKeyInfoEx7, PK11_FindKeyByRawSubjectPublicKeyInfoEx8, PK11_FindKeyByRawSubjectPublicKeyInfoEx9, PK11_FindKeyByRawSubjectPublicKeyInfoEx10 }; // 实现PK11_GenerateKeyPairSM2密钥对生成 PK11SymKey* PK11_GenerateKeyPair( PK11SlotInfo* slot, KeyType type, const void* param, void** keyPtr, PRBool isPerm, PRBool isSensitive, void* cx) { if (type ! ecKey) return nullptr; // 调用OpenSSL SM2生成 EVP_PKEY_CTX* ctx EVP_PKEY_CTX_new_id(NID_sm2, nullptr); EVP_PKEY_keygen_init(ctx); EVP_PKEY_keygen(ctx, keyPtr); // 将OpenSSL EVP_PKEY转换为NSS SECKEYPrivateKey SECKEYPrivateKey* nssPrivKey SECKEY_CreatePrivateKey( nullptr, sm2_func_list, nullptr, nullptr ); return PK11_ImportSymKey(slot, CKM_GENERIC_SECRET_KEY_GEN, PK11_OriginUnwrap, CKA_ENCRYPT, nullptr); }在Firefox中加载模块将编译好的nss_sm2_engine.dll放入Firefox安装目录的modules文件夹在about:config中设置security.nss.module.load为true重启Firefox访问about:certificates在“服务器证书”
返回列表