CrossPKI para CEF — Guia de integração
O CrossPKI dá à sua aplicação Chromium acesso, a partir de JavaScript, aos certificados digitais e chaves privadas guardados no repositório de certificados do Windows ou no keychain do macOS do usuário.
Este guia pressupõe um host CEF já funcionando, com o seu próprio CefApp e CefClient,
no Windows, no macOS ou em ambos. A integração consiste em seis callbacks encaminhados,
um arquivo-fonte compilado no seu host, um pacote npm e uma biblioteca nativa implantada
com a sua aplicação — crosspki.dll ao lado do executável no Windows e
libcrosspki.dylib no bundle da aplicação no macOS. O código-fonte que você compila e o
JavaScript que você chama são os mesmos nos dois sistemas.
1. O que você recebe
| Artefato | Origem | Como você o utiliza |
|---|---|---|
crosspki.dll | ZIP, bin/x64/ e bin/x86/ | Windows: implantado ao lado do executável e carregado em tempo de execução |
libcrosspki.dylib | ZIP, bin/macos/ | macOS: implantado em Contents/Frameworks/ e carregado em tempo de execução |
crosspki_cef_bridge.h / .cpp | ZIP | compilado na sua aplicação |
crosspki-cef | npm, ou js/ no ZIP | importado pelo seu código web |
crosspki.h | ZIP | opcional — apenas para chamar a API C diretamente |
A bridge é distribuída como código-fonte porque precisa ser compilada com o seu próprio SDK do CEF e o seu próprio toolset. Nada é vinculado em tempo de build — ela carrega a biblioteca nativa em tempo de execução.
2. Requisitos
-
Windows, x64 ou x86. O CrossPKI usa o repositório de certificados do Windows e a CNG. O ZIP traz uma DLL para cada arquitetura; implante a que corresponde ao processo do seu host, já que um processo só consegue carregar uma DLL da mesma arquitetura (32 ou 64 bits).
-
macOS 10.15 ou posterior em Intel, 11 ou posterior em Apple Silicon. O CrossPKI usa o keychain do usuário por meio do Security.framework.
libcrosspki.dylibé um único arquivo universal para as duas arquiteturas, então não há o que escolher. -
CEF. A bridge é compilada como parte da sua aplicação, então o que importa é que ela compile com o seu SDK. Verificado com MSVC v143 com o build estável atual de cada linha:
CEF /std:c++17/std:c++2093.1.14 sim não 110.0.32 sim sim 111.2.7 sim sim 138.0.62 sim sim 149.0.6 não sim A restrição é o padrão da linguagem, não a bridge — a bridge compila com os dois. Cada linha do CEF restringe o padrão que os seus próprios headers aceitam, e a sua aplicação já está sujeita a essa restrição: o CEF 93 usa
std::result_of, removido no C++20, então um host CEF 93 precisa compilar como C++17; o CEF 149 usa concepts do C++20, então um host CEF 149 precisa compilar como C++20; o CEF 110 e o 138 aceitam qualquer um dos dois.Compile a bridge com o mesmo padrão do restante do seu host. Versões fora desta tabela provavelmente funcionam — uma incompatibilidade aparece como erro de compilação durante a integração, nunca como falha em produção.
No macOS, a bridge é verificada com o Xcode 26 com o CEF 111.2.7, arm64 e x86_64, com as flags de compilador do próprio CEF (
-Werror -Wextra, sem exceções, sem RTTI). Os próprios headers do CEF 111 precisam de-Wno-deprecated-builtinscom esse Xcode; isso afeta qualquer host CEF 111, não só o CrossPKI.A bridge também precisa da
libcef_dll_wrapper, que a sua aplicação quase certamente já vincula. -
Node.js e npm, para o build da sua parte web.
3. Download
https://cdn.lacunasoftware.com/crosspki/crosspki-cef-1.1.0.zip
https://cdn.lacunasoftware.com/crosspki/crosspki-cef-1.1.0.zip.sha256.txt
SHA-256: 4553a3edf683e3c8025e4971b692a66b030a5f47d5231894f18845075a6a1e6e
Um único ZIP atende aos dois sistemas. Cada release tem uma URL imutável, então uma
determinada versão sempre corresponde aos mesmos bytes. A crosspki.dll é assinada com
Authenticode pela Lacuna Software, e a libcrosspki.dylib é assinada com o Developer ID
da Lacuna Software — verifique o checksum e a assinatura antes de implantar. Se o seu
ambiente aplica WDAC ou AppLocker, é a assinatura Authenticode que essas políticas
avaliam.
4. Adicione a bridge ao seu build
- Compile
bridge/crosspki_cef_bridge.cppno seu host — no mesmo target que compila o seuCefAppe o seuCefClient. - Adicione a raiz do pacote aos diretórios de include desse target, para que
#include "bridge/crosspki_cef_bridge.h"seja resolvido.
O diretório de include do seu SDK do CEF continua como está; a bridge inclui os headers
do CEF pelos caminhos include/... habituais.
5. Conecte o processo de renderização
A metade de renderização (renderer) instala o ponto de entrada JavaScript. Ela não carrega a DLL — nenhum código de certificado ou de chave é executado em um processo de renderização.
#include "bridge/crosspki_cef_bridge.h"
class MyApp : public CefApp,
public CefRenderProcessHandler {
public:
CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() override {
return this;
}
void OnContextCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) override {
crosspki_.OnContextCreated(browser, frame, context);
}
void OnContextReleased(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) override {
crosspki_.OnContextReleased(browser, frame, context);
}
bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message) override {
// Return false instead if you have your own messages to handle.
return crosspki_.OnProcessMessageReceived(browser, frame, source_process, message);
}
private:
crosspki::cef::RendererBridge crosspki_;
IMPLEMENT_REFCOUNTING(MyApp);
};
Os três são obrigatórios: é o OnProcessMessageReceived que traz os resultados de volta
do processo do browser, então omiti-lo faz com que as chamadas nunca retornem.
6. Conecte o processo do browser
A metade do browser é dona da DLL e faz o trabalho. Construa-a na thread de UI do
browser, depois que CefInitialize tiver retornado.
#include "bridge/crosspki_cef_bridge.h"
class MyClient : public CefClient,
public CefLifeSpanHandler,
public CefRequestHandler {
public:
MyClient() {
// A stable identifier for your application. Use reverse-DNS notation and keep
// it the same across releases.
crosspki_ = std::make_unique<crosspki::cef::BrowserBridge>("com.example.myapp");
}
CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
CefRefPtr<CefRequestHandler> GetRequestHandler() override { return this; }
bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message) override {
return crosspki_->OnProcessMessageReceived(browser, frame, source_process, message);
}
// Both of these cancel calls still in flight for the browser.
void OnBeforeClose(CefRefPtr<CefBrowser> browser) override {
crosspki_->OnBeforeClose(browser);
}
// Before CEF 124 this callback takes only (browser, status).
void OnRenderProcessTerminated(CefRefPtr<CefBrowser> browser,
TerminationStatus status,
int error_code,
const CefString& error_string) override {
crosspki_->OnRenderProcessTerminated(browser);
}
private:
std::unique_ptr<crosspki::cef::BrowserBridge> crosspki_;
IMPLEMENT_REFCOUNTING(MyClient);
};
Cada chamada é executada em uma thread de background do CEF, então uma leitura lenta do repositório de certificados ou o diálogo do provider de um smart card não bloqueiam a sua interface.
BrowserBridge::IsAvailable() indica se a biblioteca nativa foi encontrada e carregada.
Verifique-o na inicialização para exibir uma mensagem clara, em vez de deixar a primeira
chamada de PKI falhar.
7. Instale a biblioteca JavaScript
npm install crosspki-cef
As definições de TypeScript estão incluídas; nenhum pacote @types é necessário.
O pacote é distribuído em três formatos, e o seu build escolhe o certo automaticamente:
| A sua configuração | O que é resolvido |
|---|---|
| Angular, webpack, vite, rollup, esbuild | import { CrossPki } from 'crosspki-cef' |
| ES modules nativos, sem bundler | crosspki-cef/standalone |
| Nenhuma etapa de build | dist/crosspki.bundle.js em uma tag <script> e, depois, o global CrossPkiLib |
Se você não puder usar o npm, os mesmos arquivos estão no ZIP, em js/.
Observação sobre o Angular: o node-forge é uma dependência CommonJS, então o ng build
pode emitir avisos de otimização (optimization bailouts). Adicione node-forge a
allowedCommonJsDependencies no angular.json ou importe o ponto de entrada
/standalone, que já o traz embutido.
8. Implante
Windows. Distribua a crosspki.dll ao lado do executável do seu host, junto com a
libcef.dll e o restante dos binários do CEF — bin/x64/ para um host de 64 bits,
bin/x86/ para um de 32 bits. A bridge procura primeiro ali e só depois recorre ao
caminho de busca padrão, o que impede que uma cópia perdida em outro lugar da máquina
seja carregada no lugar dela.
macOS. Copie bin/macos/libcrosspki.dylib para Contents/Frameworks/ no bundle da
sua aplicação, ao lado de Chromium Embedded Framework.framework. Só a aplicação
principal precisa dela — os apps auxiliares (helpers) nunca a carregam, porque o
CrossPKI é executado no processo do browser. A bridge procura primeiro em
Contents/Frameworks/, depois ao lado do executável em Contents/MacOS/ e, por fim,
pelo rpath do seu executável; ela nunca carrega a biblioteca só pelo nome, o que
permitiria que o diretório de trabalho fornecesse uma.
Em seguida, assine-a novamente com a sua própria identidade, como parte da assinatura do seu bundle, e faça a notarização da aplicação como você já faz:
codesign --force --sign "Developer ID Application: <you>" --options runtime \
--timestamp "MyApp.app/Contents/Frameworks/libcrosspki.dylib"
Uma aplicação com hardened runtime só carrega bibliotecas assinadas pela sua própria
equipe (library validation), então a nossa assinatura existe para você verificar o que
recebeu, não para ser distribuída como está. Ferramentas que assinam um bundle inteiro —
o Xcode, scripts no estilo do electron-osx-sign, codesign --deep — fazem isso por
você.
Não há nada a registrar, nenhum componente COM, nenhum entitlement e nenhuma etapa de instalador, em nenhum dos dois sistemas.
9. Verifique a integração
Execute isto no contexto web da sua aplicação. Ele percorre o caminho inteiro — renderização, IPC, processo do browser, DLL — sem tocar nos certificados do usuário:
import { CrossPki, CrossPkiError } from 'crosspki-cef';
async function checkCrossPki() {
try {
const version = await CrossPki.getVersion();
console.log(`CrossPKI is wired up. Native library version ${version}.`);
const certs = await CrossPki.listCertificatesWithKey();
console.log(`${certs.length} certificate(s) with a private key.`);
for (const cert of certs) {
console.log(` - ${cert.subjectDisplayName}, expires ${cert.validityEnd}`);
}
} catch (e) {
if (e instanceof CrossPkiError) {
console.error(`CrossPKI failed [${e.code}]: ${e.message}`);
} else {
console.error(e);
}
}
}
Uma máquina sem certificados informa 0 — esse é um resultado correto, não uma falha.
Certificados cuja chave privada está ausente ou inutilizável não são listados.
Todo método do CrossPKI retorna uma promise e a rejeita com um CrossPkiError que traz
um code. Tome decisões com base no code, nunca no texto da mensagem.
Para a API completa — assinatura, geração de chaves, CSRs, importação de PKCS#12, campos ICP-Brasil —, veja https://docs.lacunasoftware.com/articles/crosspki.
10. Sandbox
O CrossPKI funciona com o sandbox do Chromium habilitado ou desabilitado e não exige
que você mude o seu modelo de processos em nenhum dos casos. A biblioteca nativa é
carregada no processo do browser, nunca em um processo de renderização, então um
processo de renderização em sandbox é totalmente compatível — assim como um host que
roda com no_sandbox = true, que é como o próprio ambiente de testes do CrossPKI roda,
nos dois sistemas.
Isto diz respeito ao sandbox do Chromium. No macOS, executar a aplicação inteira no App
Sandbox (com.apple.security.app-sandbox, exigido pela Mac App Store) é outra coisa e
não foi testado: ele restringe o acesso ao keychain.
Se a sua aplicação mantém o sandbox ativo, nada aqui exige relaxá-lo. Se ela roda sem sandbox, nada aqui exige adicioná-lo.
11. Solução de problemas
| Sintoma | Causa | Solução |
|---|---|---|
Toda chamada é rejeitada com platformNotSupported, "native bridge not found" | A metade de renderização não está instalada | Encaminhe OnContextCreated para RendererBridge (seção 5) |
| As chamadas nunca terminam — a promise não é resolvida nem rejeitada | Uma mensagem está sendo descartada em uma das direções | Encaminhe OnProcessMessageReceived tanto no handler de renderização quanto no client do browser, e confirme que um BrowserBridge foi de fato construído |
As chamadas são rejeitadas com internal, "native bridge failure (1)" | A biblioteca nativa não foi encontrada ou não pôde ser carregada | Windows: coloque a DLL ao lado do executável e confirme que a arquitetura (32 ou 64 bits) corresponde à do host. macOS: coloque a dylib em Contents/Frameworks/ e confirme que ela está assinada pela sua equipe (abaixo). Verifique se IsAvailable() retorna true |
macOS: a biblioteca está presente, mas não carrega; o Console mostra code signature … not valid for use in process ou different Team IDs | Library validation: uma aplicação com hardened runtime recusou uma biblioteca assinada por outra equipe | Assine novamente a libcrosspki.dylib com a sua identidade (seção 8) |
| macOS: um diálogo pergunta se a sua aplicação pode usar uma chave do keychain | A chave pertence a outra aplicação — instalada pelo Acesso às Chaves (Keychain Access) ou por outro programa | Esperado no macOS; Permitir Sempre (Always Allow) memoriza a resposta. Chaves que a própria aplicação importou ou gerou não perguntam |
TypeError: window.crossPkiQuery is not a function | O seu código web foi executado antes de a bridge de renderização instalar o ponto de entrada | Chame o CrossPKI depois do carregamento da página, não durante a avaliação inicial dos scripts |
| Um certificado visível no Windows ou no Acesso às Chaves não aparece na lista | A chave privada dele está ausente ou inacessível — uma entrada órfã, ou um token que não está conectado. No macOS, as identidades do keychain do Sistema (System) também são excluídas | Esperado. Só são listados os certificados do usuário com uma chave utilizável |
| A assinatura parece travada | O diálogo de PIN do smart card, aberto pelo provider, ficou atrás da janela da sua aplicação | Traga-o para a frente pela barra de tarefas. Ele não tem janela proprietária, então pode ser desenhado atrás da aplicação |
algorithmNotSupported ao usar SHA-224 | A CNG do Windows não tem implementação de SHA-224, e o macOS acompanha o Windows | Use SHA-256 ou mais forte |
certificateNotFound para um thumbprint que existe | O thumbprint é o Base64 do SHA-256 dos bytes DER, não uma impressão digital SHA-1 em hexadecimal | Use literalmente o valor de thumbprint retornado por listCertificatesWithKey() |
wrongPassword ao importar um PKCS#12 válido | O conteúdo não é um PKCS#12 válido, ou a senha está errada | Confirme que o arquivo é um .pfx/.p12 e que ele está codificado em Base64 antes de ser passado |
ERR_MODULE_NOT_FOUND ao importar o pacote | Um loader ESM que não segue o mapa exports | Importe crosspki-cef/standalone ou use o bundle via <script> |
A bridge não compila, com erros dentro dos próprios headers do CEF (std::result_of, convertible_to) | O padrão de C++ não corresponde ao que a sua versão do CEF aceita | Compile a bridge com a mesma configuração /std: do restante do seu host — veja a seção 2 |
Suporte
Ao relatar um problema, informe a sua versão do CrossPKI (CrossPki.getVersion()), a sua
versão do CEF e a sua versão do Windows ou do macOS.