From b19d09a236e6cf0724f8a9656dec8ecec9797273 Mon Sep 17 00:00:00 2001 From: Claudecio Martins Date: Tue, 20 Oct 2026 16:43:46 -0300 Subject: [PATCH] docs(sdk): add comprehensive integration examples in sdk/examples --- sdk/examples/01-crypto-login.js | 68 +++++++++++++++++++++++++ sdk/examples/02-sign-digest.js | 59 ++++++++++++++++++++++ sdk/examples/03-sign-pades.js | 61 +++++++++++++++++++++++ sdk/examples/04-error-handling.js | 82 +++++++++++++++++++++++++++++++ sdk/examples/README.md | 75 ++++++++++++++++++++++++++++ 5 files changed, 345 insertions(+) create mode 100644 sdk/examples/01-crypto-login.js create mode 100644 sdk/examples/02-sign-digest.js create mode 100644 sdk/examples/03-sign-pades.js create mode 100644 sdk/examples/04-error-handling.js create mode 100644 sdk/examples/README.md diff --git a/sdk/examples/01-crypto-login.js b/sdk/examples/01-crypto-login.js new file mode 100644 index 0000000..e279fbe --- /dev/null +++ b/sdk/examples/01-crypto-login.js @@ -0,0 +1,68 @@ +/** + * Exemplo 01: Login Seguro via Certificado Digital (Challenge-Response) + * + * Demonstra como autenticar um usuário sem senhas, assinando um desafio emitido + * pelo servidor backend da sua aplicação web. + */ + +import { CyberCoreSigner, AgentNotRunningError } from '@cybercore/signer-sdk'; + +async function main() { + const signer = new CyberCoreSigner({ + applicationName: 'Portal Corporativo ERP' + }); + + // 1. Verifica se o agente desktop está ativo na máquina + const isRunning = await signer.isInstalled(); + if (!isRunning) { + console.error('O CyberCore Signer não está em execução. Baixe e abra o aplicativo para continuar.'); + return; + } + + // 2. Conecta e obtém autorização + console.log('Conectando ao agente...'); + await signer.connect(); + + // 3. Lista os certificados digitais disponíveis + const certs = await signer.getCertificates(); + console.log(`Foram localizados ${certs.length} certificado(s) no computador:`); + certs.forEach((c, idx) => { + console.log(`[${idx + 1}] ${c.alias} (${c.certificate_type}) - ${c.subject_name}`); + }); + + if (certs.length === 0) { + console.warn('Nenhum certificado digital A1 ou token A3 encontrado.'); + return; + } + + // Seleciona o primeiro certificado disponível + const selectedCert = certs[0]; + + // 4. Obtém o desafio emitido pelo seu backend (simulado aqui) + const serverChallenge = `auth-session-${Date.now()}-${Math.random().toString(36).substring(2)}`; + console.log(`Desafio recebido do servidor: ${serverChallenge}`); + + // 5. Executa a autenticação criptográfica + console.log('Solicitando confirmação de login no CyberCore Signer...'); + const authResult = await signer.authenticate({ + certificateId: selectedCert.id, + challenge: serverChallenge + }); + + console.log('Login realizado com sucesso!'); + console.log('Titular:', authResult.subjectName); + console.log('CPF/CNPJ:', authResult.subjectCpfCnpj); + console.log('Algoritmo:', authResult.algorithm); + console.log('Assinatura do Desafio (Base64):', authResult.signature.substring(0, 32) + '...'); + + // O payload retornado deve ser enviado ao seu backend para verificação final: + // POST /api/auth/verify-signature { challenge, signature, certificate } +} + +main().catch(err => { + if (err instanceof AgentNotRunningError) { + console.error('Agente offline!\n' + err.actionableGuidance); + } else { + console.error(`Erro no login (${err.code}):`, err.message); + } +}); diff --git a/sdk/examples/02-sign-digest.js b/sdk/examples/02-sign-digest.js new file mode 100644 index 0000000..4b30a38 --- /dev/null +++ b/sdk/examples/02-sign-digest.js @@ -0,0 +1,59 @@ +/** + * Exemplo 02: Assinatura Desacoplada de Digest (Hash SHA-256) + * + * Demonstra a arquitetura recomendada do CCSP/1: + * O documento NUNCA sai do navegador / servidor da aplicação. Apenas o hash + * de 32 bytes (SHA-256) é enviado ao CyberCore Signer para ser assinado localmente. + */ + +import { CyberCoreSigner } from '@cybercore/signer-sdk'; + +async function main() { + const signer = new CyberCoreSigner({ + applicationName: 'Sistema de Prontuário Eletrônico' + }); + + await signer.connect(); + + const certs = await signer.getCertificates(); + if (certs.length === 0) { + throw new Error('Nenhum certificado disponível.'); + } + + const selectedCert = certs[0]; + + // Simula o documento clínico ou contrato gerado na aplicação + const documentoTexto = 'Evolução clínica do paciente João da Silva - 20/10/2026. Procedimento sem intercorrências.'; + + // 1. Calcula o SHA-256 no próprio ambiente local + const digestBase64 = await signer._calculateSha256(documentoTexto); + console.log('Hash SHA-256 gerado localmente:', digestBase64); + + // 2. Envia apenas o digest para o CyberCore Signer + console.log('Aguardando confirmação do usuário no diálogo nativo...'); + const result = await signer.sign({ + certificateId: selectedCert.id, + digest: digestBase64, + documentName: 'evolucao_clinica_1048.txt', + documentType: 'TXT', + purpose: 'Assinatura de Prontuário Médico', + expiresInSeconds: 120 + }); + + console.log('Documento assinado com sucesso!'); + console.log('Status:', result.status); + console.log('Request ID:', result.requestId); + console.log('Algoritmo:', result.algorithm); + console.log('Assinatura (Base64):', result.signature.substring(0, 48) + '...'); + console.log('Cadeia X.509 recebida:', result.certificateChain.length, 'certificado(s)'); + + // 3. Validação preliminar da assinatura no client + const isValid = await signer.verify({ + signature: result.signature, + digest: result.digest, + certificate: result.certificate + }); + console.log('Assinatura é consistente?', isValid); +} + +main().catch(console.error); diff --git a/sdk/examples/03-sign-pades.js b/sdk/examples/03-sign-pades.js new file mode 100644 index 0000000..8758ff8 --- /dev/null +++ b/sdk/examples/03-sign-pades.js @@ -0,0 +1,61 @@ +/** + * Exemplo 03: Assinatura de PDF Completo PAdES com Selo Visual + * + * Demonstra como assinar um arquivo PDF aplicando o selo gráfico ICP-Brasil ou Gov.br + * estilizado pelo CyberCore Signer, retornando o PDF assinado completo em Base64/Blob. + */ + +import { CyberCoreSigner } from '@cybercore/signer-sdk'; + +async function main() { + const signer = new CyberCoreSigner({ + applicationName: 'Assinador de Contratos Online' + }); + + await signer.connect(); + + // 1. Consulta os temas de selos visuais disponíveis no agente + const themesResp = await signer.getSealThemes(); + console.log('Selo padrão configurado:', themesResp.default_seal_id); + console.log('Temas disponíveis:', themesResp.seals.map(s => s.name).join(', ')); + + // 2. Mock de um PDF de teste (no navegador, normalmente é obtido via ) + const fakePdfContent = Buffer.from('%PDF-1.4\n1 0 obj\n<< /Type /Catalog >>\nendobj\n%%EOF'); + + const certs = await signer.getCertificates(); + if (certs.length === 0) { + console.error('Nenhum certificado para assinatura.'); + return; + } + + // 3. Executa a assinatura com estampa gráfica posicionada no rodapé da última página + console.log('Enviando solicitação de assinatura PAdES...'); + const result = await signer.signPdfDocument({ + pdf: fakePdfContent, + certificateId: certs[0].id, + documentName: 'contrato_prestacao_servicos.pdf', + position: 'BOTTOM_RIGHT', + pageChoice: 'LAST_PAGE', + scale: 1.0, + visibleSeal: true, + sealThemeId: 'icp-brasil', + reason: 'Concordância integral com os termos do contrato', + location: 'Brasília - DF', + cpfDisplayMode: 'MASKED', // Exibe CPF mascarado no carimbo (ex: ***.456.789-**) + includeDateTime: true, + customExtraInfo: 'Matrícula: 55420-9' + }); + + console.log('PDF assinado com sucesso!'); + console.log('Titular:', result.subjectName); + console.log('CPF/CNPJ:', result.subjectCpfCnpj); + console.log('Tamanho do PDF assinado:', result.signedPdf.length, 'caracteres Base64'); + + // No navegador web, você pode invocar diretamente: + // result.download('contrato_assinado.pdf'); + // Ou obter os bytes binários para envio via fetch/multipart: + const pdfBytes = result.getSignedPdfBytes(); + console.log('Bytes binários gerados:', pdfBytes.length); +} + +main().catch(console.error); diff --git a/sdk/examples/04-error-handling.js b/sdk/examples/04-error-handling.js new file mode 100644 index 0000000..781119d --- /dev/null +++ b/sdk/examples/04-error-handling.js @@ -0,0 +1,82 @@ +/** + * Exemplo 04: Tratamento Estruturado e Acionável de Erros + * + * Demonstra como capturar cada erro específico do CCSP/1 e orientar + * o usuário final com mensagens claras e passos práticos de resolução. + */ + +import { + CyberCoreSigner, + AgentNotRunningError, + ApplicationNotAuthorizedError, + UserCancelledError, + CertificateNotFoundError, + CertificateExpiredError, + CertificateInvalidError, + PrivateKeyNotAvailableError, + PinRequiredError, + PinIncorrectError, + TokenNotFoundError, + RequestExpiredError, + TimeoutError, + CyberCoreError +} from '@cybercore/signer-sdk'; + +async function performSigningOperation(signer, certId, fileDigest) { + try { + return await signer.sign({ + certificateId: certId, + digest: fileDigest, + documentName: 'termo_responsabilidade.pdf' + }); + } catch (err) { + if (err instanceof AgentNotRunningError) { + // O agente local está fechado ou bloqueado + console.warn('⚠️ AGENTE NÃO DETECTADO:'); + console.warn(err.actionableGuidance); + // Sugestão de UI: exibir banner com botão "Baixar CyberCore Signer" + } else if (err instanceof ApplicationNotAuthorizedError) { + // O usuário clicou em "Rejeitar" no prompt de autorização da aplicação + console.warn('⚠️ APLICAÇÃO NÃO AUTORIZADA:'); + console.warn('O usuário não autorizou este portal no diálogo de segurança do CyberCore Signer.'); + } else if (err instanceof UserCancelledError) { + // O usuário cancelou a assinatura explicitamente na janela nativa + console.info('ℹ️ Assinatura cancelada pelo usuário.'); + } else if (err instanceof CertificateExpiredError) { + console.error('❌ CERTIFICADO EXPIRADO: O certificado digital selecionado expirou.'); + } else if (err instanceof CertificateInvalidError) { + console.error('❌ CERTIFICADO INVÁLIDO: O certificado possui estrutura corrompida ou foi revogado.'); + } else if (err instanceof PrivateKeyNotAvailableError) { + console.error('❌ CHAVE PRIVADA INDISPONÍVEL: A chave privada deste certificado não está acessível no computador.'); + } else if (err instanceof PinIncorrectError) { + console.error('❌ PIN INCORRETO: O PIN digitado para o token A3 está errado. Cuidado com o bloqueio do dispositivo.'); + } else if (err instanceof PinRequiredError) { + console.error('❌ PIN OBRIGATÓRIO: É necessário fornecer o PIN do dispositivo criptográfico.'); + } else if (err instanceof TokenNotFoundError) { + console.error('❌ TOKEN NÃO ENCONTRADO: O token USB ou smart card foi removido da porta USB.'); + } else if (err instanceof RequestExpiredError) { + console.warn('⚠️ REQUISIÇÃO EXPIRADA: O tempo limite para confirmação do usuário esgotou.'); + } else if (err instanceof TimeoutError) { + console.error('⏱️ TIMEOUT: O agente demorou mais tempo que o esperado para responder.'); + } else if (err instanceof CyberCoreError) { + console.error(`⚠️ ERRO DO PROTOCOLO CCSP (${err.code}):`, err.message); + } else { + console.error('Erro desconhecido:', err); + } + throw err; + } +} + +async function main() { + const signer = new CyberCoreSigner({ timeoutMs: 15000 }); + const fakeDigest = Buffer.alloc(32, 9).toString('base64'); + + console.log('Testando tratamento de erros com certificado simulado expirado...'); + try { + await performSigningOperation(signer, 'cert-expirado', fakeDigest); + } catch { + console.log('Tratamento de exceções concluído com sucesso.'); + } +} + +main().catch(() => {}); diff --git a/sdk/examples/README.md b/sdk/examples/README.md new file mode 100644 index 0000000..de59ac5 --- /dev/null +++ b/sdk/examples/README.md @@ -0,0 +1,75 @@ +# Exemplos de Integração — CyberCore Signer Web SDK + +Este diretório contém exemplos práticos e prontos para uso demonstrando os principais fluxos de integração do `@cybercore/signer-sdk` com aplicações web. + +--- + +## 📁 Arquivos de Exemplo + +| Arquivo | Descrição | +| :--- | :--- | +| [`01-crypto-login.js`](./01-crypto-login.js) | Autenticação segura por desafio criptográfico (Challenge-Response) sem senhas. | +| [`02-sign-digest.js`](./02-sign-digest.js) | Assinatura desacoplada em que o navegador calcula o hash SHA-256 e o CyberCore assina apenas o digest. | +| [`03-sign-pades.js`](./03-sign-pades.js) | Assinatura digital completa de PDF (PAdES) com selo gráfico posicionado (ICP-Brasil / Gov.br). | +| [`04-error-handling.js`](./04-error-handling.js) | Captura e tratamento estruturado de todos os erros do protocolo CCSP/1 com guia acionável. | + +--- + +## 🚀 Como Executar + +### Pré-requisitos +1. Agente desktop **CyberCore Signer** em execução na máquina (`127.0.0.1:21325`). +2. Node.js 18+ instalado. + +### Execução direta via Node.js +Como o pacote usa módulos ES (`"type": "module"`), você pode testar diretamente com: + +```bash +# Executar exemplo de login +node examples/01-crypto-login.js + +# Executar exemplo de assinatura de digest +node examples/02-sign-digest.js + +# Executar exemplo de assinatura PAdES com selo +node examples/03-sign-pades.js + +# Executar exemplo de tratamento de erros +node examples/04-error-handling.js +``` + +--- + +## 💡 Uso em Projetos Frontend (React, Vue, Angular, Svelte, Vanilla) + +Instale o pacote a partir do seu registry npm privado: +```bash +npm install @cybercore/signer-sdk +``` + +Importe os métodos e tipos em qualquer componente ou serviço web: +```typescript +import { CyberCoreSigner, AgentNotRunningError } from '@cybercore/signer-sdk'; + +const signer = new CyberCoreSigner({ + applicationName: 'Meu Sistema Web' +}); + +async function assinarDocumento(arquivoPdf: File, certId: string) { + try { + const resultado = await signer.signPdfDocument({ + pdf: arquivoPdf, + certificateId: certId, + position: 'BOTTOM_RIGHT', + sealThemeId: 'icp-brasil' + }); + + // Baixa o documento assinado no navegador + resultado.download('documento_assinado.pdf'); + } catch (err) { + if (err instanceof AgentNotRunningError) { + alert(err.actionableGuidance); + } + } +} +```