# Handoff: ativação do cert SPB T010 pelo HSM + feature self-service de certificados

Data: 2026-06-30
Sessão: continuação do foco SPB/MQ/HSM
Branches: `main` (certs reais + design), `feat/spb-cert-selfservice` (implementação em andamento)

## 1. Estado canônico (o que está no ar agora)

A cabine **SPB agora assina e decifra as mensagens do BACEN usando o HSM** (RTM EcosCryptoServer, `https://monetarie-hsm-hml.priv.rtmcloud.net.br` porta **443**, vHSM **60042**). A identidade de assinatura do SPB passou a ser o certificado ICP-Brasil **real T010** da Monetarie (CN=MONETARIE SOCIEDADE DE CREDITO DIRETO S.A T010, emitido pela AC VALID SPB v5, código de AC **6**), cuja **chave privada vive no HSM** sob o UID `V/YVgxtsO+m2jJyNIOgc` (pública `T4GGZ4edSga/jRacj2mb`).

O **T010 foi ATIVADO no BACEN** via uma mensagem **GEN0006 assinada pelo HSM**, confirmada pelo **GEN0006R1** (decodificado e com a assinatura do BACEN verificada). A cabine PIX já assinava pelo HSM (chave CPIA, UID `lMcWo5...`). Portanto **PIX e SPB agora assinam pelo HSM**; o cert local/dev self-signed do SPB **não é mais o assinante**.

Tudo isso é **permanente** (sobrevive a restart): ECS `spb-api` task-def **:21**, imagem `…/monetarie/spb-api@sha256:d25a5c0f…` (camada com os certs reais sobre a `…@sha256:ce693590`), com as envs:
- `RTM_HSM_ENABLED=true`
- `RTM_HSM_PRIVATE_KEY_UID=V/YVgxtsO+m2jJyNIOgc`
- `RTM_HSM_PUBLIC_KEY_UID=T4GGZ4edSga/jRacj2mb`
- `RTM_HSM_VHSM=60042`
- `BACEN_CERT_PEM_FILE=monetarie_spb_t010.pem`
- `WEBHOOK_ALLOW_UNSIGNED_INTERNAL=true`
- `BACEN_CONTROL_PREFIX=MON`

Verificado no boot (rpc no node novo): `RtmHsm.enabled?()=true`, `our_certificate` serial = T010 (`00000000000000005D6A6A9B53DAAD7E`), `lookup_by_serial(T068)=:ok`, destino SPB01 AC=1, `validate_key_uid=:ok`, prefixo=MON, webhook=true.

## 2. A sequência de bugs reais (todos achados empiricamente, zero inferência)

A ativação do T010 só fechou depois de descobrir e corrigir, um a um:

1. **`EGEN9907 - Código da AC do Certificado do Destinatário Inválido`.** Causa: os arquivos `priv/certs/bacen_00038166_T068/T069.pem` do repositório eram **FALSOS** (self-signed `CN=monetarie-spb-local`, AC code **0**). Como ciframos para o cert de destino do BACEN, o `ac_cert_dest` ia 0 e o BACEN rejeitava. Correção: importar os certs **REAIS** do BACEN (BANCO CENTRAL DO BRASIL T068/T069, emitidos por **AC SERPRO**, AC code **1**), fornecidos pelo dono. Serial T068 = `624D56E1377AD7142ED7A3B4`, T069 = `6231240A831BE7E6A0CC7EA3`.
2. **`stale_sweep` em qualquer tráfego** (sweeper que passa `sent_to_mq -> error` após 60s). Causa: o sidecar do MQ postava no webhook `/api/mq/webhook/SPB01` **sem o header `X-Webhook-Signature`** e o `WebhookAuthPlug` respondia **401**, descartando toda resposta do BACEN; sem R1 em 60s, o sweeper marcava erro. Correção: `WEBHOOK_ALLOW_UNSIGNED_INTERNAL=true` (o sidecar é interno 127.0.0.1 e não assina; o plug lê esse env dinamicamente). Esse era também o motivo das filas inbound nunca drenarem.
3. **GEN0006R1 não aparecia na tela.** A R1 decifrava (cod_erro 0, BACEN aceitou) mas a **verificação de assinatura falhava** com `{:sender_cert_not_loaded, "…624D56E1…"}`: o cert de assinatura do BACEN (T068, serial 624D56E1) não estava em `bacen_certificates()` (só os fakes), então `find_bacen_certificate_by_serial` não achava. Correção: com os certs reais carregados no boot (`load_bacen_certificates` lê todos os `bacen_*.pem`), o serial passa a resolver e a verificação fica `:ok`.
4. **NumCtrlIF saía `CEC…`** (resíduo do legado Cecresa). Correção: `BACEN_CONTROL_PREFIX=MON` (o `control_prefix/0` lê esse env).

Provas do HSM (sem inferência): round-trip (cifrar com a pública do T010 e o HSM decifrou com a privada T010, match); e a GEN0006R1 real foi capturada ao vivo via `:dbg` tracing de `MessagePacker.unpack/1`, decodificou e a assinatura verificou. Mensagens antigas cifradas para um cert anterior (serial `7B9AB4FA…`) davam HSM 500 (lixo) e foram purgadas das filas + DLQ.

## 3. Detalhes do GEN0006 (ativação de certificado)

- O `MessagePacker` já trata GEN0006 (`message_packer.ex:141`, tratamento_especial=1, cert ainda não ativado).
- Campos do `gen0006.ex`: `IdentdEmissor`/`ISPBIF` = `46026562`, `IdentdDestinatario` = `00038166`, `DtMovto`, `NumCtrlIF`, `NUOp` (gerados pelo sistema), e os dois do certificado:
  - **`CodCertifrAtv`** = o **código da AC** (numérico, máx 2 dígitos). Para o T010 (VALID) = **6**. NÃO é o serial. Bate com `get_ac_code` e com o `ac_cert_assinatura` do header.
  - **`CertifAtv`** = o **serial** do certificado em 32 hex com zeros à esquerda (ex. `00000000000000005D6A6A9B53DAAD7E`). O nome do campo engana; é o serial, não o cert base64.
- Referência real: `spb/services/bacen_gateway/test/fixtures/bacen_real_samples/sample_GEN0006.txt` (amostras reais da Monetarie com `CodCertifrAtv`=1/7 e `CertifAtv`=serial).
- A GEN0006 pode ser enviada pela tela do SPB admin (Compor + Enviar) ou por `POST /api/messages/send` (tem `dry_run`).

## 4. MQ (estado e como conectou)

- Sender SDR (`C46026562.00038166.1`) e Receiver RCVR (`C00038166.46026562.1`) ambos conectados ao BACEN (`QM.SPB01_H_BC`). A entrada exigiu `RESET CHANNEL(C00038166.46026562.1) SEQNUM(1463)` (o BACEN mandava 1463 e nós esperávamos 1). Saída exigiu `RESET … SEQNUM(2711)` no sender.
- VM do MQ: `097514rj-mq-hml` / `172.31.2.50`, QM `QM.46026562.01`. SSH `herbeth@172.31.2.50` via EC2 OpenVPN `10.45.1.30`. `amqsbcg` NÃO está instalado nessa VM; para inspecionar mensagem use `:dbg` tracing no spb-api.
- A rota RSFN da VM e o `resolv.conf` foram ajustados pela RTM (ver `Diagnostico-MQ-SPB-BACEN-172.31.2.50-20260630.md` no Desktop). PENDENTE: tornar o `resolv.conf` persistente (RTM).

## 5. Feature self-service de certificados (em andamento)

Design completo: `docs/plans/2026-06-30-spb-bacen-cert-selfservice-import-design.md`. Duas trilhas centrais (pedido do dono "do Bacen e do nosso tb"):
- **Trilha A (certs do BACEN)**: import/uso/verificação por serial, pickup sem restart.
- **Trilha B (nossos certs de assinatura)**: import + vínculo ao UID do HSM + verify (round-trip) + ativação via GEN0006 + promoção atômica, com o cuidado da **transição dupla-chave** (assinar a GEN0006 com o UID NOVO e continuar decifrando o inbound com o UID ATIVO até a promoção; o `RtmHsm` precisa de UID por chamada).

Implementado e commitado na branch `feat/spb-cert-selfservice` (TDD):
- **A1** (`5c52bb87`): migration + schema `bacen_domain_certificates` (versionado, 1 ativo por domínio).
- **A2** (`5c52bb87`): `BacenCertStore` (parse/validação que bloqueia não-BACEN/AC 0/expirado; import versionado; `active_pem`/`list_active`/`ets_entries`). 6/0 testes. Mais o fallback de `get_institution_cert("00038166")` sem domínio para SPB01/T068.
- **A3** (`9ee40d6e`): `CertificateRegistry.refresh_bacen_certificates/0` + boot lê do banco (sobrepondo arquivos, fallback resiliente). 1/0 teste.

Falta: **A4** (controller/rotas admin preview/import/list/activate), **A5** (tela), **B1** (schema `our_signing_certificates`), **B2** (`RtmHsm` UID por chamada + lifecycle import/verify/activate GEN0006), **B3** (controller + aba no front).

Nota de teste: `certificate_registry_db_test.exs` tem 1 falha **pré-existente** (confirmada no commit base `c55e17dd`; `put_institution_cert`+`lookup_by_serial`), não é regressão desta sessão.

## 6. Proibido / Correto

PROIBIDO:
- Reverter o endpoint do HSM para `:6443` (é diagnóstico histórico de gateway K8s, não o runtime). O runtime é **443**.
- Colocar qualquer chave privada em repositório, markdown, `.env` ou Terraform. As chaves vivem no HSM e os segredos no AWS Secrets Manager.
- Core assinar/decifrar qualquer mensagem BACEN (TED/PIX/SPB/DICT/ICOM). Quem assina são as cabines PIX e SPB; Core publica eventos via NATS.
- Reintroduzir os certs FAKE do BACEN (`CN=monetarie-spb-local`, AC 0) no lugar dos reais.

CORRETO:
- HSM em 443, vHSM 60042; SPB usa o cert T010 (UID `V/YVgxtsO+m2jJyNIOgc`); PIX usa CPIA (UID `lMcWo5...`).
- Certs do BACEN: T068 (SPB01) e T069 (MES01), emitidos por AC SERPRO (código 1).
- `CodCertifrAtv` = código da AC (não serial); `CertifAtv` = serial 32 hex.
- BACEN é a verdade; quando ele rejeita, o defeito é nosso e precisa ser provado empiricamente.

## 7. Próximas ações recomendadas

1. Continuar a feature self-service: A4 (API da trilha A) -> A5 (tela) -> B1..B3 (nossos certs, com o `RtmHsm` UID por chamada e a transição dupla-chave do design).
2. RTM: persistir o `resolv.conf` da VM do MQ; abrir MES01 `:12522`.
3. Validar uma GEN0001 (echo) ponta a ponta agora que tudo está no HSM (deve ficar 100%: enviar -> R1 -> verificado -> tela).
4. Discrepância anotada: o IB mostrava 0 chaves PIX enquanto a cabine tinha EVP ativa (tratar quando estabilizar o SPB).

## 8. Referências

- Design: `docs/plans/2026-06-30-spb-bacen-cert-selfservice-import-design.md`
- Memórias: `monetarie-spb-hsm-t010-activation`, `monetarie-spb-bacen-cert-import-selfservice`, `monetarie-hsm-ecoscryptoserver-endpoint`, `monetarie-mq-hml-spb-server-fix`
- Diagnóstico MQ: `~/Desktop/Diagnostico-MQ-SPB-BACEN-172.31.2.50-20260630.md`
- Certs reais (origem): `~/Downloads/ISPB-00038166-SPB-T068.cer`, `~/Downloads/ISPB-00038166-MES-T069.cer`, `~/Desktop/Certificado SPB T010.zip`
