---
title: "CrossPKI para CEF — Guia de integração"
description: "Guia de integração do CrossPKI para CEF, que dá a uma aplicação Chromium acesso via JavaScript aos certificados do repositório do Windows ou do keychain do macOS."
canonical: "https://docs.lacunasoftware.com/articles/crosspki"
locale: "pt-BR"
product: "CrossPKI"
last_updated: "2026-10-12"
---

# 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++20` |
  |---|---|---|
  | 93.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-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

```text
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.

```cpp
#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.

```cpp
#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

```bash
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:

```bash
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:

```typescript
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.
