Pular para o conteúdo principal

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​

ArtefatoOrigemComo você o utiliza
crosspki.dllZIP, bin/x64/ e bin/x86/Windows: implantado ao lado do executável e carregado em tempo de execução
libcrosspki.dylibZIP, bin/macos/macOS: implantado em Contents/Frameworks/ e carregado em tempo de execução
crosspki_cef_bridge.h / .cppZIPcompilado na sua aplicação
crosspki-cefnpm, ou js/ no ZIPimportado pelo seu código web
crosspki.hZIPopcional — 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++20
    93.1.14simnão
    110.0.32simsim
    111.2.7simsim
    138.0.62simsim
    149.0.6nãosim

    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-builtins com 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​

  1. Compile bridge/crosspki_cef_bridge.cpp no seu host — no mesmo target que compila o seu CefApp e o seu CefClient.
  2. 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çãoO que é resolvido
Angular, webpack, vite, rollup, esbuildimport { CrossPki } from 'crosspki-cef'
ES modules nativos, sem bundlercrosspki-cef/standalone
Nenhuma etapa de builddist/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​

SintomaCausaSolução
Toda chamada é rejeitada com platformNotSupported, "native bridge not found"A metade de renderização não está instaladaEncaminhe OnContextCreated para RendererBridge (seção 5)
As chamadas nunca terminam — a promise não é resolvida nem rejeitadaUma mensagem está sendo descartada em uma das direçõesEncaminhe 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 carregadaWindows: 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 IDsLibrary validation: uma aplicação com hardened runtime recusou uma biblioteca assinada por outra equipeAssine 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 keychainA chave pertence a outra aplicação — instalada pelo Acesso às Chaves (Keychain Access) ou por outro programaEsperado 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 functionO seu código web foi executado antes de a bridge de renderização instalar o ponto de entradaChame 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 listaA 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ídasEsperado. Só são listados os certificados do usuário com uma chave utilizável
A assinatura parece travadaO diálogo de PIN do smart card, aberto pelo provider, ficou atrás da janela da sua aplicaçãoTraga-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-224A CNG do Windows não tem implementação de SHA-224, e o macOS acompanha o WindowsUse SHA-256 ou mais forte
certificateNotFound para um thumbprint que existeO thumbprint é o Base64 do SHA-256 dos bytes DER, não uma impressão digital SHA-1 em hexadecimalUse literalmente o valor de thumbprint retornado por listCertificatesWithKey()
wrongPassword ao importar um PKCS#12 válidoO conteúdo não é um PKCS#12 válido, ou a senha está erradaConfirme que o arquivo é um .pfx/.p12 e que ele está codificado em Base64 antes de ser passado
ERR_MODULE_NOT_FOUND ao importar o pacoteUm loader ESM que não segue o mapa exportsImporte 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 aceitaCompile 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.

Ver em Markdown