Como obter as credenciais e gerar o certificado dinâmico?

Confira neste guia como obter as suas credenciais e gerar o certificado dinâmico

Atenção: Antes de iniciar o processo de obtenção das credenciais e geração do certificado dinâmico, é importante garantir que todos os pré-requisitos foram atendidos. Para isso, acesse Pré-requisitos para se conectar ao ambiente Itaú.

Fluxo de obtenção de credenciais e geração de certificado

Este manual tem como objetivo orientar o usuário na obtenção de credenciais e na geração do Certificado Dinâmico, ferramenta essencial para a autenticação junto à API do Itaú. Disponível em cinco linguagens de programação — Java, Python, Node.js, Go e C# e também por meio de um script de execução para Windows via Git Bash (executar-gitbash.sh), o programa oferece flexibilidade para que você escolha a tecnologia que melhor se adapta ao seu ambiente, garantindo a mesma funcionalidade completa em todas as opções. A seguir, apresentaremos os passos necessários para configurar e utilizar a ferramenta de forma eficiente e segura.

O que é isso?

Este programa ajuda você a obter o Certificado Dinâmico exigido para usar as APIs do Itaú. Ele faz todo o trabalho técnico por você, sendo necessário somente seguir as instruções na tela.

Você não precisa saber programar para usar este programa.

Resumo rápido: o que vai acontecer?

O programa vai guiar você por 4 etapas:

Etapa O que acontece O que você precisa fazer
1 O programa gera suas chaves de segurança Apenas digitar 1 e Enter
2 O programa gera seu certificado digital Colar 3 valores que você recebeu por e-mail
3 O programa testa se tudo está funcionando Apenas digitar 3 e Enter
4 Renovar o certificado (só quando necessário) Informar um token de renovação

Ao final, você terá o certificado digital e as credenciais necessárias para integrar com as APIs do Itaú.

Como executar o programa

Existem 3 formas de executar o processo. Escolha a que for mais fácil para você:

Opção 1: Script de execução Opção 2: Executar pelo terminal Opção 3: Linha de comando
Dificuldade Fácil Médio Avançado
Para quem Todos Quem já usa o terminal Quem conhece openssl e curl
Precisa de openssl? Não Não Sim
Menu interativo? Sim Sim Não
Salva progresso? Sim Sim Não

Opção 1: Script de execução (Recomendado)

Esta é a forma mais simples de executar o processo. Você executa um único arquivo e o programa faz o resto.

Vantagens:

  • Basta um duplo clique (Windows) ou um único comando (Linux/macOS)
  • Menu para escolher a linguagem, sem precisar digitar comandos
  • Verificação automática da instalação da linguagem escolhida
  • Não é necessário instalar openssl
  • Salva o progresso automaticamente
  • Tratamento de erros com mensagens claras
  • Logs de cada execução para facilitar o suporte
  • Mascaramento automático de dados sensíveis

Passo a passo

Passo 1: Baixe arquivos

Faça o download do arquivo .zip com a estrutura de pastas.

Importante:

É seguro usar este programa?

Sim. O programa:

  • Não envia suas chaves privadas para nenhum lugar;
  • Mascara informações sensíveis nos logs e na tela;
  • Não armazena o Client Secret em arquivo, ele aparece apenas uma vez na tela;
  • Salva todos os arquivos localmente no seu computador;
  • As chaves privadas (private.pem e ARQUIVO_CHAVE_PRIVADA.key) nunca devem ser compartilhadas.

Caso não possa realizar o download do .zip, disponibilizamos os códigos dos scripts na seção Scripts. Basta copiá-los e salvar em um arquivo de acordo com as extensões e estrutura de pastas a seguir.


Estrutura de pastas

Após descompactar o arquivo, a estrutura de pastas será:

certificado-dinamico/
|-- executar.bat              <- Duplo clique para executar no Windows
|-- executar-gitbash.sh       <- Executar no Windows (Git Bash)
|-- executar.sh               <- Executar no Linux
|-- executar.zsh              <- Executar no macOS
|-- java/
|   |-- CertificadoDinamico.java
|-- python/
|   |-- certificado_dinamico.py
|-- node/
|   |-- certificadoDinamico.js
|-- go/
|   |-- main.go
|-- csharp/
|   |-- CertificadoDinamico.cs
|-- output/                   <- Criada automaticamente na primeira execucao
    |-- estado_certificado.json   <- Progresso salvo (etapas 1 e 2)
    |-- etapa1/
    |   |-- private.pem           <- Sua chave privada (NAO compartilhe)
    |   |-- public.pem            <- Sua chave publica (envie ao Analista de operacoes)
    |-- etapa2/
    |   |-- client_id.txt                   <- Client ID decifrado
    |   |-- token_temporario.txt            <- Token temporario decifrado
    |   |-- ARQUIVO_REQUEST_CERTIFICADO.csr  <- Pedido de certificado
    |   |-- ARQUIVO_CHAVE_PRIVADA.key        <- Chave privada do certificado
    |   |-- certificado.crt  <- Certificado assinado
    |   |-- certificado.pfx  <- (Opcional) PFX protegido por senha
    |-- etapa4/
    |   |-- certificado.crt  <- Certificado renovado
    |   |-- certificado.pfx  <- (Opcional) PFX protegido por senha
    |-- logs/
        |-- certificado_dinamico_YYYYMMDD_HHMMSS.log  <- Log de cada execucao

Passo 2: Descompacte o arquivo ZIP

Windows:

  1. Localize o arquivo .zip na pasta de Downloads.
  2. Clique com o botão direito no arquivo.
  3. Selecione "Extrair tudo...".
  4. Escolha uma pasta de destino e clique em "Extrair".

macOS:

  1. Dê um duplo clique no arquivo .zip na pasta de Downloads.

Linux:

  1. Clique com o botão direito no arquivo e selecione "Extrair aqui", ou execute no terminal:
   unzip certificado-dinamico.zip

Passo 3: Entre na pasta do projeto

Importante: Você precisa estar dentro da pasta certificado-dinamico para executar os scripts.

Windows:

  1. Abra a pasta certificado-dinamico no Explorador de Arquivos (dê um duplo clique na pasta).

macOS / Linux:

  1. Abra o Terminal.
  2. Navegue até a pasta usando o comando cd. Por exemplo, se a pasta estiver em Downloads:
   cd ~/Downloads/certificado-dinamico

Passo 4: Execute o script correspondente ao seu sistema operacional

Sistema Como executar
Windows Dê um duplo clique no arquivo executar.bat
Windows (Git Bash) No terminal, execute: chmod +x executar-gitbash.sh && ./executar-gitbash.sh
Linux No terminal, execute: chmod +x executar.sh && ./executar.sh
macOS No terminal, execute: chmod +x executar.zsh && ./executar.zsh

Nota: o comando chmod +x só precisa ser executado uma única vez. Ele dá permissão para o script ser executado.

Passo 5: Escolha a linguagem

O programa exibirá um menu como este:

=============================================
  Certificado Dinamico Itau - Launcher
=============================================

  Escolha a linguagem para executar:

  1. Java
  2. Python
  3. Node.js
  4. Go
  5. C# (.NET)

  0. Sair

Escolha (1-5, recomendado: 1):

Digite o número da linguagem desejada e pressione Enter. O programa verificará se a linguagem está instalada antes de continuar.

Dica: Se você não sabe qual escolher, digite 1 (Java), em caso de erro, tente digitar 2 (Python).

Passo 6: Siga as etapas do menu principal

Após escolher a linguagem, o programa exibirá o menu principal com as 4 etapas. Basta digitar o número da etapa e pressionar Enter. O programa sugere automaticamente a próxima etapa a ser executada.

Confira o detalhamento de cada etapa na seção Guia completos das etapas.

Opção 2: Executar pelo Terminal

Se você já sabe abrir o terminal e executar comandos, pode rodar o programa diretamente, sem usar o script launcher. O resultado é o mesmo da Opção 1.

Importante: Entre na pasta da linguagem escolhida dentro do projeto antes de executar.

Java (Recomendado)

cd java
javac CertificadoDinamico.java
java CertificadoDinamico

Dica: O comando javac (compilação) precisa ser executado uma única vez.

Python

cd python
pip install requests
pip install cryptography
python certificado_dinamico.py

Dica: No Linux/macOS pode ser necessario usar python3 e pip3.

Node.js

cd node
node certificadoDinamico.js

Go

cd go
go run main.go

C# (.NET)

cd csharp
dotnet-script CertificadoDinamico.cs

Dica: Se o dotnet-script não estiver disponível, crie um arquivo CertificadoDinamico.csproj com o seguinte conteúdo e use dotnet run:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
</Project>
dotnet run

Após executar, o menu principal será exibido. Confira o Guia completo das etapas.

Opção 3: Linha de comando com OpenSSL e cURL

Para usuários avançados. Exige openssl e curl instalados. As etapas a seguir correspondem as do menu scripts.

Etapa 1 - Gerar chaves (openssl)

Gerar a chave privada:

openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048

Extrair a chave pública:

openssl rsa -in private.pem -pubout -out public.pem

Exibir a chave pública:

cat public.pem

Windows: Use type public.pem em vez de cat.

Copie todo o conteúdo exibido (incluindo -----BEGIN PUBLIC KEY----- e -----END PUBLIC KEY-----) e envie ao Analista de Operações Itaú.

Etapa 2 - Gerar certificado (openssl + curl)

Após receber as credenciais cifradas por e-mail:

Descriptografar a chave de sessão:

echo 'CHAVE_DE_SESSAO_CIFRADA' | base64 -d | openssl rsautl -decrypt -inkey private.pem -out session_key.bin

Descriptografar o Client ID:

echo 'CLIENT_ID_CIFRADO' | base64 -d | openssl enc -aes-256-cbc -d -K $(xxd -p session_key.bin) -iv 0 -out client_id.txt

Descriptografar o Token temporário:

echo 'TOKEN_TEMPORARIO_CIFRADO' | base64 -d | openssl enc -aes-256-cbc -d -K $(xxd -p session_key.bin) -iv 0 -out token.txt

Importante: Substitua os valores entre aspas pelos valores que você recebeu por e-mail.

Gerar CSR:

openssl req -new -newkey rsa:2048 -nodes \
  -keyout ARQUIVO_CHAVE_PRIVADA.key \
  -out ARQUIVO_REQUEST_CERTIFICADO.csr \
  -sha512 \
  -subj "/CN=SEU_CLIENT_ID/OU=SUA_ORGANIZACAO/L=SAO PAULO/ST=SP/C=BR"

Importante: Substitua SEU_CLIENT_ID pelo valor decifrado e SUA_ORGANIZACAO pelo nome da sua empresa.

Enviar CSR ao Itaú:

curl -X POST 'https://sts.itau.com.br/seguranca/v1/certificado/solicitacao' \
  -H 'Content-Type: text/plain' \
  -H "Authorization: Bearer $(cat token.txt)" \
  -d "$(cat ARQUIVO_REQUEST_CERTIFICADO.csr)"

A resposta conterá o certificado assinado e o client_secret. Salve o certificado:

echo '-----BEGIN CERTIFICATE-----
... conteudo do certificado retornado ...
-----END CERTIFICATE-----' > certificado.crt

Importante: O Client Secret e equivalente a uma Senha. Ele é exibido uma única vez. Copie e guarde em local seguro (ex.: gerenciador de senhas). Não compartilhe com ninguém.

Etapa 3 - Testar token OAuth (curl)

curl -X POST 'https://sts.itau.com.br/as/token.oauth2' \
  --cert certificado.crt \
  --key ARQUIVO_CHAVE_PRIVADA.key \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials&client_id=SEU_CLIENT_ID&client_secret=SEU_CLIENT_SECRET'

Importante: Substitua SEU_CLIENT_ID e SEU_CLIENT_SECRET pelos valores obtidos na etapa 2.

Resultado: A resposta contera o access_token. Este e o token de acesso que deve ser informado no header Authorization: Bearer <access_token> das requisições as APIs do Itaú. O token tem validade de 300 segundos (5 minutos).

Nota: Esta etapa é para testes. Na sua aplicação final, você deverá implementar esse fluxo OAuth diretamente no seu código, renovando o token a cada 5 minutos.

Etapa 4 - Renovar certificado (curl)

Renova o certificado dinâmico quando ele está próximo da expiração (menos de 30 dias).

curl -X POST 'https://sts.itau.com.br/seguranca/v1/certificado/solicitacao/renovacao' \
  --cert certificado.crt \
  --key ARQUIVO_CHAVE_PRIVADA.key \
  -H 'Content-Type: text/plain' \
  -H "Authorization: Bearer SEU_TOKEN_STS" \
  -d "$(cat ARQUIVO_REQUEST_CERTIFICADO.csr)"

Importante: Substitua SEU_TOKEN_STS por um token válido para autenticação no STS Itaú.

Guia completo das etapas

Esta seção detalha exatamente o que acontece e o que você precisa fazer em cada etapa do menu interativo (Opções 1 e 2).

Como funciona o menu

Ao abrir o programa, você pode conferir o menu principal:

=============================================
  Certificado Dinamico Itau - Menu Principal
=============================================

  Progresso atual: Etapa 0 de 3 concluida(s)

 1. Gerar par de chaves e exibir chave publica  [PROXIMA >>] <------ voce esta aqui
  2. Descriptografar credenciais, gerar e enviar certificado  [PENDENTE]
  3. Testar geracao de token                      [PENDENTE]
  4. Renovar certificado

  0. Sair
  9. Recomecar do zero (limpa progresso)

Como ler o menu:

  • [CONCLUIDA] = Você já fez esta etapa
  • [PROXIMA >>] < voce esta aqui = Esta é a próxima etapa que você deve fazer
  • [PENDENTE] = Você ainda não pode fazer esta etapa (precisa terminar as anteriores primeiro)

Dica: O programa sugere automaticamente a próxima etapa. Se você pressionar Enter (sem digitar nada), ele executará a etapa sugerida.

Etapa 1 - Gerar par de chaves de segurança

Objetivo: Criar um par de chaves criptográficas (uma "chave privada" e uma "chave pública") e mostrar a chave pública para você enviar ao Analista de operações Itaú.

O que você precisa fazer:

  1. Digite 1 e pressione Enter
  2. Pronto! O programa gera as chaves e mostra a chave pública na tela

O que você vai ver na tela:

[PASSO 1.1] Gerando par de chaves RSA 2048...
Chave privada salva em /caminho/completo/output/etapa1/private.pem
Chave publica salva em /caminho/completo/output/etapa1/public.pem

[PASSO 1.2] Exibindo chave publica...

=============================================
  Chave Publica Gerada
=============================================
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...
(varias linhas de texto)
...
-----END PUBLIC KEY-----

>> Etapa 1 concluida. Progresso salvo.

  O que fazer agora:
  1. Copie o conteudo da chave publica exibido acima
  2. Envie a chave publica para o Analista de operacoes Itau
  3. Aguarde o e-mail do Itau com as credenciais cifradas
  4. Quando receber o e-mail, execute a etapa 2

Próximo passo (Fora do programa):

Copie todo o texto da chave pública (incluindo as linhas -----BEGIN PUBLIC KEY----- e -----END PUBLIC KEY-----) e envie para o Analista de operações Itaú. Ele usará essa chave para gerar as credenciais cifradas que você usará na etapa 2.

Atenção: Aguarde, o analista de operações Itaú precisará de algum tempo para processar. Você receberá um e-mail com 3 valores cifrados. Só então poderá continuar para a etapa 2.

Arquivos gerados:

Arquivo Onde fica Para que serve
private.pem output/etapa1/ Chave privada. NÃO compartilhe este arquivo com ninguém.
public.pem output/etapa1/ Chave pública. Envie ao Analista de operações Itaú.

Etapa 2 - Gerar o certificado digital

Objetivo: Utilizar as credenciais recebidas por e-mail para gerar o certificado digital.

Antes de começar, certifique-se de ter

  • O e-mail do Itaú contendo os três valores cifrados:
    • Client ID cifrado
    • Token temporário cifrado
    • Chave de sessão cifrada

O que você precisa fazer:

  1. Digite 2 e pressione Enter.
  2. O programa solicitará três valores. Para cada um, copie do e-mail e cole no terminal:
Valor solicitado Origem para copiar
Client ID cifrado: Copie o valor "Client ID cifrado" do e-mail
Token temporário cifrado: Copie o valor "Token temporário cifrado" do e-mail
Chave de sessão cifrada: Copie o valor "Chave de sessão cifrada" do e-mail

Dica

Como colar no terminal:

  • Windows (Prompt de Comando): Clique com o botão direito do mouse.
  • Windows (PowerShell/Terminal): Ctrl + V.
  • macOS: Cmd + V.
  • Linux: Ctrl + Shift + V.
  1. O programa solicitará os dados da sua empresa. Todos os campos são obrigatórios. Caso você pressione Enter sem digitar nada, o programa solicitará o preenchimento novamente.
Campo Exemplo
Nome da Empresa: Minha Empresa LTDA
Cidade: SAO PAULO
Estado - UF: SP
País: BR
  1. O programa realizará todo o processo automaticamente (descriptografar, gerar CSR, enviar ao Itaú).

O que acontece ao final:

O programa exibirá uma tela com os arquivos gerados e, muito importante, o Client Secret:

==============================================
  ATENCAO - CLIENT SECRET
==============================================

O Client Secret e equivalente a uma SENHA. Trate-o com o mesmo cuidado.
O valor do Client Secret sera exibido APENAS NESTE MOMENTO.
Copie e salve em um local seguro. Este valor NAO sera armazenado pelo programa.

  Client Secret: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

==============================================
Guarde essas informacoes em local seguro. O Client ID e o Client Secret sao equivalentes a senhas
e serao necessarios para gerar tokens OAuth e consumir as APIs do Itau.
NAO compartilhe essas credenciais. Quem tiver acesso a elas podera acessar as APIs em seu nome.

O Itau nao se responsabiliza pelo armazenamento local das credenciais.
A guarda correta e de responsabilidade exclusiva do cliente.

Atenção: O Client Secret é equivalente a uma senha e aparece apenas uma única vez neste momento. Se você não copiar neste momento, precisará refazer todo o processo. Copie e guarde em local seguro imediatamente (ex.: gerenciador de senhas). Não compartilhe, Não envie por e-mail e Não deixe exposto em código-fonte. Quem tiver acesso ao Client ID e Client Secret poderá acessar as APIs em seu nome.

Arquivos gerados:

Arquivo Onde fica Para que serve
client_id.txt output/etapa2/ Client ID decifrado
token_temporario.txt output/etapa2/ Token temporário decifrado
ARQUIVO_REQUEST_CERTIFICADO.csr output/etapa2/ Pedido de certificado (CSR)
ARQUIVO_CHAVE_PRIVADA.key output/etapa2/ Chave privada do certificado. Não compartilhe.
certificado.crt output/etapa2/ Certificado digital assinado pelo Itaú
certificado.pfx output/etapa2/ (Opcional) Certificado + chave privada em formato PKCS#12 protegido por senha

PFX (opcional): Após a etapa 2, o programa perguntará se você deseja gerar um arquivo .pfx. O PFX combina o certificado e a chave privada em um único arquivo protegido por senha. É útil para plataformas que trabalham com esse formato (ex: .NET, Java KeyStore, IIS, Azure). Se não precisar, basta responder N.

Validade do certificado:

O programa exibirá na tela as informações de validade:

===============================================
  VALIDADE DO CERTIFICADO
===============================================
  Validade: 365 dias
  Data de expiracao: DD/MM/AAAA
  Periodo de renovacao: de DD/MM/AAAA ate DD/MM/AAAA

  O processo de renovacao (etapa 4) pode ser realizado a partir de
  30 dias antes da expiracao ate um dia antes da data de expiracao do certificado.

Importante: O certificado é válido por 365 dias a partir da data de geração. A renovação pode ser feita a partir de 30 dias antes da expiração até um dia antes da data de expiração. Anote a data de expiração para não perder o prazo!

A responsabilidade de renovar o certificado dentro do prazo é exclusivamente sua. Certificados expirados impossibilitam o acesso às APIs do Itaú e exigem a geração de um novo certificado do zero.

Etapa 3 - Testar o certificado

Objetivo: Verificar se o certificado e as credenciais estão funcionando corretamente, gerando um token de acesso de teste.

O que você precisa fazer:

  1. Digite 3 e pressione Enter.
  2. Se você acabou de executar a etapa 2 (sem fechar o programa), o Client Secret será usado automaticamente.
  3. Se você reiniciou o programa, ele pedirá: Informe o Client Secret: - cole o valor que você guardou na etapa 2.

O que acontece:

O programa usa o certificado e as credenciais para gerar um token de acesso OAuth. Se tudo estiver correto, o programa informará:

==============================================
  Credenciais e certificado VALIDADOS!
==============================================

Access Token:
eyJhbGciOiJSUzI1NiIs... (token longo)

  Validade: 300 segundos (5 minutos)
  Uso: Informe este token no header 'Authorization: Bearer <access_token>'
       das requisicoes as APIs do Itau.

As credenciais (client_id e client_secret) e o certificado digital estao
validados e funcionais. Voce ja pode utiliza-los em suas integracoes.

Nota: Esta etapa é apenas para teste. Na sua aplicação final, você deverá implementar esse fluxo OAuth no seu código, renovando o token a cada 5 minutos.

Etapa 4 - Renovar o certificado

Objetivo: Renovar o certificado digital quando ele estiver próximo de expirar.

Quando usar: Somente quando o certificado estiver perto da data de vencimento (menos de 30 dias). Esta opção está sempre disponível no menu, independente do progresso das outras etapas.

O que você precisa fazer:

  1. Digite 4 e pressione Enter.
  2. Informe o Token STS para renovação quando solicitado.
  3. Se o arquivo CSR da etapa 2 for encontrado automaticamente, o programa o utiliza. Caso contrário, pedirá o caminho do arquivo.

Arquivo gerado:

Arquivo Onde fica Para que serve
certificado.crt output/etapa4/ Novo certificado digital renovado
certificado.pfx output/etapa4/ (Opcional) Certificado renovado + chave privada em formato PKCS#12 protegido por senha

PFX (opcional): Assim como na etapa 2, o programa perguntara se você deseja gerar o arquivo .pfx para o certificado renovado.

Validade do certificado renovado:

Após a renovação, o programa exibirá as novas datas de validade:

===============================================
  VALIDADE DO CERTIFICADO RENOVADO
===============================================
  Validade: 365 dias
  Data de expiracao: DD/MM/AAAA
  Periodo de renovacao: de DD/MM/AAAA ate DD/MM/AAAA

  O processo de renovacao (etapa 4) pode ser realizado a partir de
  30 dias antes da expiracao ate um dia antes da data de expiracao do certificado.

Importante: O certificado renovado também é válido por 365 dias. Anote a nova data de expiração.

A responsabilidade pela renovação do certificado dentro do prazo é exclusivamente sua. Certificados expirados impedem o acesso às APIs do Itaú e exigem a geração de um novo certificado do zero.

Opção 9 - Recomeçar do zero

Se você precisar refazer todo o processo desde o início, digite 9 no menu. Isso apaga todos os arquivos gerados e o progresso salvo.

Cuidado: Esta ação é irreversível. Todos os arquivos (chaves, certificados, estado) serão excluídos permanentemente.

Opção 0 - Sair

Encerra o programa. O progresso das etapas 1 e 2 é salvo automaticamente, na próxima vez que abrir o programa, ele continuará de onde parou.

Nota: A etapa 3 (teste de token) não fica salva. Se você fechar e reabrir o programa, precisará reexecutar a etapa 3. Isso é normal, ela é apenas um teste.

Perguntas frequentes (FAQ)

Preciso saber programar?

Não. O programa é interativo e guia você por cada etapa. Você só precisa digitar números e pressionar Enter. A linguagem (Java, Python, etc.) é usada internamente — você não precisa entender nada sobre ela.

Preciso descompactar o arquivo ZIP?

Sim. O programa não funciona de dentro do arquivo .zip. Descompacte primeiro. Confira as Descompacte o arquivo zip.

Preciso estar dentro da pasta para executar?

Sim. O programa precisa encontrar os arquivos internos. No Windows, abra a pasta no Explorador e dê duplo clique no executar.bat. No Linux/macOS, use cd para navegar até a pasta.

Qual a diferença entre as 3 opções de execução?

  • Opção 1 (Script de execução): Mais fácil. Duplo clique e siga o menu. Para a maioria dos usuários.
  • Opção 2 (Terminal): Você executa direto pela linguagem. Para quem já usa terminal.
  • Opção 3 (openssl/curl): Você executa cada comando manualmente. Para usuários avançados ou auditoria.

Preciso copiar e colar código?

Não. Você não precisa copiar nenhum código de programação. O único momento em que precisará colar algo é na etapa 2, quando o programa pede as credenciais cifradas que você recebeu por e-mail.

O que fazer se o programa fechar sem querer?

Não se preocupe. O progresso das etapas 1 e 2 é salvo automaticamente. Basta abrir o programa novamente e ele continuará de onde parou. Apenas a etapa 3 (teste de token) precisará ser reexecutada, pois não fica salva.

Posso usar qualquer uma das 5 linguagens?

Sim. Todas fazem exatamente a mesma coisa. Escolha a que já estiver instalada. Se não souber, tente Java (opção 1). Se der erro, tente Python (opção 2).

O que fazer se aparecer "comando não encontrado"?

A linguagem não está instalada ou não está no Path. Confira a seção Pré-requisitos e instale. Ou simplesmente tente outra linguagem no menu.

O que é o Client Secret e por que ele só aparece uma vez?

O Client Secret é equivalente a uma Senha da sua aplicação. Assim como uma senha de banco, quem tiver acesso a ele poderá acessar as APIs em seu nome. Por segurança, o programa exibe esse valor apenas uma vez na etapa 2 e não o salva em nenhum arquivo. Se você perder esse valor, precisará refazer o processo (opção 9 para recomeçar do zero). O mesmo vale para o Client ID, o certificado e a chave privada, todos são equivalentes a senhas e Não devem ser compartilhados.

O que fazer se a etapa 2 retornar erro 403?

O erro 403 significa "acesso negado". Geralmente indica que o token expirou. Solicite novas credenciais ao Analista de Operações Itaú e execute novamente a etapa 2.

O que fazer se aparecer "NullPointerException" na resposta?

Isso pode indicar que os dados enviados estavam incompletos. Use a opção 9 para recomeçar do zero e refaça todas as etapas.

Onde ficam os logs?

Na pasta output/logs/. O arquivo de log é criado automaticamente a cada execução. Informações sensíveis (tokens, senhas) são mascaradas nos logs, então é seguro compartilhá-los com o suporte.

Como renovar o certificado?

Use a opção 4 no menu. Ela está sempre disponível. Utilize quando o certificado estiver próximo da expiração (menos de 30 dias).

Posso executar em outro computador?

Sim, desde que a linguagem esteja instalada. Porém, os arquivos gerados ficam na pasta output/ do computador original. Se precisar transferir, copie toda a pasta certificado-dinamico/ (incluindo output/).

É seguro usar este programa?

Sim. O programa:

  • Não envia suas chaves privadas para nenhum lugar;
  • Mascara informações sensíveis nos logs e na tela;
  • Não armazena o Client Secret em arquivo, ele aparece apenas uma vez na tela;
  • Salva todos os arquivos localmente no seu computador;
  • As chaves privadas (private.pem e ARQUIVO_CHAVE_PRIVADA.key) nunca devem ser compartilhadas.

Lembre-se: Client ID, Client Secret, certificado e chave privada são equivalentes a senhas. Não compartilhe, não envie por e-mail e não deixe exposto em código-fonte. Trate essas credenciais com o mesmo cuidado que você trata suas senhas bancárias.

O Itaú não se responsabiliza pelo armazenamento local das credenciais. A guarda correta é de responsabilidade exclusiva do cliente.

Resolução de problemas

"javac não é reconhecido como comando interno ou externo"

O Java não está instalado ou não está no PATH. Instale em https://adoptium.net/ e reinicie o terminal.

"python não é reconhecido como comando interno ou externo"

O Python não está instalado ou não está no PATH. Instale em https://www.python.org/downloads/ e marque "Add Python to PATH" durante a instalação.

"Erro ao descriptografar as credenciais"

Os valores cifrados copiados do e-mail podem estar incorretos ou incompletos. Certifique-se de copiar o texto completo de cada campo, sem espaços extras no início ou final.

"Status 403 - Acesso negado"

O token provavelmente expirou. Solicite novas credenciais ao Analista de Operações Itaú e refaça a etapa 2.

"Status 500 - Erro interno do servidor"

Erro no lado do Itaú. Tente novamente em alguns minutos.

O programa não encontra os arquivos

Verifique se você está executando o programa de dentro da pasta certificado-dinamico. Os caminhos dos arquivos são relativos a essa pasta.

O script .bat não executa ao dar duplo clique

Tente: clique com o botão direito > "Executar como administrador". Ou abra o Prompt de Comando, navegue até a pasta e execute executar.bat.

Suporte

Em caso de dúvidas ou problemas:

  • Verifique os logs em output/logs/ para detalhes do erro.

Para dúvidas que não forem esclarecidas por essa documentação, entre em contato conosco diretamente pelos canais de suporte combinados com o seu representante comercial Itaú.

Scripts

Importante: Os códigos e comandos a seguir são os mesmos que você encontrará no arquivo .zip para download. Para utilizá-los é necessário copiar cada arquivo e salvá-lo com a extensão correspondente, além de criar a estrutura de pastas conforme disponibilizado em Estrutura de pastas.

@echo off
chcp 65001 >nul 2>&1
setlocal enabledelayedexpansion
title Certificado Dinamico Itau

set "SCRIPT_DIR=%~dp0"

echo.
echo =============================================
echo   Certificado Dinamico Itau
echo =============================================
echo.
echo   Escolha a linguagem para executar:
echo.
echo   1. Java (recomendado)
echo   2. Python
echo   3. Node.js
echo   4. Go
echo   5. C# (.NET)
echo.
echo   0. Sair
echo.

set "escolha=1"
set /p escolha="Escolha (1-5, padrao: 1 - Java): "

if "%escolha%"=="0" goto opt_sair
if "%escolha%"=="1" goto opt_java
if "%escolha%"=="2" goto opt_python
if "%escolha%"=="3" goto opt_node
if "%escolha%"=="4" goto opt_go
if "%escolha%"=="5" goto opt_csharp
goto opt_invalida

:opt_sair
echo Saindo...
exit /b 0

:opt_java
echo.
echo Verificando pre-requisitos para Java...
echo.
set "PREREQ_OK=1"
where javac >nul 2>&1
if errorlevel 1 (
    echo   [ERRO] javac nao encontrado. Instale o JDK.
    set "PREREQ_OK=0"
) else (
    for /f "tokens=*" %%v in ('javac -version 2^>^&1') do echo   [OK] %%v
)
where java >nul 2>&1
if errorlevel 1 (
    echo   [ERRO] java nao encontrado. Instale o JDK.
    set "PREREQ_OK=0"
) else (
    for /f "tokens=*" %%v in ('java -version 2^>^&1') do echo   [OK] %%v
)
if exist "%SCRIPT_DIR%java\CertificadoDinamico.java" (
    echo   [OK] Arquivo CertificadoDinamico.java encontrado
) else (
    echo   [ERRO] Arquivo CertificadoDinamico.java nao encontrado
    set "PREREQ_OK=0"
)
echo.
if "%PREREQ_OK%"=="0" (
    echo Pre-requisitos nao atendidos. Instale o JDK e adicione ao PATH.
    echo Download: https://adoptium.net/
    pause
    exit /b 1
)
echo Pre-requisitos atendidos. Iniciando programa...
echo.
pushd "%SCRIPT_DIR%java"
if not exist "CertificadoDinamico.class" (
    echo Compilando CertificadoDinamico.java...
    javac CertificadoDinamico.java
    if errorlevel 1 (
        echo [ERRO] Falha na compilacao.
        popd
        pause
        exit /b 1
    )
)
java CertificadoDinamico
popd
goto fim

:opt_python
echo.
echo Verificando pre-requisitos para Python...
echo.
set "PREREQ_OK=1"
set "PYTHON_CMD="

REM 1) Tentar primeiro o Python Launcher (py), que vem com a instalacao oficial
where py >nul 2>&1
if not errorlevel 1 (
    py -3 --version >nul 2>&1
    if not errorlevel 1 set "PYTHON_CMD=py -3"
)

REM 2) Fallback: python3 no PATH, ignorando o stub do Microsoft Store (WindowsApps)
if "!PYTHON_CMD!"=="" (
    for /f "delims=" %%P in ('where python3 2^>nul') do (
        if "!PYTHON_CMD!"=="" (
            echo %%P| findstr /i "WindowsApps" >nul
            if errorlevel 1 set "PYTHON_CMD="%%P""
        )
    )
)

REM 3) Fallback: python no PATH, ignorando o stub do Microsoft Store (WindowsApps)
if "!PYTHON_CMD!"=="" (
    for /f "delims=" %%P in ('where python 2^>nul') do (
        if "!PYTHON_CMD!"=="" (
            echo %%P| findstr /i "WindowsApps" >nul
            if errorlevel 1 set "PYTHON_CMD="%%P""
        )
    )
)

if "!PYTHON_CMD!"=="" (
    echo   [ERRO] python nao encontrado ^(ou apenas o stub da Microsoft Store esta no PATH^)
    echo          Desabilite os aliases em: Configuracoes ^> Aplicativos ^> Aliases de execucao do aplicativo
    echo          ou instale o Python em https://www.python.org/downloads/
    set "PREREQ_OK=0"
) else (
    !PYTHON_CMD! --version > "%TEMP%\pyver.txt" 2>&1
    set /p PYVER=<"%TEMP%\pyver.txt"
    del "%TEMP%\pyver.txt" >nul 2>&1
    echo   [OK] !PYVER!
)
if not "!PYTHON_CMD!"=="" (
    !PYTHON_CMD! -c "import requests" >nul 2>&1
    if errorlevel 1 (
        echo   [AVISO] Biblioteca 'requests' nao encontrada - sera instalada automaticamente
    ) else (
        echo   [OK] Biblioteca 'requests' disponivel
    )
    !PYTHON_CMD! -c "import cryptography" >nul 2>&1
    if errorlevel 1 (
        echo   [AVISO] Biblioteca 'cryptography' nao encontrada - sera instalada automaticamente
    ) else (
        echo   [OK] Biblioteca 'cryptography' disponivel
    )
)
if exist "%SCRIPT_DIR%python\certificado_dinamico.py" (
    echo   [OK] Arquivo certificado_dinamico.py encontrado
) else (
    echo   [ERRO] Arquivo certificado_dinamico.py nao encontrado
    set "PREREQ_OK=0"
)
echo.
if "!PREREQ_OK!"=="0" (
    echo Pre-requisitos nao atendidos. Instale o Python e adicione ao PATH.
    echo Download: https://www.python.org/downloads/
    pause
    exit /b 1
)
echo Pre-requisitos atendidos. Iniciando programa...
echo.
pushd "%SCRIPT_DIR%python"
echo Instalando dependencias...

REM Em ambientes corporativos com SSL inspection (ex: proxy Itau), o pip precisa de:
REM  - usar o proxy correto (a variavel HTTPS_PROXY pode apontar para porta inativa)
REM  - confiar nos hosts do PyPI para nao falhar na verificacao do certificado MITM
set "PIP_PROXY_ARG="
if defined HTTP_PROXY set "PIP_PROXY_ARG=--proxy %HTTP_PROXY%"
set "PIP_TRUSTED=--trusted-host pypi.org --trusted-host files.pythonhosted.org --trusted-host pypi.python.org"

!PYTHON_CMD! -m pip install %PIP_PROXY_ARG% %PIP_TRUSTED% requests --quiet
!PYTHON_CMD! -m pip install %PIP_PROXY_ARG% %PIP_TRUSTED% cryptography --quiet
!PYTHON_CMD! certificado_dinamico.py
popd
goto fim

:opt_node
echo.
echo Verificando pre-requisitos para Node.js...
echo.
set "PREREQ_OK=1"
where node >nul 2>&1
if errorlevel 1 (
    echo   [ERRO] node nao encontrado
    set "PREREQ_OK=0"
) else (
    for /f "tokens=*" %%v in ('node --version 2^>^&1') do echo   [OK] Node.js %%v
)
if exist "%SCRIPT_DIR%node\certificadoDinamico.js" (
    echo   [OK] Arquivo certificadoDinamico.js encontrado
) else (
    echo   [ERRO] Arquivo certificadoDinamico.js nao encontrado
    set "PREREQ_OK=0"
)
echo.
if "%PREREQ_OK%"=="0" (
    echo Pre-requisitos nao atendidos. Instale o Node.js e adicione ao PATH.
    echo Download: https://nodejs.org/
    pause
    exit /b 1
)
echo Pre-requisitos atendidos. Iniciando programa...
echo.
pushd "%SCRIPT_DIR%node"
node certificadoDinamico.js
popd
goto fim

:opt_go
echo.
echo Verificando pre-requisitos para Go...
echo.
set "PREREQ_OK=1"
where go >nul 2>&1
if errorlevel 1 (
    echo   [ERRO] go nao encontrado
    set "PREREQ_OK=0"
) else (
    for /f "tokens=3" %%v in ('go version 2^>^&1') do echo   [OK] Go %%v
)
if exist "%SCRIPT_DIR%go\main.go" (
    echo   [OK] Arquivo main.go encontrado
) else (
    echo   [ERRO] Arquivo main.go nao encontrado
    set "PREREQ_OK=0"
)
echo.
if "%PREREQ_OK%"=="0" (
    echo Pre-requisitos nao atendidos. Instale o Go e adicione ao PATH.
    echo Download: https://go.dev/dl/
    pause
    exit /b 1
)
echo Pre-requisitos atendidos. Iniciando programa...
echo.
pushd "%SCRIPT_DIR%go"
go run main.go
popd
goto fim

:opt_csharp
echo.
echo Verificando pre-requisitos para C# .NET...
echo.
set "PREREQ_OK=1"
where dotnet >nul 2>&1
if errorlevel 1 (
    echo   [ERRO] dotnet nao encontrado
    set "PREREQ_OK=0"
) else (
    for /f "tokens=*" %%v in ('dotnet --version 2^>^&1') do echo   [OK] .NET SDK %%v
)
if exist "%SCRIPT_DIR%csharp\CertificadoDinamico.cs" (
    echo   [OK] Arquivo CertificadoDinamico.cs encontrado
) else (
    echo   [ERRO] Arquivo CertificadoDinamico.cs nao encontrado
    set "PREREQ_OK=0"
)
echo.
if "%PREREQ_OK%"=="0" (
    echo Pre-requisitos nao atendidos. Instale o .NET SDK e adicione ao PATH.
    echo Download: https://dotnet.microsoft.com/download
    pause
    exit /b 1
)
echo Pre-requisitos atendidos. Iniciando programa...
echo.
pushd "%SCRIPT_DIR%csharp"
if not exist "CertificadoDinamico.csproj" (
    echo ^<Project Sdk="Microsoft.NET.Sdk"^> > CertificadoDinamico.csproj
    echo   ^<PropertyGroup^> >> CertificadoDinamico.csproj
    echo     ^<OutputType^>Exe^</OutputType^> >> CertificadoDinamico.csproj
    echo     ^<TargetFramework^>net8.0^</TargetFramework^> >> CertificadoDinamico.csproj
    echo   ^</PropertyGroup^> >> CertificadoDinamico.csproj
    echo ^</Project^> >> CertificadoDinamico.csproj
)
dotnet run
popd
goto fim

:opt_invalida
echo Opcao invalida.
pause
exit /b 1

:fim
pause
exit /b 0
#!/bin/zsh
# Certificado Dinamico Itau - Script de execucao para macOS

SCRIPT_DIR="${0:A:h}"

echo ""
echo "============================================="
echo "  Certificado Dinamico Itau"
echo "============================================="
echo ""
echo "  Escolha a linguagem para executar:"
echo ""
echo "  1. Java (recomendado)"
echo "  2. Python"
echo "  3. Node.js"
echo "  4. Go"
echo "  5. C# (.NET)"
echo ""
echo "  0. Sair"
echo ""

read "escolha?Escolha (1-5, padrao: 1 - Java): "
escolha=${escolha:-1}

verificar_arquivo() {
    if [ -f "$1" ]; then
        echo "  [OK] Arquivo $(basename "$1") encontrado"
        return 0
    else
        echo "  [ERRO] Arquivo $(basename "$1") nao encontrado em $(dirname "$1")/"
        return 1
    fi
}

case "$escolha" in
    0)
        echo "Saindo..."
        exit 0
        ;;
    1)
        echo ""
        echo "Verificando pre-requisitos para Java..."
        echo ""
        PREREQ_OK=1
        if command -v javac &> /dev/null; then
            echo "  [OK] $(javac -version 2>&1)"
        else
            echo "  [ERRO] javac (compilador Java) nao encontrado"
            PREREQ_OK=0
        fi
        if command -v java &> /dev/null; then
            echo "  [OK] $(java -version 2>&1 | head -1)"
        else
            echo "  [ERRO] java (runtime) nao encontrado"
            PREREQ_OK=0
        fi
        verificar_arquivo "$SCRIPT_DIR/java/CertificadoDinamico.java" || PREREQ_OK=0
        echo ""
        if [ "$PREREQ_OK" -eq 0 ]; then
            echo "Pre-requisitos nao atendidos. Instale o JDK e adicione ao PATH."
            echo "  Homebrew: brew install openjdk"
            echo "  Download: https://adoptium.net/"
            exit 1
        fi
        echo "Pre-requisitos atendidos. Iniciando programa..."
        echo ""
        cd "$SCRIPT_DIR/java"
        if [ ! -f "CertificadoDinamico.class" ]; then
            echo "Compilando CertificadoDinamico.java..."
            javac CertificadoDinamico.java
            if [ $? -ne 0 ]; then
                echo "[ERRO] Falha na compilacao."
                exit 1
            fi
        fi
        java CertificadoDinamico
        ;;
    2)
        echo ""
        echo "Verificando pre-requisitos para Python..."
        echo ""
        PREREQ_OK=1
        # Usamos um array para permitir comandos com argumentos (ex.: "py -3") e
        # tambem caminhos com espacos (ex.: "/Library/Frameworks/Python.framework/.../python3").
        PYTHON_CMD=()

        # Mantemos a mesma logica do executar.sh para que o comportamento seja
        # consistente entre Linux/Git Bash e macOS: tentamos o Python Launcher
        # primeiro, depois python3/python ignorando ocorrencias em WindowsApps
        # (irrelevante em macOS, mas defensivo) e validando com --version.

        # 1. Python Launcher (py -3), padrao em instalacoes oficiais
        if command -v py >/dev/null 2>&1 && py -3 --version >/dev/null 2>&1; then
            PYTHON_CMD=(py -3)
        fi

        # 2. python3 ou python no PATH, descartando o stub do Microsoft Store
        if [ ${#PYTHON_CMD[@]} -eq 0 ]; then
            for cmd in python3 python; do
                while IFS= read -r cmd_path; do
                    [ -z "$cmd_path" ] && continue
                    case "$cmd_path" in
                        *WindowsApps*) continue ;;
                    esac
                    if "$cmd_path" --version >/dev/null 2>&1; then
                        PYTHON_CMD=("$cmd_path")
                        break 2
                    fi
                done < <(whence -ap "$cmd" 2>/dev/null)
            done
        fi

        if [ ${#PYTHON_CMD[@]} -eq 0 ]; then
            echo "  [ERRO] python nao encontrado"
            PREREQ_OK=0
        else
            echo "  [OK] $("${PYTHON_CMD[@]}" --version 2>&1)"
        fi
        if [ ${#PYTHON_CMD[@]} -gt 0 ]; then
            if "${PYTHON_CMD[@]}" -c "import requests" &> /dev/null; then
                echo "  [OK] Biblioteca 'requests' disponivel"
            else
                echo "  [AVISO] Biblioteca 'requests' nao encontrada (sera instalada automaticamente)"
            fi
            if "${PYTHON_CMD[@]}" -c "import cryptography" &> /dev/null; then
                echo "  [OK] Biblioteca 'cryptography' disponivel"
            else
                echo "  [AVISO] Biblioteca 'cryptography' nao encontrada (sera instalada automaticamente)"
            fi
        fi
        verificar_arquivo "$SCRIPT_DIR/python/certificado_dinamico.py" || PREREQ_OK=0
        echo ""
        if [ "$PREREQ_OK" -eq 0 ]; then
            echo "Pre-requisitos nao atendidos. Instale o Python e adicione ao PATH."
            echo "  Homebrew: brew install python"
            echo "  Download: https://www.python.org/downloads/"
            exit 1
        fi
        echo "Pre-requisitos atendidos. Iniciando programa..."
        echo ""
        cd "$SCRIPT_DIR/python"
        echo "Instalando dependencias..."

        # Em ambientes corporativos com SSL inspection (ex: proxy Itau), o pip precisa de:
        #  - usar o proxy correto (a variavel HTTPS_PROXY pode apontar para porta inativa)
        #  - confiar nos hosts do PyPI para nao falhar na verificacao do certificado MITM
        PIP_PROXY_ARGS=()
        if [ -n "$HTTP_PROXY" ]; then
            PIP_PROXY_ARGS=(--proxy "$HTTP_PROXY")
        elif [ -n "$http_proxy" ]; then
            PIP_PROXY_ARGS=(--proxy "$http_proxy")
        fi
        PIP_TRUSTED=(--trusted-host pypi.org --trusted-host files.pythonhosted.org --trusted-host pypi.python.org)

        "${PYTHON_CMD[@]}" -m pip install "${PIP_PROXY_ARGS[@]}" "${PIP_TRUSTED[@]}" requests --quiet
        "${PYTHON_CMD[@]}" -m pip install "${PIP_PROXY_ARGS[@]}" "${PIP_TRUSTED[@]}" cryptography --quiet
        "${PYTHON_CMD[@]}" certificado_dinamico.py
        ;;
    3)
        echo ""
        echo "Verificando pre-requisitos para Node.js..."
        echo ""
        PREREQ_OK=1
        if command -v node &> /dev/null; then
            echo "  [OK] Node.js $(node --version 2>&1)"
        else
            echo "  [ERRO] node nao encontrado"
            PREREQ_OK=0
        fi
        verificar_arquivo "$SCRIPT_DIR/node/certificadoDinamico.js" || PREREQ_OK=0
        echo ""
        if [ "$PREREQ_OK" -eq 0 ]; then
            echo "Pre-requisitos nao atendidos. Instale o Node.js e adicione ao PATH."
            echo "  Homebrew: brew install node"
            echo "  Download: https://nodejs.org/"
            exit 1
        fi
        echo "Pre-requisitos atendidos. Iniciando programa..."
        echo ""
        cd "$SCRIPT_DIR/node"
        node certificadoDinamico.js
        ;;
    4)
        echo ""
        echo "Verificando pre-requisitos para Go..."
        echo ""
        PREREQ_OK=1
        if command -v go &> /dev/null; then
            echo "  [OK] $(go version 2>&1)"
        else
            echo "  [ERRO] go nao encontrado"
            PREREQ_OK=0
        fi
        verificar_arquivo "$SCRIPT_DIR/go/main.go" || PREREQ_OK=0
        echo ""
        if [ "$PREREQ_OK" -eq 0 ]; then
            echo "Pre-requisitos nao atendidos. Instale o Go e adicione ao PATH."
            echo "  Homebrew: brew install go"
            echo "  Download: https://go.dev/dl/"
            exit 1
        fi
        echo "Pre-requisitos atendidos. Iniciando programa..."
        echo ""
        cd "$SCRIPT_DIR/go"
        go run main.go
        ;;
    5)
        echo ""
        echo "Verificando pre-requisitos para C# (.NET)..."
        echo ""
        PREREQ_OK=1
        if command -v dotnet &> /dev/null; then
            echo "  [OK] .NET SDK $(dotnet --version 2>&1)"
        else
            echo "  [ERRO] dotnet nao encontrado"
            PREREQ_OK=0
        fi
        verificar_arquivo "$SCRIPT_DIR/csharp/CertificadoDinamico.cs" || PREREQ_OK=0
        echo ""
        if [ "$PREREQ_OK" -eq 0 ]; then
            echo "Pre-requisitos nao atendidos. Instale o .NET SDK e adicione ao PATH."
            echo "  Homebrew: brew install dotnet-sdk"
            echo "  Download: https://dotnet.microsoft.com/download"
            exit 1
        fi
        echo "Pre-requisitos atendidos. Iniciando programa..."
        echo ""
        cd "$SCRIPT_DIR/csharp"
        if [ ! -f "CertificadoDinamico.csproj" ]; then
            cat > CertificadoDinamico.csproj <<EOF
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
</Project>
EOF
        fi
        dotnet run
        ;;
    *)
        echo "Opcao invalida."
        exit 1
        ;;
esac
#!/bin/bash
# Certificado Dinamico Itau - Script de execucao para Linux

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

echo ""
echo "============================================="
echo "  Certificado Dinamico Itau"
echo "============================================="
echo ""
echo "  Escolha a linguagem para executar:"
echo ""
echo "  1. Java (recomendado)"
echo "  2. Python"
echo "  3. Node.js"
echo "  4. Go"
echo "  5. C# (.NET)"
echo ""
echo "  0. Sair"
echo ""

read -p "Escolha (1-5, padrao: 1 - Java): " escolha
escolha=${escolha:-1}

verificar_arquivo() {
    if [ -f "$1" ]; then
        echo "  [OK] Arquivo $(basename "$1") encontrado"
        return 0
    else
        echo "  [ERRO] Arquivo $(basename "$1") nao encontrado em $(dirname "$1")/"
        return 1
    fi
}

case "$escolha" in
    0)
        echo "Saindo..."
        exit 0
        ;;
    1)
        echo ""
        echo "Verificando pre-requisitos para Java..."
        echo ""
        PREREQ_OK=1
        if command -v javac &> /dev/null; then
            echo "  [OK] $(javac -version 2>&1)"
        else
            echo "  [ERRO] javac (compilador Java) nao encontrado"
            PREREQ_OK=0
        fi
        if command -v java &> /dev/null; then
            echo "  [OK] $(java -version 2>&1 | head -1)"
        else
            echo "  [ERRO] java (runtime) nao encontrado"
            PREREQ_OK=0
        fi
        verificar_arquivo "$SCRIPT_DIR/java/CertificadoDinamico.java" || PREREQ_OK=0
        echo ""
        if [ "$PREREQ_OK" -eq 0 ]; then
            echo "Pre-requisitos nao atendidos. Instale o JDK e adicione ao PATH."
            echo "  Ubuntu/Debian: sudo apt install default-jdk"
            echo "  Fedora/RHEL:   sudo dnf install java-latest-openjdk-devel"
            echo "  Download:      https://adoptium.net/"
            exit 1
        fi
        echo "Pre-requisitos atendidos. Iniciando programa..."
        echo ""
        cd "$SCRIPT_DIR/java"
        if [ ! -f "CertificadoDinamico.class" ]; then
            echo "Compilando CertificadoDinamico.java..."
            javac CertificadoDinamico.java
            if [ $? -ne 0 ]; then
                echo "[ERRO] Falha na compilacao."
                exit 1
            fi
        fi
        java CertificadoDinamico
        ;;
    2)
        echo ""
        echo "Verificando pre-requisitos para Python..."
        echo ""
        PREREQ_OK=1
        # Usamos um array para permitir comandos com argumentos (ex.: "py -3") e
        # tambem caminhos com espacos (ex.: "/c/Program Files/Python311/python").
        PYTHON_CMD=()

        # Em Windows (Git Bash/MSYS), 'python' e 'python3' frequentemente apontam
        # para o stub do Microsoft Store (em WindowsApps), que apenas exibe instrucoes
        # de instalacao e nao executa nada. Tentamos primeiro o Python Launcher (py)
        # e em seguida python3/python, ignorando ocorrencias em WindowsApps e
        # validando com --version para descartar instalacoes quebradas.

        # 1. Python Launcher (py -3), padrao em instalacoes oficiais no Windows
        if command -v py >/dev/null 2>&1 && py -3 --version >/dev/null 2>&1; then
            PYTHON_CMD=(py -3)
        fi

        # 2. python3 ou python no PATH, descartando o stub do Microsoft Store
        if [ ${#PYTHON_CMD[@]} -eq 0 ]; then
            for cmd in python3 python; do
                while IFS= read -r cmd_path; do
                    [ -z "$cmd_path" ] && continue
                    case "$cmd_path" in
                        *WindowsApps*) continue ;;
                    esac
                    if "$cmd_path" --version >/dev/null 2>&1; then
                        PYTHON_CMD=("$cmd_path")
                        break 2
                    fi
                done < <(type -ap "$cmd" 2>/dev/null)
            done
        fi

        if [ ${#PYTHON_CMD[@]} -eq 0 ]; then
            echo "  [ERRO] python nao encontrado"
            case "${OSTYPE:-}" in
                msys*|cygwin*|win32*)
                    echo "         No Windows, o stub do Microsoft Store (WindowsApps)"
                    echo "         nao executa nada. Desabilite-o em:"
                    echo "         Configuracoes > Aplicativos > Aliases de execucao do aplicativo"
                    echo "         ou instale o Python oficial em https://www.python.org/downloads/"
                    ;;
            esac
            PREREQ_OK=0
        else
            echo "  [OK] $("${PYTHON_CMD[@]}" --version 2>&1)"
        fi
        if [ ${#PYTHON_CMD[@]} -gt 0 ]; then
            if "${PYTHON_CMD[@]}" -c "import requests" &> /dev/null; then
                echo "  [OK] Biblioteca 'requests' disponivel"
            else
                echo "  [AVISO] Biblioteca 'requests' nao encontrada (sera instalada automaticamente)"
            fi
            if "${PYTHON_CMD[@]}" -c "import cryptography" &> /dev/null; then
                echo "  [OK] Biblioteca 'cryptography' disponivel"
            else
                echo "  [AVISO] Biblioteca 'cryptography' nao encontrada (sera instalada automaticamente)"
            fi
        fi
        verificar_arquivo "$SCRIPT_DIR/python/certificado_dinamico.py" || PREREQ_OK=0
        echo ""
        if [ "$PREREQ_OK" -eq 0 ]; then
            echo "Pre-requisitos nao atendidos. Instale o Python e adicione ao PATH."
            echo "  Ubuntu/Debian: sudo apt install python3"
            echo "  Fedora/RHEL:   sudo dnf install python3"
            echo "  Download:      https://www.python.org/downloads/"
            exit 1
        fi
        echo "Pre-requisitos atendidos. Iniciando programa..."
        echo ""
        cd "$SCRIPT_DIR/python"
        echo "Instalando dependencias..."

        # Em ambientes corporativos com SSL inspection (ex: proxy Itau), o pip precisa de:
        #  - usar o proxy correto (a variavel HTTPS_PROXY pode apontar para porta inativa)
        #  - confiar nos hosts do PyPI para nao falhar na verificacao do certificado MITM
        PIP_PROXY_ARGS=()
        if [ -n "$HTTP_PROXY" ]; then
            PIP_PROXY_ARGS=(--proxy "$HTTP_PROXY")
        elif [ -n "$http_proxy" ]; then
            PIP_PROXY_ARGS=(--proxy "$http_proxy")
        fi
        PIP_TRUSTED=(--trusted-host pypi.org --trusted-host files.pythonhosted.org --trusted-host pypi.python.org)

        "${PYTHON_CMD[@]}" -m pip install "${PIP_PROXY_ARGS[@]}" "${PIP_TRUSTED[@]}" requests --quiet
        "${PYTHON_CMD[@]}" -m pip install "${PIP_PROXY_ARGS[@]}" "${PIP_TRUSTED[@]}" cryptography --quiet
        "${PYTHON_CMD[@]}" certificado_dinamico.py
        ;;
    3)
        echo ""
        echo "Verificando pre-requisitos para Node.js..."
        echo ""
        PREREQ_OK=1
        if command -v node &> /dev/null; then
            echo "  [OK] Node.js $(node --version 2>&1)"
        else
            echo "  [ERRO] node nao encontrado"
            PREREQ_OK=0
        fi
        verificar_arquivo "$SCRIPT_DIR/node/certificadoDinamico.js" || PREREQ_OK=0
        echo ""
        if [ "$PREREQ_OK" -eq 0 ]; then
            echo "Pre-requisitos nao atendidos. Instale o Node.js e adicione ao PATH."
            echo "  Ubuntu/Debian: sudo apt install nodejs"
            echo "  Fedora/RHEL:   sudo dnf install nodejs"
            echo "  Download:      https://nodejs.org/"
            exit 1
        fi
        echo "Pre-requisitos atendidos. Iniciando programa..."
        echo ""
        cd "$SCRIPT_DIR/node"
        node certificadoDinamico.js
        ;;
    4)
        echo ""
        echo "Verificando pre-requisitos para Go..."
        echo ""
        PREREQ_OK=1
        if command -v go &> /dev/null; then
            echo "  [OK] $(go version 2>&1)"
        else
            echo "  [ERRO] go nao encontrado"
            PREREQ_OK=0
        fi
        verificar_arquivo "$SCRIPT_DIR/go/main.go" || PREREQ_OK=0
        echo ""
        if [ "$PREREQ_OK" -eq 0 ]; then
            echo "Pre-requisitos nao atendidos. Instale o Go e adicione ao PATH."
            echo "  Ubuntu/Debian: sudo apt install golang-go"
            echo "  Fedora/RHEL:   sudo dnf install golang"
            echo "  Download:      https://go.dev/dl/"
            exit 1
        fi
        echo "Pre-requisitos atendidos. Iniciando programa..."
        echo ""
        cd "$SCRIPT_DIR/go"
        go run main.go
        ;;
    5)
        echo ""
        echo "Verificando pre-requisitos para C# (.NET)..."
        echo ""
        PREREQ_OK=1
        if command -v dotnet &> /dev/null; then
            echo "  [OK] .NET SDK $(dotnet --version 2>&1)"
        else
            echo "  [ERRO] dotnet nao encontrado"
            PREREQ_OK=0
        fi
        verificar_arquivo "$SCRIPT_DIR/csharp/CertificadoDinamico.cs" || PREREQ_OK=0
        echo ""
        if [ "$PREREQ_OK" -eq 0 ]; then
            echo "Pre-requisitos nao atendidos. Instale o .NET SDK e adicione ao PATH."
            echo "  Ubuntu/Debian: https://learn.microsoft.com/dotnet/core/install/linux-ubuntu"
            echo "  Fedora/RHEL:   sudo dnf install dotnet-sdk-8.0"
            echo "  Download:      https://dotnet.microsoft.com/download"
            exit 1
        fi
        echo "Pre-requisitos atendidos. Iniciando programa..."
        echo ""
        cd "$SCRIPT_DIR/csharp"
        if [ ! -f "CertificadoDinamico.csproj" ]; then
            cat > CertificadoDinamico.csproj <<EOF
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
</Project>
EOF
        fi
        dotnet run
        ;;
    *)
        echo "Opcao invalida."
        exit 1
        ;;
esac

Scripts de certificado dinâmico

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Runtime.CompilerServices;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;

public class CertificadoDinamico
{
    public static RSA GerarParDeChaves(string caminhoChavePrivada = "private.pem", string caminhoChavePublica = "public.pem")
    {
        RSA rsa = RSA.Create(2048);

        string privateKeyPem = rsa.ExportPkcs8PrivateKeyPem();
        File.WriteAllText(caminhoChavePrivada, privateKeyPem + Environment.NewLine);
        Console.WriteLine($"Chave privada salva em {Path.GetFullPath(caminhoChavePrivada)}");

        string publicKeyPem = rsa.ExportSubjectPublicKeyInfoPem();
        File.WriteAllText(caminhoChavePublica, publicKeyPem + Environment.NewLine);
        Console.WriteLine($"Chave publica salva em {Path.GetFullPath(caminhoChavePublica)}");

        return rsa;
    }

    private static RSA CarregarChavePrivada(string caminhoChavePrivada)
    {
        string pemContent = File.ReadAllText(caminhoChavePrivada);
        RSA rsa = RSA.Create();
        rsa.ImportFromPem(pemContent);
        return rsa;
    }

    // Carrega certificado + chave privada para uso em mTLS de forma compativel com
    // o SChannel (stack TLS nativa do Windows). X509Certificate2.CreateFromPem
    // associa a chave em memoria, mas no Windows ela nao fica persistida no
    // formato esperado pelo SChannel, o que causa o handshake mTLS a falhar com
    // "The SSL connection could not be established". A solucao padrao e
    // reexportar para PFX em memoria e reimportar.
    private static X509Certificate2 CarregarCertificadoMtls(string caminhoCRT, string caminhoKey)
    {
        string certPem = File.ReadAllText(caminhoCRT);
        string keyPem = File.ReadAllText(caminhoKey);
        using var certComChave = X509Certificate2.CreateFromPem(certPem, keyPem);
        byte[] pfxBytes = certComChave.Export(X509ContentType.Pkcs12);
        return new X509Certificate2(pfxBytes, (string)null, X509KeyStorageFlags.Exportable);
    }

    private static byte[] DescriptografarRsa(string caminhoChavePrivada, string dadosCifradosBase64)
    {
        using (RSA rsa = CarregarChavePrivada(caminhoChavePrivada))
        {
            byte[] dadosCifrados = Convert.FromBase64String(dadosCifradosBase64);
            return rsa.Decrypt(dadosCifrados, RSAEncryptionPadding.Pkcs1);
        }
    }

    private static string DescriptografarAes(byte[] chaveAes, string textoCifradoBase64)
    {
        byte[] textoCifrado = Convert.FromBase64String(textoCifradoBase64);
        byte[] iv = new byte[16];

        using (Aes aes = Aes.Create())
        {
            aes.Key = chaveAes;
            aes.IV = iv;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;

            using (ICryptoTransform decryptor = aes.CreateDecryptor())
            {
                byte[] textoDecifrado = decryptor.TransformFinalBlock(textoCifrado, 0, textoCifrado.Length);
                return Encoding.UTF8.GetString(textoDecifrado);
            }
        }
    }

    public static (string clientId, string token) DescriptografarCredenciais(
        string clientIdCifrado,
        string tokenCifrado,
        string chaveSessaoCifrada,
        string caminhoChavePrivada)
    {
        Console.WriteLine("\n=========================================");
        Console.WriteLine("    Processo de Descriptografia           ");
        Console.WriteLine("=========================================");

        byte[] chaveSessaoDecifrada = DescriptografarRsa(caminhoChavePrivada, chaveSessaoCifrada);

        string clientIdDecifrado = DescriptografarAes(chaveSessaoDecifrada, clientIdCifrado);
        Console.WriteLine($"\nClient ID decifrado com a chave de sessao AES:\n[ {Mascarar(clientIdDecifrado)} ]");

        string tokenDecifrado = DescriptografarAes(chaveSessaoDecifrada, tokenCifrado);
        Console.WriteLine($"\nToken decifrado com a chave de sessao AES:\n[ {Mascarar(tokenDecifrado)} ]");

        return (clientIdDecifrado, tokenDecifrado);
    }

    public static void GerarCSR(
        string clientId,
        string organizacao,
        string cidade,
        string estado,
        string pais,
        string caminhoCSR = "ARQUIVO_REQUEST_CERTIFICADO.csr",
        string caminhoChavePrivadaCSR = "ARQUIVO_CHAVE_PRIVADA.key")
    {
        using (RSA rsaCSR = RSA.Create(2048))
        {
            string privateKeyPem = rsaCSR.ExportPkcs8PrivateKeyPem();
            File.WriteAllText(caminhoChavePrivadaCSR, privateKeyPem + Environment.NewLine);
            Console.WriteLine($"\nChave privada do CSR salva em {Path.GetFullPath(caminhoChavePrivadaCSR)}");

            string subjectName = $"CN={clientId}, OU={organizacao}, L={cidade}, ST={estado}, C={pais}";

            CertificateRequest csr = new CertificateRequest(
                new X500DistinguishedName(subjectName),
                rsaCSR,
                HashAlgorithmName.SHA512,
                RSASignaturePadding.Pkcs1);

            byte[] csrDer = csr.CreateSigningRequest();
            string csrBase64 = Convert.ToBase64String(csrDer, Base64FormattingOptions.InsertLineBreaks);
            string csrPem = $"-----BEGIN CERTIFICATE REQUEST-----\n{csrBase64}\n-----END CERTIFICATE REQUEST-----\n";

            File.WriteAllText(caminhoCSR, csrPem);
            Console.WriteLine($"CSR salvo em {Path.GetFullPath(caminhoCSR)}");
        }
    }

    public static string EnviarCertificado(
        string token,
        string caminhoCSR = "ARQUIVO_REQUEST_CERTIFICADO.csr",
        string caminhoCRT = "certificado.crt")
    {
        string conteudoCSR = File.ReadAllText(caminhoCSR);

        using var client = new HttpClient();
        var requestMsg = new HttpRequestMessage(HttpMethod.Post, "https://sts.itau.com.br/seguranca/v1/certificado/solicitacao");
        requestMsg.Content = new StringContent(conteudoCSR, Encoding.ASCII, "text/plain");
        requestMsg.Headers.Accept.ParseAdd("*/*");
        requestMsg.Headers.AcceptEncoding.ParseAdd("gzip");
        requestMsg.Headers.AcceptEncoding.ParseAdd("deflate");
        requestMsg.Headers.AcceptEncoding.ParseAdd("br");
        requestMsg.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);

        string responseSecret = "";

        Log("INFO", "=== DETALHES DA REQUISICAO HTTP (Enviar CSR) ===");
        Log("INFO", $"URL: https://sts.itau.com.br/seguranca/v1/certificado/solicitacao");
        Log("INFO", "Metodo: POST");
        Log("INFO", $"Headers: Content-Type=text/plain, Accept=*/*, Accept-Encoding=gzip deflate br, Authorization=Bearer {Mascarar(token)}");
        string csrPreview = conteudoCSR.Length > 200 ? conteudoCSR.Substring(0, 200) + "..." : conteudoCSR;
        Log("INFO", $"Body (CSR): {csrPreview}");
        Log("INFO", $"Tamanho do body: {conteudoCSR.Length} bytes");
        try
        {
            var httpResponse = client.SendAsync(requestMsg).Result;
            string response = httpResponse.Content.ReadAsStringAsync().Result;

            Log("INFO", "=== DETALHES DA RESPOSTA HTTP (Enviar CSR) ===");
            Log("INFO", $"Status code: {(int)httpResponse.StatusCode}");
            string respHeaders = "";
            foreach (var header in httpResponse.Headers)
                respHeaders += $"{header.Key}={string.Join(", ", header.Value)}; ";
            Log("INFO", $"Response headers: {respHeaders}");
            string corpoResp = response.Length > 500 ? response.Substring(0, 500) : response;
            Log("INFO", $"Response body (primeiros 500 chars): {corpoResp}");

            if (!httpResponse.IsSuccessStatusCode)
            {
                string detalhe = DetalharErroHttp((int)httpResponse.StatusCode);
                throw new Exception($"Erro na geracao do certificado. Status: {(int)httpResponse.StatusCode}\n  Possivel causa: {detalhe}\n  Resposta do servidor: {corpoResp}");
            }

            StreamWriter fileCertificate = File.CreateText(caminhoCRT);
            var linhas = response.Split(Environment.NewLine.ToCharArray());

            var uuidPattern = new Regex(@"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}");
            foreach (var item in linhas)
            {
                if (item.Contains("Secret:"))
                {
                    var match = uuidPattern.Match(item);
                    responseSecret = match.Success ? match.Value : item;
                }
                else
                {
                    fileCertificate.WriteLine(item);
                }
            }

            fileCertificate.Close();
            Console.WriteLine($"\nCertificado salvo em {Path.GetFullPath(caminhoCRT)}");
            Console.WriteLine($"Client Secret: {Mascarar(responseSecret)}");

            return responseSecret;
        }
        catch (AggregateException aex) when (aex.InnerException is HttpRequestException hrex)
        {
            throw new Exception("Erro ao solicitar o certificado.\r\nDetalhe: " + hrex.Message);
        }
        catch (Exception ex) when (!(ex is AggregateException))
        {
            throw new Exception("Erro ao solicitar o certificado.\r\nDetalhe: " + ex.Message);
        }
    }

    public static string ObterTokenOAuth(string clientId, string clientSecret, string caminhoCRT, string caminhoKey)
    {
        var certificate = CarregarCertificadoMtls(caminhoCRT, caminhoKey);

        var handler = new HttpClientHandler();
        handler.ClientCertificates.Add(certificate);
        handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;

        using var client = new HttpClient(handler);

        string body = $"grant_type=client_credentials&client_id={Uri.EscapeDataString(clientId)}&client_secret={Uri.EscapeDataString(clientSecret)}";

        Log("INFO", "=== DETALHES DA REQUISICAO HTTP (Token OAuth) ===");
        Log("INFO", "URL: https://sts.itau.com.br/api/oauth/token");
        Log("INFO", "Metodo: POST");
        Log("INFO", "Headers: Content-Type=application/x-www-form-urlencoded");
        Log("INFO", $"Certificado mTLS: {caminhoCRT}");
        Log("INFO", $"Chave mTLS: {caminhoKey}");
        Log("INFO", $"Body: grant_type=client_credentials&client_id={Mascarar(clientId)}&client_secret={Mascarar(clientSecret)}");

        var content = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded");
        var response = client.PostAsync("https://sts.itau.com.br/api/oauth/token", content).Result;

        string responseBody = response.Content.ReadAsStringAsync().Result;

        Log("INFO", "=== DETALHES DA RESPOSTA HTTP (Token OAuth) ===");
        Log("INFO", $"Status code: {(int)response.StatusCode}");
        string respHeaders6 = "";
        foreach (var header in response.Headers)
            respHeaders6 += $"{header.Key}={string.Join(", ", header.Value)}; ";
        Log("INFO", $"Response headers: {respHeaders6}");
        string corpoResp6 = responseBody.Length > 500 ? responseBody.Substring(0, 500) : responseBody;
        Log("INFO", $"Response body (primeiros 500 chars): {corpoResp6}");

        if (!response.IsSuccessStatusCode)
        {
            string detalhe = DetalharErroHttp((int)response.StatusCode);
            throw new Exception($"Erro ao obter token OAuth. Status: {(int)response.StatusCode}\n  Possivel causa: {detalhe}\n  Resposta do servidor: {corpoResp6}");
        }

        return responseBody;
    }

    public static string RenovarCertificado(
        string tokenSTS,
        string caminhoCSR = "ARQUIVO_REQUEST_CERTIFICADO.csr",
        string caminhoCRT = "certificado.crt",
        string caminhoCertMtls = "",
        string caminhoKeyMtls = "")
    {
        string conteudoCSR = File.ReadAllText(caminhoCSR);

        HttpClientHandler handler = new HttpClientHandler();
        handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
        if (!string.IsNullOrEmpty(caminhoCertMtls) && !string.IsNullOrEmpty(caminhoKeyMtls))
        {
            var clientCert = CarregarCertificadoMtls(caminhoCertMtls, caminhoKeyMtls);
            handler.ClientCertificates.Add(clientCert);
            Log("INFO", $"mTLS cert: {caminhoCertMtls}");
            Log("INFO", $"mTLS key: {caminhoKeyMtls}");
        }
        using var client = new HttpClient(handler);
        var requestMsg = new HttpRequestMessage(HttpMethod.Post, "https://sts.itau.com.br/seguranca/v2/certificado/renovacao");
        requestMsg.Content = new StringContent(conteudoCSR, Encoding.ASCII, "text/plain");
        string correlationId = Guid.NewGuid().ToString();
        requestMsg.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", tokenSTS);
        requestMsg.Headers.Add("x-itau-correlationID", correlationId);

        Log("INFO", "=== DETALHES DA REQUISICAO HTTP (Renovacao) ===");
        Log("INFO", $"URL: https://sts.itau.com.br/seguranca/v2/certificado/renovacao");
        Log("INFO", "Metodo: POST");
        Log("INFO", $"Headers: Content-Type=text/plain, Authorization=Bearer {Mascarar(tokenSTS)}, x-itau-correlationID={correlationId}");
        string csrPreview5 = conteudoCSR.Length > 200 ? conteudoCSR.Substring(0, 200) + "..." : conteudoCSR;
        Log("INFO", $"Body (CSR): {csrPreview5}");
        Log("INFO", $"Tamanho do body: {conteudoCSR.Length} bytes");
        try
        {
            var httpResponse = client.SendAsync(requestMsg).Result;
            string response = httpResponse.Content.ReadAsStringAsync().Result;

            Log("INFO", "=== DETALHES DA RESPOSTA HTTP (Renovacao) ===");
            Log("INFO", $"Status code: {(int)httpResponse.StatusCode}");
            string respHeaders5 = "";
            foreach (var header in httpResponse.Headers)
                respHeaders5 += $"{header.Key}={string.Join(", ", header.Value)}; ";
            Log("INFO", $"Response headers: {respHeaders5}");
            string corpoResp5 = response.Length > 500 ? response.Substring(0, 500) : response;
            Log("INFO", $"Response body (primeiros 500 chars): {corpoResp5}");

            if (!httpResponse.IsSuccessStatusCode)
            {
                string detalhe = DetalharErroHttp((int)httpResponse.StatusCode);
                throw new Exception($"Erro na renovacao do certificado. Status: {(int)httpResponse.StatusCode}\n  Possivel causa: {detalhe}\n  Resposta do servidor: {corpoResp5}");
            }

            File.WriteAllText(caminhoCRT, response);
            Console.WriteLine($"\nCertificado renovado salvo em {Path.GetFullPath(caminhoCRT)}");

            return response;
        }
        catch (AggregateException aex) when (aex.InnerException is HttpRequestException hrex)
        {
            throw new Exception("Erro ao renovar o certificado.\r\nDetalhe: " + hrex.Message);
        }
        catch (Exception ex) when (!(ex is AggregateException))
        {
            throw new Exception("Erro ao renovar o certificado.\r\nDetalhe: " + ex.Message);
        }
    }

    // Caminho do arquivo fonte em tempo de compilacao (equivalente a __file__ em Python,
    // __dirname em Node.js e runtime.Caller(0) em Go). Necessario porque
    // AppDomain.CurrentDomain.BaseDirectory aponta para o diretorio do binario
    // (csharp/bin/Debug/netX.X/) e nao para a pasta csharp/ onde o codigo esta.
    private static string GetThisFilePath([CallerFilePath] string path = "") => path;

    // sobe de csharp/ para certificado-dinamico/
    private static readonly string _dirRaiz = ResolverDirRaiz();
    private static readonly string PastaSaida = Path.Combine(_dirRaiz, "output");
    private static readonly string PastaEtapa1 = Path.Combine(PastaSaida, "etapa1");
    private static readonly string PastaEtapa2 = Path.Combine(PastaSaida, "etapa2");
    private static readonly string PastaEtapa4 = Path.Combine(PastaSaida, "etapa4");
    private static readonly string PastaLogs = Path.Combine(PastaSaida, "logs");
    private static readonly string ArquivoEstado = Path.Combine(PastaSaida, "estado_certificado.json");

    private static StreamWriter logWriter = null;

    private static string ResolverDirRaiz()
    {
        string thisFile = GetThisFilePath();
        string thisDir = Path.GetDirectoryName(thisFile);
        if (!string.IsNullOrEmpty(thisDir) && Directory.Exists(thisDir))
        {
            return Path.GetFullPath(Path.Combine(thisDir, ".."));
        }
        // Fallback caso o caminho compilado nao exista mais (ex: binarios copiados para outra maquina).
        // Assume a estrutura padrao csharp/bin/Debug/netX.X/ e sobe 4 niveis ate certificado-dinamico/.
        return Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", ".."));
    }

    private static string DetalharErroHttp(int statusCode)
    {
        switch (statusCode)
        {
            case 400: return "Requisicao invalida. O CSR pode estar malformado ou os dados enviados estao incorretos.";
            case 401: return "Nao autorizado. O token pode estar invalido, expirado ou nao foi informado corretamente. Tente executar novamente a partir da etapa 2.";
            case 403: return "Acesso negado. O token nao tem permissao para esta operacao ou esta expirado. Tente executar novamente a partir da etapa 2 para obter um novo token.";
            case 404: return "Endpoint nao encontrado. Verifique se a URL do STS Itau esta correta.";
            case 500: return "Erro interno do servidor STS Itau. Tente novamente mais tarde.";
            case 502: return "Bad Gateway. O servidor STS Itau esta temporariamente indisponivel. Tente novamente mais tarde.";
            case 503: return "Servico indisponivel. O servidor STS Itau esta em manutencao. Tente novamente mais tarde.";
            default: return $"Erro HTTP {statusCode}. Verifique a documentacao do STS Itau.";
        }
    }

    private static string Mascarar(string valor)
    {
        if (string.IsNullOrWhiteSpace(valor)) return "******";
        valor = valor.Trim();
        if (valor.Length <= 6) return "******";
        return valor.Substring(0, 3) + "******" + valor.Substring(valor.Length - 3);
    }

    // Formata uma excecao incluindo a cadeia de InnerException, util para diagnosticar
    // erros como AggregateException onde a causa real fica em InnerException.
    private static string FormatarExcecao(Exception ex)
    {
        var sb = new StringBuilder();
        var atual = ex;
        int nivel = 0;
        while (atual != null && nivel < 5)
        {
            if (nivel > 0) sb.Append(" --> ");
            sb.Append(atual.GetType().Name).Append(": ").Append(atual.Message);
            atual = (atual is AggregateException ae && ae.InnerExceptions.Count > 0)
                ? ae.InnerExceptions[0]
                : atual.InnerException;
            nivel++;
        }
        return sb.ToString();
    }

    private static string ConfigurarLog()
    {
        Directory.CreateDirectory(PastaLogs);
        string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
        string arquivoLog = Path.Combine(PastaLogs, $"certificado_dinamico_{timestamp}.log");
        logWriter = new StreamWriter(arquivoLog, true, Encoding.UTF8) { AutoFlush = true };
        return arquivoLog;
    }

    private static void Log(string nivel, string mensagem)
    {
        if (logWriter == null) return;
        string ts = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
        logWriter.WriteLine($"{ts} [{nivel}] {mensagem}");
    }

    private static readonly Dictionary<int, string> EtapasDesc = new Dictionary<int, string>
    {
        { 1, "Gerar par de chaves e exibir chave publica" },
        { 2, "Descriptografar credenciais, gerar e enviar certificado" },
        { 3, "Testar geracao de token" }
    };

    // Chaves cujo valor deve ser serializado como numero JSON (sem aspas).
    // Demais chaves sao tratadas como strings.
    private static readonly HashSet<string> ChavesNumericasEstado = new HashSet<string>
    {
        "etapaConcluida"
    };

    private static Dictionary<string, string> CarregarEstado()
    {
        var estado = new Dictionary<string, string> { { "etapaConcluida", "0" } };
        foreach (string pasta in new[] { PastaSaida, PastaEtapa1, PastaEtapa2, PastaEtapa4, PastaLogs })
            Directory.CreateDirectory(pasta);
        if (File.Exists(ArquivoEstado))
        {
            try
            {
                string json = File.ReadAllText(ArquivoEstado);
                var parsed = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json);
                if (parsed != null)
                {
                    estado.Clear();
                    foreach (var kv in parsed)
                    {
                        estado[kv.Key] = kv.Value.ValueKind == JsonValueKind.String
                            ? kv.Value.GetString()
                            : kv.Value.GetRawText();
                    }
                }
            }
            catch {  }
        }
        return estado;
    }

    private static void SalvarEstado(Dictionary<string, string> estado)
    {
        var sb = new StringBuilder();
        sb.Append("{\n");
        int i = 0;
        int total = estado.Count;
        foreach (var kv in estado)
        {
            string chave = kv.Key;
            string valor = kv.Value ?? string.Empty;
            sb.Append("  ").Append(JsonSerializer.Serialize(chave)).Append(": ");
            if (ChavesNumericasEstado.Contains(chave) && long.TryParse(valor, out long n))
            {
                sb.Append(n);
            }
            else
            {
                sb.Append(JsonSerializer.Serialize(valor));
            }
            if (i < total - 1) sb.Append(",");
            sb.Append("\n");
            i++;
        }
        sb.Append("}");
        File.WriteAllText(ArquivoEstado, sb.ToString());
    }

    private static int GetEtapaConcluida(Dictionary<string, string> estado)
    {
        if (estado.TryGetValue("etapaConcluida", out string val) && int.TryParse(val, out int n))
            return n;
        return 0;
    }

    private static string GetEstadoStr(Dictionary<string, string> estado, string chave, string padrao)
    {
        return estado.TryGetValue(chave, out string val) ? val : padrao;
    }

    private static string StatusEtapa(int n, int etapaConcluida)
    {
        if (n <= etapaConcluida) return "[CONCLUIDA]";
        if (n == etapaConcluida + 1) return "[PROXIMA >>] <------ voce esta aqui";
        return "[PENDENTE]";
    }

    private static int ExibirMenu(Dictionary<string, string> estado)
    {
        int etapaConcluida = GetEtapaConcluida(estado);

        Console.WriteLine("\n=============================================");
        Console.WriteLine("  Certificado Dinamico Itau - Menu Principal ");
        Console.WriteLine("=============================================");
        Console.WriteLine($"\n  Progresso atual: Etapa {etapaConcluida} de 3 concluida(s)\n");

        Console.WriteLine($"  1. {EtapasDesc[1]} {StatusEtapa(1, etapaConcluida)}");

        Console.WriteLine($"  2. {EtapasDesc[2]} {StatusEtapa(2, etapaConcluida)}");

        Console.WriteLine($"  3. {EtapasDesc[3]} {StatusEtapa(3, etapaConcluida)}");

        Console.WriteLine("  4. Renovar certificado");

        Console.WriteLine("\n  0. Sair");
        Console.WriteLine("  9. Recomecar do zero (limpa progresso)");

        return etapaConcluida + 1 <= 3 ? etapaConcluida + 1 : 3;
    }

    static void Main(string[] args)
    {
        string arquivoLog = ConfigurarLog();
        Log("INFO", "Programa iniciado");
        Console.WriteLine($"  Log desta execucao: {arquivoLog}");

        var estado = CarregarEstado();
        string clientSecretMemoria = "";
        while (true)
        {
            int proxima = ExibirMenu(estado);

            Console.Write($"\nEscolha a etapa (padrao: {proxima}): ");
            string escolhaStr = Console.ReadLine()?.Trim() ?? "";
            int escolha;
            if (string.IsNullOrEmpty(escolhaStr))
            {
                escolha = proxima;
            }
            else if (!int.TryParse(escolhaStr, out escolha))
            {
                Console.WriteLine("Opcao invalida.");
                continue;
            }

            try
            {
                Log("INFO", $"Opcao escolhida pelo usuario: {escolha}");

                if (escolha == 0)
                {
                    Log("INFO", "Programa encerrado pelo usuario");
                    Console.WriteLine("\nSaindo. Seu progresso foi salvo.");
                    break;
                }
                else if (escolha == 9)
                {
                    Log("INFO", "Limpeza de progresso e arquivos gerados iniciada");
                    foreach (string pasta in new[] { PastaEtapa1, PastaEtapa2, PastaEtapa4 })
                    {
                        if (Directory.Exists(pasta))
                        {
                            foreach (string arquivo in Directory.GetFiles(pasta))
                            {
                                File.Delete(arquivo);
                            }
                            Console.WriteLine($"  Arquivos removidos da pasta: {Path.GetFullPath(pasta)}");
                            Log("INFO", $"Arquivos removidos da pasta: {Path.GetFullPath(pasta)}");
                        }
                    }
                    if (File.Exists(ArquivoEstado))
                    {
                        File.Delete(ArquivoEstado);
                        Console.WriteLine($"  Removido: {Path.GetFullPath(ArquivoEstado)}");
                        Log("INFO", $"Removido: {Path.GetFullPath(ArquivoEstado)}");
                    }
                    Console.WriteLine("\nProgresso e arquivos gerados removidos.");
                    Log("INFO", "Limpeza concluida");
                    estado = new Dictionary<string, string>();
                    clientSecretMemoria = "";
                    continue;
                }
                else if (escolha == 1)
                {
                    Log("INFO", "Inicio da Etapa 1: Gerar par de chaves e exibir chave publica");
                    Directory.CreateDirectory(PastaEtapa1);
                    Console.WriteLine("\n[PASSO 1.1] Gerando par de chaves RSA 2048...");
                    string caminhoPrivada = Path.Combine(PastaEtapa1, "private.pem");
                    string caminhoPublica = Path.Combine(PastaEtapa1, "public.pem");
                    GerarParDeChaves(caminhoPrivada, caminhoPublica);

                    Log("INFO", $"Chave privada gerada: {Path.GetFullPath(caminhoPrivada)}");
                    Log("INFO", $"Chave publica gerada: {Path.GetFullPath(caminhoPublica)}");

                    Console.WriteLine("\n[PASSO 1.2] Exibindo chave publica...");
                    string conteudoChavePublica = File.ReadAllText(caminhoPublica);
                    Console.WriteLine("\n=============================================");
                    Console.WriteLine("  Chave Publica Gerada                       ");
                    Console.WriteLine("=============================================");
                    Console.WriteLine(conteudoChavePublica);

                    estado["etapaConcluida"] = Math.Max(GetEtapaConcluida(estado), 1).ToString();
                    estado["caminhoChavePrivada"] = Path.GetFullPath(caminhoPrivada);
                    estado["caminhoChavePublica"] = Path.GetFullPath(caminhoPublica);
                    SalvarEstado(estado);
                    Log("INFO", "Etapa 1 concluida com sucesso");
                    Console.WriteLine("\n>> Etapa 1 concluida. Progresso salvo.");
                    Console.WriteLine("\n  O que fazer agora:");
                    Console.WriteLine("  1. Copie o conteudo da chave publica exibido acima");
                    Console.WriteLine("  2. Envie a chave publica para o Analista de operacoes Itau");
                    Console.WriteLine("  3. Aguarde o e-mail do Itau com as credenciais cifradas");
                    Console.WriteLine("  4. Quando receber o e-mail, execute a etapa 2");
                }
                else if (escolha == 2)
                {
                    if (GetEtapaConcluida(estado) < 1)
                    {
                        Console.WriteLine("\n[AVISO] A etapa 1 (gerar chaves) precisa ser concluida antes.");
                        Log("WARN", "Tentativa de executar Etapa 2 sem concluir Etapa 1");
                        continue;
                    }

                    Log("INFO", "Inicio da Etapa 2: Descriptografar credenciais, gerar e enviar certificado");
                    Directory.CreateDirectory(PastaEtapa2);

                    Console.WriteLine("\n[PASSO 2.1] Descriptografar credenciais recebidas por e-mail...");
                    string caminhoChavePrivada = Path.GetFullPath(GetEstadoStr(estado, "caminhoChavePrivada", "private.pem"));
                    Console.WriteLine($"  Chave privada: {caminhoChavePrivada}");
                    Log("INFO", $"Chave privada utilizada: {Path.GetFullPath(caminhoChavePrivada)}");

                    Console.Write("Client ID cifrado: ");
                    string clientIdCifrado = Console.ReadLine().Trim();
                    Console.Write("Token temporario cifrado: ");
                    string tokenCifrado = Console.ReadLine().Trim();
                    Console.Write("Chave de sessao cifrada: ");
                    string chaveSessaoCifrada = Console.ReadLine().Trim();
                    Log("INFO", $"Client ID cifrado: {Mascarar(clientIdCifrado)}");
                    Log("INFO", $"Token cifrado: {Mascarar(tokenCifrado)}");
                    Log("INFO", $"Chave de sessao cifrada: {Mascarar(chaveSessaoCifrada)}");

                    var (clientId, tokenDecifrado) = DescriptografarCredenciais(
                        clientIdCifrado, tokenCifrado, chaveSessaoCifrada, caminhoChavePrivada);

                    string caminhoClientId = Path.Combine(PastaEtapa2, "client_id.txt");
                    File.WriteAllText(caminhoClientId, clientId);
                    Console.WriteLine($"  Client ID salvo em {Path.GetFullPath(caminhoClientId)}");
                    Log("INFO", $"Client ID decifrado: {Mascarar(clientId)}");

                    string caminhoToken = Path.Combine(PastaEtapa2, "token_temporario.txt");
                    File.WriteAllText(caminhoToken, tokenDecifrado);
                    Console.WriteLine($"  Token temporario salvo em {Path.GetFullPath(caminhoToken)}");
                    Log("INFO", $"Token decifrado: {Mascarar(tokenDecifrado)}");

                    Console.WriteLine("\n[PASSO 2.2] Gerando CSR (Certificate Sign Request)...");
                    Console.WriteLine($"  Client ID: {Mascarar(clientId)}");

                    string organizacao = "";
                    while (string.IsNullOrEmpty(organizacao))
                    {
                        Console.Write("Nome da Empresa: ");
                        organizacao = Console.ReadLine().Trim();
                        if (string.IsNullOrEmpty(organizacao)) Console.WriteLine("  Campo obrigatorio. Informe o nome da empresa.");
                    }
                    string cidade = "";
                    while (string.IsNullOrEmpty(cidade))
                    {
                        Console.Write("Cidade: ");
                        cidade = Console.ReadLine().Trim();
                        if (string.IsNullOrEmpty(cidade)) Console.WriteLine("  Campo obrigatorio. Informe a cidade.");
                    }
                    string estadoUF = "";
                    while (string.IsNullOrEmpty(estadoUF))
                    {
                        Console.Write("Estado - UF: ");
                        estadoUF = Console.ReadLine().Trim();
                        if (string.IsNullOrEmpty(estadoUF)) Console.WriteLine("  Campo obrigatorio. Informe o estado (UF).");
                    }
                    string pais = "";
                    while (string.IsNullOrEmpty(pais))
                    {
                        Console.Write("Pais: ");
                        pais = Console.ReadLine().Trim();
                        if (string.IsNullOrEmpty(pais)) Console.WriteLine("  Campo obrigatorio. Informe o pais.");
                    }
                    Log("INFO", $"Client ID: {Mascarar(clientId)}");
                    Log("INFO", $"Organizacao: {organizacao}, Cidade: {cidade}, Estado: {estadoUF}, Pais: {pais}");

                    string caminhoCSR3 = Path.Combine(PastaEtapa2, "ARQUIVO_REQUEST_CERTIFICADO.csr");
                    string caminhoChaveCSR = Path.Combine(PastaEtapa2, "ARQUIVO_CHAVE_PRIVADA.key");
                    GerarCSR(clientId, organizacao, cidade, estadoUF, pais, caminhoCSR3, caminhoChaveCSR);

                    Console.WriteLine("\n[PASSO 2.3] Enviando CSR ao STS Itau...");
                    Log("INFO", $"Token: {Mascarar(tokenDecifrado)}");
                    Log("INFO", $"CSR: {Path.GetFullPath(caminhoCSR3)}");
                    Log("INFO", "Enviando requisicao para https://sts.itau.com.br/seguranca/v1/certificado/solicitacao");

                    string caminhoCRT = Path.Combine(PastaEtapa2, "certificado.crt");
                    string secret = EnviarCertificado(tokenDecifrado, caminhoCSR3, caminhoCRT);
                    clientSecretMemoria = secret;

                    Console.WriteLine("\n=============================================");
                    Console.WriteLine("  Certificado gerado com sucesso!            ");
                    Console.WriteLine("=============================================");
                    Console.WriteLine("Arquivos gerados:");
                    Console.WriteLine($"  - {Path.GetFullPath(Path.Combine(PastaEtapa1, "private.pem"))} (chave privada para descriptografia)");
                    Console.WriteLine($"  - {Path.GetFullPath(Path.Combine(PastaEtapa1, "public.pem"))} (chave publica)");
                    Console.WriteLine($"  - {Path.GetFullPath(Path.Combine(PastaEtapa2, "client_id.txt"))} (Client ID)");
                    Console.WriteLine($"  - {Path.GetFullPath(Path.Combine(PastaEtapa2, "ARQUIVO_CHAVE_PRIVADA.key"))} (chave privada do certificado)");
                    Console.WriteLine($"  - {Path.GetFullPath(Path.Combine(PastaEtapa2, "ARQUIVO_REQUEST_CERTIFICADO.csr"))} (CSR)");
                    Console.WriteLine($"  - {Path.GetFullPath(Path.Combine(PastaEtapa2, "certificado.crt"))} (certificado assinado)");
                    Console.WriteLine("\n==============================================");
                    Console.WriteLine("  ATENCAO - CLIENT SECRET                     ");
                    Console.WriteLine("==============================================");
                    Console.WriteLine("\nO Client Secret e equivalente a uma SENHA. Trate-o com o mesmo cuidado.");
                    Console.WriteLine("O valor do Client Secret sera exibido APENAS NESTE MOMENTO.");
                    Console.WriteLine("Copie e salve em um local seguro. Este valor NAO sera armazenado pelo programa.");
                    Console.WriteLine($"\n  Client Secret: {secret}");
                    Console.WriteLine("\n==============================================");
                    Console.WriteLine("Guarde essas informacoes em local seguro. O Client ID e o Client Secret sao equivalentes a senhas");
                    Console.WriteLine("e serao necessarios para gerar tokens OAuth e consumir as APIs do Itau.");
                    Console.WriteLine("NAO compartilhe essas credenciais. Quem tiver acesso a elas podera acessar as APIs em seu nome.");
                    Console.WriteLine("\nO Itau nao se responsabiliza pelo armazenamento local das credenciais.");
                    Console.WriteLine("A guarda correta e de responsabilidade exclusiva do cliente.");

                    var expiracao = DateTime.Now.AddDays(365);
                    var inicioRenovacao = expiracao.AddDays(-30);
                    var fimRenovacao = expiracao.AddDays(-1);
                    Console.WriteLine("\n===============================================");
                    Console.WriteLine("  VALIDADE DO CERTIFICADO                      ");
                    Console.WriteLine("===============================================");
                    Console.WriteLine("  Validade: 365 dias");
                    Console.WriteLine($"  Data de expiracao: {expiracao:dd/MM/yyyy}");
                    Console.WriteLine($"  Periodo de renovacao: de {inicioRenovacao:dd/MM/yyyy} ate {fimRenovacao:dd/MM/yyyy}");
                    Console.WriteLine("\n  O processo de renovacao (etapa 4) pode ser realizado a partir de");
                    Console.WriteLine("  30 dias antes da expiracao ate um dia antes da data de expiracao do certificado.");
                    Console.WriteLine("\n  ATENCAO: A responsabilidade de renovar o certificado dentro do prazo");
                    Console.WriteLine("  e exclusivamente sua. Certificados expirados impossibilitam o acesso");
                    Console.WriteLine("  as APIs do Itau e exigem a geracao de um novo certificado do zero.");

                    estado["etapaConcluida"] = Math.Max(GetEtapaConcluida(estado), 2).ToString();
                    estado["caminhoClientId"] = Path.GetFullPath(caminhoClientId);
                    estado["caminhoChavePrivada"] = caminhoChavePrivada;
                    estado["caminhoCSR"] = Path.GetFullPath(caminhoCSR3);
                    estado["caminhoCRT"] = Path.GetFullPath(caminhoCRT);
                    SalvarEstado(estado);
                    Log("INFO", $"Certificado salvo: {Path.GetFullPath(caminhoCRT)}");
                    Log("INFO", $"Client Secret: {Mascarar(secret)}");
                    Log("INFO", "Etapa 2 concluida com sucesso");
                    Console.WriteLine("\n>> Etapa 2 concluida. Progresso salvo.");
                    Console.WriteLine("\n  O que fazer agora:");
                    Console.WriteLine("  1. Guarde o Client ID e o Client Secret em local seguro - eles sao equivalentes a SENHAS");
                    Console.WriteLine($"  2. O certificado (.crt) e a chave privada (.key) estao salvos na pasta {Path.GetFullPath(PastaEtapa2)}");
                    Console.WriteLine("  3. Execute a etapa 3 para validar que tudo esta funcionando corretamente");
                    Console.WriteLine("  4. Apos validar, utilize o client_id, client_secret, certificado e chave privada na sua aplicacao");
                    Console.WriteLine("\n  LEMBRE-SE: Client ID, Client Secret, certificado e chave privada sao equivalentes a senhas.");
                    Console.WriteLine("  NAO compartilhe, NAO envie por e-mail e NAO deixe exposto em codigo-fonte.");
                    Console.WriteLine("  O Itau NAO se responsabiliza pelo armazenamento local. A guarda e responsabilidade exclusiva do cliente.");

                    Console.Write("\nDeseja gerar o arquivo PFX (PKCS#12)? O PFX combina o certificado e a chave privada em um unico arquivo protegido por senha. (s/N): ");
                    string respPfx2 = Console.ReadLine().Trim();
                    if (respPfx2.Equals("s", StringComparison.OrdinalIgnoreCase))
                    {
                        Console.Write("  Informe a senha para proteger o PFX: ");
                        string senhaPfx2 = Console.ReadLine().Trim();
                        if (string.IsNullOrEmpty(senhaPfx2))
                        {
                            Console.WriteLine("  Senha nao informada. PFX nao gerado.");
                            Log("WARN", "PFX nao gerado - senha nao informada");
                        }
                        else
                        {
                            string caminhoPfx2 = Path.Combine(PastaEtapa2, "certificado.pfx");
                            string caminhoKeyPfx2 = Path.GetFullPath(Path.Combine(PastaEtapa2, "ARQUIVO_CHAVE_PRIVADA.key"));
                            var certPfx = X509Certificate2.CreateFromPemFile(caminhoCRT, caminhoKeyPfx2);
                            File.WriteAllBytes(caminhoPfx2, certPfx.Export(X509ContentType.Pfx, senhaPfx2));
                            Console.WriteLine($"  PFX gerado: {Path.GetFullPath(caminhoPfx2)}");
                            Log("INFO", $"PFX gerado: {Path.GetFullPath(caminhoPfx2)}");
                        }
                    }
                }
                else if (escolha == 3)
                {
                    if (GetEtapaConcluida(estado) < 2)
                    {
                        Console.WriteLine("\n[AVISO] A etapa 2 (gerar e enviar certificado) precisa ser concluida antes.");
                        Log("WARN", "Tentativa de executar Etapa 3 sem concluir Etapa 2");
                        continue;
                    }
                                        Log("INFO", "Inicio da Etapa 3: Testar geracao de token");
                                        Console.WriteLine("\n[PASSO 3] Testando geracao de token...");
                    string caminhoClientId7 = GetEstadoStr(estado, "caminhoClientId", Path.Combine(PastaEtapa2, "client_id.txt"));
                    if (!File.Exists(caminhoClientId7))
                    {
                        throw new Exception($"Arquivo de client_id nao encontrado: {caminhoClientId7}. Execute as etapas anteriores.");
                    }
                    string clientIdOAuth = File.ReadAllText(caminhoClientId7).Trim();
                    string clientSecretOAuth;
                    if (!string.IsNullOrEmpty(clientSecretMemoria))
                    {
                        clientSecretOAuth = clientSecretMemoria;
                        Console.WriteLine($"  Client Secret: {Mascarar(clientSecretOAuth)} (obtido da sessao atual)");
                    }
                    else
                    {
                        Console.Write("  Informe o Client Secret: ");
                        clientSecretOAuth = Console.ReadLine().Trim();
                    }
                    string caminhoCRTOAuth = GetEstadoStr(estado, "caminhoCRT", Path.Combine(PastaEtapa2, "certificado.crt"));
                    string caminhoKeyOAuth = Path.GetFullPath(Path.Combine(PastaEtapa2, "ARQUIVO_CHAVE_PRIVADA.key"));

                    Console.WriteLine("\n  Arquivos utilizados na requisicao:");
                    Console.WriteLine($"  - {Path.GetFullPath(caminhoClientId7)} (Client ID: {Mascarar(clientIdOAuth)})");
                    Console.WriteLine($"  - {Path.GetFullPath(caminhoCRTOAuth)} (certificado assinado)");
                    Console.WriteLine($"  - {Path.GetFullPath(caminhoKeyOAuth)} (chave privada do certificado)");

                    string respostaOAuth = ObterTokenOAuth(clientIdOAuth, clientSecretOAuth, caminhoCRTOAuth, caminhoKeyOAuth);
                    Log("INFO", "Token OAuth obtido com sucesso");
                    Log("INFO", "Etapa 3 concluida com sucesso");

                    string accessToken = "";
                    try
                    {
                        var jsonDoc = System.Text.Json.JsonDocument.Parse(respostaOAuth);
                        if (jsonDoc.RootElement.TryGetProperty("access_token", out var atProp))
                            accessToken = atProp.GetString() ?? "";
                    }
                    catch {}

                    Console.WriteLine("\n==============================================");
                    Console.WriteLine("  Credenciais e certificado VALIDADOS!        ");
                    Console.WriteLine("==============================================");
                    Console.WriteLine("\nAccess Token:");
                    Console.WriteLine(string.IsNullOrEmpty(accessToken) ? respostaOAuth : accessToken);
                    Console.WriteLine("\n  Validade: 300 segundos (5 minutos)");
                    Console.WriteLine("  Uso: Informe este token no header 'Authorization: Bearer <access_token>' das requisicoes as APIs do Itau.");
                    Console.WriteLine("\nAs credenciais (client_id e client_secret) e o certificado digital estao validados e funcionais.");
                    Console.WriteLine("Voce ja pode utiliza-los em suas integracoes.");
                    Console.WriteLine("\n>> Etapa 3 concluida.");
                    Console.WriteLine("\n  O que fazer agora:");
                    Console.WriteLine("  1. Suas credenciais e certificado estao validados e prontos para uso");
                    Console.WriteLine("  2. Na sua aplicacao, implemente o fluxo OAuth usando:");
                    Console.WriteLine("     - Client ID e Client Secret (da etapa 2)");
                    Console.WriteLine($"     - Certificado .crt e chave privada .key (da pasta {Path.GetFullPath(PastaEtapa2)})");
                    Console.WriteLine("  3. O token gerado tem validade de 5 minutos - sua aplicacao deve renova-lo periodicamente");
                    Console.WriteLine("  4. Lembre-se de renovar o certificado antes da data de expiracao (etapa 4)");
                }
                else if (escolha == 4)
                {
                    Log("INFO", "Inicio da Etapa 4: Renovar certificado");
                    Directory.CreateDirectory(PastaEtapa4);

                    string caminhoCRTValidacao = GetEstadoStr(estado, "caminhoCRT", Path.Combine(PastaEtapa2, "certificado.crt"));
                    if (File.Exists(caminhoCRTValidacao))
                    {
                        try
                        {
                            var certObj = new X509Certificate2(caminhoCRTValidacao);
                            DateTime dataExpiracao = certObj.NotAfter;
                            int diasRestantes = (int)(dataExpiracao - DateTime.Now).TotalDays;
                            Log("INFO", $"Certificado encontrado: {Path.GetFullPath(caminhoCRTValidacao)}");
                            Log("INFO", $"Data de expiracao: {dataExpiracao:dd/MM/yyyy} | Dias restantes: {diasRestantes}");
                            Console.WriteLine($"\n  Certificado encontrado: {Path.GetFullPath(caminhoCRTValidacao)}");
                            Console.WriteLine($"  Data de expiracao: {dataExpiracao:dd/MM/yyyy} ({diasRestantes} dias restantes)");
                            if (diasRestantes > 30)
                            {
                                Console.WriteLine("\n  [AVISO] O certificado ainda nao esta no periodo de renovacao.");
                                Console.WriteLine("  A renovacao e permitida nos ultimos 30 dias antes da expiracao.");
                                Console.WriteLine($"  Faltam {diasRestantes - 30} dias para o inicio do periodo de renovacao.");
                                Log("WARN", $"Certificado fora do periodo de renovacao. Dias restantes: {diasRestantes}");
                                Console.Write("\n  Deseja prosseguir mesmo assim? (s/N): ");
                                string prosseguir = Console.ReadLine().Trim();
                                if (!prosseguir.Equals("s", StringComparison.OrdinalIgnoreCase))
                                {
                                    Console.WriteLine("  Renovacao cancelada pelo usuario.");
                                    Log("INFO", "Renovacao cancelada pelo usuario - fora do periodo");
                                    continue;
                                }
                                Log("INFO", "Usuario optou por prosseguir com a renovacao fora do periodo");
                            }
                            else if (diasRestantes < 0)
                            {
                                Console.WriteLine($"\n  [AVISO] O certificado ja esta EXPIRADO ha {Math.Abs(diasRestantes)} dias.");
                                Log("WARN", $"Certificado expirado ha {Math.Abs(diasRestantes)} dias");
                            }
                            else
                            {
                                Console.WriteLine("  Certificado dentro do periodo de renovacao.");
                            }
                        }
                        catch (Exception exCert)
                        {
                            Log("WARN", $"Nao foi possivel ler o certificado para validacao: {exCert.Message}");
                            Console.WriteLine("\n  [AVISO] Nao foi possivel ler o certificado para validar a data de expiracao.");
                            Console.WriteLine("  Prosseguindo com a renovacao...");
                        }
                    }
                    else
                    {
                        Log("INFO", "Certificado nao encontrado para validacao de periodo. Prosseguindo com a renovacao.");
                        Console.WriteLine($"\n  [INFO] Certificado nao encontrado em: {Path.GetFullPath(caminhoCRTValidacao)}");
                        Console.WriteLine("  Nao foi possivel validar o periodo de renovacao (30 dias antes da expiracao).");
                        Console.WriteLine("  Prosseguindo com a renovacao...");
                    }

                    Console.WriteLine("\n=============================================");
                    Console.WriteLine("  Renovacao de Certificado - Escolha a opcao ");
                    Console.WriteLine("=============================================");
                    Console.WriteLine("\n  A. Renovacao completa (gera novo CSR e nova chave privada) - recomendada");
                    Console.WriteLine("     - Gera nova chave privada e novo CSR antes de renovar");
                    Console.WriteLine("     - Recomendado para maior seguranca ou quando a chave pode ter sido comprometida");
                    Console.WriteLine("\n  B. Renovacao simples (reutiliza o CSR existente)");
                    Console.WriteLine("     - Mais rapido, mantem a mesma chave privada do certificado");
                    Console.WriteLine("     - Recomendado quando nao ha necessidade de trocar a chave");

                    Console.Write("\nEscolha a opcao (A ou B): ");
                    string opcaoRenov = Console.ReadLine().Trim().ToUpper();
                    if (opcaoRenov != "A" && opcaoRenov != "B")
                    {
                        Console.WriteLine("Opcao invalida. Escolha A ou B.");
                        continue;
                    }

                    // Funcao local para obter token
                    (string token, string certPath, string keyPath) ObterTokenRenovacao()
                    {
                        Console.WriteLine("\n=============================================");
                        Console.WriteLine("  Como deseja obter o token para renovacao?  ");
                        Console.WriteLine("=============================================");
                        Console.WriteLine("  1. Automatico (via mTLS com certificado atual)");
                        Console.WriteLine("  2. Manual (informar token manualmente)");
                        Console.Write("\nEscolha (1 ou 2): ");
                        string opcaoToken = Console.ReadLine().Trim();

                        if (opcaoToken == "1")
                        {
                            Log("INFO", "Obtencao de token automatica via mTLS");
                            Console.WriteLine("\n[PASSO 2.1] Obtendo token automaticamente via mTLS...");
                            string caminhoClientIdToken = GetEstadoStr(estado, "caminhoClientId", Path.Combine(PastaEtapa2, "client_id.txt"));
                            string clientIdToken;
                            if (File.Exists(caminhoClientIdToken))
                            {
                                clientIdToken = File.ReadAllText(caminhoClientIdToken).Trim();
                                Console.WriteLine($"  Client ID encontrado: {Mascarar(clientIdToken)}");
                            }
                            else if (estado.ContainsKey("clientId") && !string.IsNullOrEmpty(estado["clientId"]))
                            {
                                clientIdToken = estado["clientId"];
                                Console.WriteLine($"  Client ID recuperado do estado salvo: {Mascarar(clientIdToken)}");
                            }
                            else
                            {
                                Console.WriteLine($"  [AVISO] Arquivo de Client ID nao encontrado em: {Path.GetFullPath(caminhoClientIdToken)}");
                                clientIdToken = "";
                                while (string.IsNullOrEmpty(clientIdToken))
                                {
                                    Console.Write("  Informe o Client ID: ");
                                    clientIdToken = Console.ReadLine().Trim();
                                    if (string.IsNullOrEmpty(clientIdToken)) Console.WriteLine("  Campo obrigatorio. Informe o Client ID.");
                                }
                            }
                            string clientSecretToken;
                            if (!string.IsNullOrEmpty(clientSecretMemoria))
                            {
                                clientSecretToken = clientSecretMemoria;
                                Console.WriteLine($"  Client Secret: {Mascarar(clientSecretToken)} (obtido da sessao atual)");
                            }
                            else
                            {
                                clientSecretToken = "";
                                while (string.IsNullOrEmpty(clientSecretToken))
                                {
                                    Console.Write("  Informe o Client Secret: ");
                                    clientSecretToken = Console.ReadLine().Trim();
                                    if (string.IsNullOrEmpty(clientSecretToken)) Console.WriteLine("  Campo obrigatorio. Informe o Client Secret.");
                                }
                            }
                            string caminhoCRTToken = GetEstadoStr(estado, "caminhoCRT", Path.Combine(PastaEtapa2, "certificado.crt"));
                            if (!File.Exists(caminhoCRTToken))
                            {
                                Console.WriteLine($"  [AVISO] Certificado nao encontrado em: {Path.GetFullPath(caminhoCRTToken)}");
                                caminhoCRTToken = "";
                                while (string.IsNullOrEmpty(caminhoCRTToken) || !File.Exists(caminhoCRTToken))
                                {
                                    Console.Write("  Informe o caminho do certificado (.crt): ");
                                    caminhoCRTToken = Console.ReadLine().Trim();
                                    if (string.IsNullOrEmpty(caminhoCRTToken) || !File.Exists(caminhoCRTToken)) Console.WriteLine($"  Arquivo nao encontrado: {caminhoCRTToken}. Informe um caminho valido.");
                                }
                            }
                            Console.WriteLine($"  Certificado: {Path.GetFullPath(caminhoCRTToken)}");
                            string caminhoKeyToken = Path.GetFullPath(Path.Combine(PastaEtapa2, "ARQUIVO_CHAVE_PRIVADA.key"));
                            if (!File.Exists(caminhoKeyToken))
                            {
                                Console.WriteLine($"  [AVISO] Chave privada nao encontrada em: {caminhoKeyToken}");
                                caminhoKeyToken = "";
                                while (string.IsNullOrEmpty(caminhoKeyToken) || !File.Exists(caminhoKeyToken))
                                {
                                    Console.Write("  Informe o caminho da chave privada (.key): ");
                                    caminhoKeyToken = Console.ReadLine().Trim();
                                    if (string.IsNullOrEmpty(caminhoKeyToken) || !File.Exists(caminhoKeyToken)) Console.WriteLine($"  Arquivo nao encontrado: {caminhoKeyToken}. Informe um caminho valido.");
                                }
                            }
                            Console.WriteLine($"  Chave privada: {Path.GetFullPath(caminhoKeyToken)}");
                            string respostaToken = ObterTokenOAuth(clientIdToken, clientSecretToken, caminhoCRTToken, caminhoKeyToken);
                            string tokenObtido = "";
                            try
                            {
                                using var doc = JsonDocument.Parse(respostaToken);
                                if (doc.RootElement.TryGetProperty("access_token", out JsonElement atEl))
                                    tokenObtido = atEl.GetString() ?? "";
                            }
                            catch { }
                            if (string.IsNullOrEmpty(tokenObtido))
                                throw new Exception("Nao foi possivel obter o token automaticamente. Tente a opcao manual.");
                            Console.WriteLine($"  Token obtido com sucesso: {Mascarar(tokenObtido)}");
                            Log("INFO", $"Token obtido automaticamente via mTLS: {Mascarar(tokenObtido)}");
                            return (tokenObtido, caminhoCRTToken, caminhoKeyToken);
                        }
                        else
                        {
                            Log("INFO", "Obtencao de token manual");
                            Console.Write("Token STS para renovacao: ");
                            string tokenManual = Console.ReadLine().Trim();
                            if (string.IsNullOrEmpty(tokenManual)) throw new Exception("Token STS e obrigatorio para renovacao do certificado.");
                            Console.WriteLine("\n  Para a requisicao de renovacao, e necessario o certificado e a chave privada (mTLS).");
                            string caminhoCertMtls = GetEstadoStr(estado, "caminhoCRT", Path.Combine(PastaEtapa2, "certificado.crt"));
                            if (!File.Exists(caminhoCertMtls))
                            {
                                Console.WriteLine($"  [AVISO] Certificado nao encontrado em: {Path.GetFullPath(caminhoCertMtls)}");
                                caminhoCertMtls = "";
                                while (string.IsNullOrEmpty(caminhoCertMtls) || !File.Exists(caminhoCertMtls))
                                {
                                    Console.Write("  Informe o caminho do certificado (.crt): ");
                                    caminhoCertMtls = Console.ReadLine().Trim();
                                    if (string.IsNullOrEmpty(caminhoCertMtls) || !File.Exists(caminhoCertMtls)) Console.WriteLine($"  Arquivo nao encontrado: {caminhoCertMtls}. Informe um caminho valido.");
                                }
                            }
                            Console.WriteLine($"  Certificado: {Path.GetFullPath(caminhoCertMtls)}");
                            string caminhoKeyMtls = Path.GetFullPath(Path.Combine(PastaEtapa2, "ARQUIVO_CHAVE_PRIVADA.key"));
                            if (!File.Exists(caminhoKeyMtls))
                            {
                                Console.WriteLine($"  [AVISO] Chave privada nao encontrada em: {caminhoKeyMtls}");
                                caminhoKeyMtls = "";
                                while (string.IsNullOrEmpty(caminhoKeyMtls) || !File.Exists(caminhoKeyMtls))
                                {
                                    Console.Write("  Informe o caminho da chave privada (.key): ");
                                    caminhoKeyMtls = Console.ReadLine().Trim();
                                    if (string.IsNullOrEmpty(caminhoKeyMtls) || !File.Exists(caminhoKeyMtls)) Console.WriteLine($"  Arquivo nao encontrado: {caminhoKeyMtls}. Informe um caminho valido.");
                                }
                            }
                            Console.WriteLine($"  Chave privada: {Path.GetFullPath(caminhoKeyMtls)}");
                            return (tokenManual, caminhoCertMtls, caminhoKeyMtls);
                        }
                    }

                    // Funcao local para exibir validade
                    void ExibirValidadeRenovacao(string crtPath)
                    {
                        var expiracaoRenov = DateTime.Now.AddDays(365);
                        var inicioRenov = expiracaoRenov.AddDays(-30);
                        var fimRenov = expiracaoRenov.AddDays(-1);
                        Console.WriteLine("\n===============================================");
                        Console.WriteLine("  VALIDADE DO CERTIFICADO RENOVADO             ");
                        Console.WriteLine("===============================================");
                        Console.WriteLine("  Validade: 365 dias");
                        Console.WriteLine($"  Data de expiracao: {expiracaoRenov:dd/MM/yyyy}");
                        Console.WriteLine($"  Periodo de renovacao: de {inicioRenov:dd/MM/yyyy} ate {fimRenov:dd/MM/yyyy}");
                        Console.WriteLine("\n  O processo de renovacao (etapa 4) pode ser realizado a partir de");
                        Console.WriteLine("  30 dias antes da expiracao ate um dia antes da data de expiracao do certificado.");
                        Console.WriteLine("\n  ATENCAO: A responsabilidade de renovar o certificado dentro do prazo");
                        Console.WriteLine("  e exclusivamente sua. Certificados expirados impossibilitam o acesso");
                        Console.WriteLine("  as APIs do Itau e exigem a geracao de um novo certificado do zero.");
                        Log("INFO", $"Certificado renovado: {Path.GetFullPath(crtPath)}");
                        Log("INFO", "Etapa 4 concluida com sucesso");
                        Console.WriteLine("\n>> Etapa 4 concluida. Certificado renovado.");
                    }

                    // Funcao local para oferecer PFX
                    void OferecerPfxRenovacao(string crtPath, string keyPath)
                    {
                        Console.Write("\nDeseja gerar o arquivo PFX (PKCS#12)? O PFX combina o certificado e a chave privada em um unico arquivo protegido por senha. (s/N): ");
                        string respPfx = Console.ReadLine().Trim();
                        if (respPfx.Equals("s", StringComparison.OrdinalIgnoreCase))
                        {
                            Console.Write("  Informe a senha para proteger o PFX: ");
                            string senhaPfx = Console.ReadLine().Trim();
                            if (string.IsNullOrEmpty(senhaPfx))
                            {
                                Console.WriteLine("  Senha nao informada. PFX nao gerado.");
                                Log("WARN", "PFX nao gerado - senha nao informada");
                            }
                            else
                            {
                                string caminhoPfx = Path.Combine(PastaEtapa4, "certificado.pfx");
                                var certPfx = X509Certificate2.CreateFromPemFile(crtPath, keyPath);
                                File.WriteAllBytes(caminhoPfx, certPfx.Export(X509ContentType.Pfx, senhaPfx));
                                Console.WriteLine($"  PFX gerado: {Path.GetFullPath(caminhoPfx)}");
                                Log("INFO", $"PFX gerado: {Path.GetFullPath(caminhoPfx)}");
                            }
                        }
                    }

                    if (opcaoRenov == "A")
                    {
                        Log("INFO", "Opcao A: Renovacao completa (novo CSR e nova chave privada)");
                        Console.WriteLine("\n[PASSO 1] Gerando nova chave privada e novo CSR...");
                        string caminhoClientId4b = GetEstadoStr(estado, "caminhoClientId", Path.Combine(PastaEtapa2, "client_id.txt"));
                        string clientId4b;
                        if (File.Exists(caminhoClientId4b))
                        {
                            clientId4b = File.ReadAllText(caminhoClientId4b).Trim();
                        }
                        else if (estado.ContainsKey("clientId") && !string.IsNullOrEmpty(GetEstadoStr(estado, "clientId", "")))
                        {
                            clientId4b = GetEstadoStr(estado, "clientId", "");
                            Console.WriteLine("  Client ID recuperado do estado salvo.");
                        }
                        else
                        {
                            Console.WriteLine("  Nao foi possivel recuperar o Client ID automaticamente.");
                            Console.WriteLine("  Informe o Client ID manualmente para continuar.");
                            clientId4b = "";
                            while (string.IsNullOrEmpty(clientId4b))
                            {
                                Console.Write("Client ID: ");
                                clientId4b = Console.ReadLine().Trim();
                                if (string.IsNullOrEmpty(clientId4b)) Console.WriteLine("  Campo obrigatorio. Informe o Client ID.");
                            }
                        }
                        Console.WriteLine($"  Client ID: {Mascarar(clientId4b)}");

                        string organizacao4 = "";
                        while (string.IsNullOrEmpty(organizacao4))
                        {
                            Console.Write("Nome da Empresa: ");
                            organizacao4 = Console.ReadLine().Trim();
                            if (string.IsNullOrEmpty(organizacao4)) Console.WriteLine("  Campo obrigatorio. Informe o nome da empresa.");
                        }
                        string cidade4 = "";
                        while (string.IsNullOrEmpty(cidade4))
                        {
                            Console.Write("Cidade: ");
                            cidade4 = Console.ReadLine().Trim();
                            if (string.IsNullOrEmpty(cidade4)) Console.WriteLine("  Campo obrigatorio. Informe a cidade.");
                        }
                        string estadoUF4 = "";
                        while (string.IsNullOrEmpty(estadoUF4))
                        {
                            Console.Write("Estado - UF: ");
                            estadoUF4 = Console.ReadLine().Trim();
                            if (string.IsNullOrEmpty(estadoUF4)) Console.WriteLine("  Campo obrigatorio. Informe o estado (UF).");
                        }
                        string pais4 = "";
                        while (string.IsNullOrEmpty(pais4))
                        {
                            Console.Write("Pais: ");
                            pais4 = Console.ReadLine().Trim();
                            if (string.IsNullOrEmpty(pais4)) Console.WriteLine("  Campo obrigatorio. Informe o pais.");
                        }

                        string caminhoCSR4b = Path.Combine(PastaEtapa4, "ARQUIVO_REQUEST_CERTIFICADO.csr");
                        string caminhoChaveCSR4b = Path.Combine(PastaEtapa4, "ARQUIVO_CHAVE_PRIVADA.key");
                        GerarCSR(clientId4b, organizacao4, cidade4, estadoUF4, pais4, caminhoCSR4b, caminhoChaveCSR4b);
                        Log("INFO", $"Novo CSR gerado: {Path.GetFullPath(caminhoCSR4b)}");
                        Log("INFO", $"Nova chave privada gerada: {Path.GetFullPath(caminhoChaveCSR4b)}");

                        Console.WriteLine("\n[PASSO 2] Obtendo token para renovacao...");
                        var resultadoToken = ObterTokenRenovacao();
                        string tokenSTS = resultadoToken.token;
                        string caminhoCertMtlsA = resultadoToken.certPath;
                        string caminhoKeyMtlsA = resultadoToken.keyPath;

                        Console.WriteLine("\n[PASSO 3] Enviando novo CSR para renovacao...");
                        string caminhoCRT = Path.Combine(PastaEtapa4, "certificado.crt");
                        Log("INFO", $"Token STS: {Mascarar(tokenSTS)}");
                        Log("INFO", $"CSR: {Path.GetFullPath(caminhoCSR4b)}");
                        Log("INFO", "Enviando requisicao para https://sts.itau.com.br/seguranca/v2/certificado/renovacao");
                        RenovarCertificado(tokenSTS, caminhoCSR4b, caminhoCRT, caminhoCertMtlsA, caminhoKeyMtlsA);

                        ExibirValidadeRenovacao(caminhoCRT);
                        Console.WriteLine("\n  O que fazer agora:");
                        Console.WriteLine($"  1. O novo certificado esta salvo na pasta {Path.GetFullPath(PastaEtapa4)}");
                        Console.WriteLine("  2. Substitua o certificado antigo E a chave privada pelos novos na sua aplicacao");
                        Console.WriteLine($"  3. A nova chave privada (.key) esta em {Path.GetFullPath(caminhoChaveCSR4b)}");
                        Console.WriteLine("  4. Teste a geracao de token (etapa 3) para confirmar que o novo certificado esta funcionando");

                        OferecerPfxRenovacao(caminhoCRT, Path.GetFullPath(caminhoChaveCSR4b));
                    }
                    else
                    {
                        Log("INFO", "Opcao B: Renovacao simples (reutiliza CSR existente)");
                        Console.WriteLine("\n[PASSO 1] Localizando CSR existente...");
                        string caminhoCSR = GetEstadoStr(estado, "caminhoCSR", "");
                        if (string.IsNullOrEmpty(caminhoCSR) || !File.Exists(caminhoCSR))
                        {
                            string caminhoCSRPadrao = Path.GetFullPath(Path.Combine(PastaEtapa2, "ARQUIVO_REQUEST_CERTIFICADO.csr"));
                            if (File.Exists(caminhoCSRPadrao))
                            {
                                caminhoCSR = caminhoCSRPadrao;
                            }
                            else
                            {
                                Console.Write("Caminho do arquivo CSR: ");
                                caminhoCSR = Console.ReadLine().Trim();
                                if (string.IsNullOrEmpty(caminhoCSR) || !File.Exists(caminhoCSR)) throw new Exception($"Arquivo CSR nao encontrado: {caminhoCSR}");
                            }
                        }
                        Console.WriteLine($"  CSR encontrado: {Path.GetFullPath(caminhoCSR)}");

                        Console.WriteLine("\n[PASSO 2] Obtendo token para renovacao...");
                        var resultadoTokenB = ObterTokenRenovacao();
                        string tokenSTS = resultadoTokenB.token;
                        string caminhoCertMtlsB = resultadoTokenB.certPath;
                        string caminhoKeyMtlsB = resultadoTokenB.keyPath;

                        Console.WriteLine("\n[PASSO 3] Enviando CSR para renovacao...");
                        string caminhoCRT = Path.Combine(PastaEtapa4, "certificado.crt");
                        Log("INFO", $"Token STS: {Mascarar(tokenSTS)}");
                        Log("INFO", $"CSR: {Path.GetFullPath(caminhoCSR)}");
                        Log("INFO", "Enviando requisicao para https://sts.itau.com.br/seguranca/v2/certificado/renovacao");
                        RenovarCertificado(tokenSTS, caminhoCSR, caminhoCRT, caminhoCertMtlsB, caminhoKeyMtlsB);

                        ExibirValidadeRenovacao(caminhoCRT);
                        Console.WriteLine("\n  O que fazer agora:");
                        Console.WriteLine($"  1. O novo certificado esta salvo na pasta {Path.GetFullPath(PastaEtapa4)}");
                        Console.WriteLine("  2. Substitua o certificado antigo pelo novo na sua aplicacao");
                        Console.WriteLine("  3. A chave privada (.key) da etapa 2 continua sendo a mesma - nao muda na renovacao");
                        Console.WriteLine("  4. Teste a geracao de token (etapa 3) para confirmar que o novo certificado esta funcionando");

                        string caminhoKeyPfx = Path.GetFullPath(Path.Combine(PastaEtapa2, "ARQUIVO_CHAVE_PRIVADA.key"));
                        OferecerPfxRenovacao(caminhoCRT, caminhoKeyPfx);
                    }
                }
                else
                {
                    Console.WriteLine("Opcao invalida.");
                    continue;
                }
            }
            catch (Exception ex)
            {
                string detalheErro = FormatarExcecao(ex);
                Log("ERROR", detalheErro);
                Log("WARN", "Etapa NAO foi marcada como concluida devido ao erro");
                Console.WriteLine($"\n[ERRO] {detalheErro}");
                Console.WriteLine("A etapa NAO foi marcada como concluida. Verifique o erro acima e tente novamente. Caso persista entre em contato com o suporte.");
            }

            Console.Write("\nPressione ENTER para voltar ao menu...");
            Console.ReadLine();
        }

        if (logWriter != null) logWriter.Close();
    }
}
package main

import (
	"bufio"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strconv"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"time"
)

type CertificadoDinamico struct{}

func (c CertificadoDinamico) GerarParDeChaves(caminhoPrivada, caminhoPublica string) (*rsa.PrivateKey, error) {
	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, fmt.Errorf("erro ao gerar chave privada: %v", err)
	}

	privBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
	if err != nil {
		return nil, fmt.Errorf("erro ao serializar chave privada: %v", err)
	}
	privPem := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
	if err := os.WriteFile(caminhoPrivada, privPem, 0600); err != nil {
		return nil, fmt.Errorf("erro ao salvar %s: %v", caminhoPrivada, err)
	}
	absPriv, _ := filepath.Abs(caminhoPrivada)
	fmt.Printf("Chave privada salva em %s\n", absPriv)

	pubBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
	if err != nil {
		return nil, fmt.Errorf("erro ao serializar chave publica: %v", err)
	}
	pubPem := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubBytes})
	if err := os.WriteFile(caminhoPublica, pubPem, 0644); err != nil {
		return nil, fmt.Errorf("erro ao salvar %s: %v", caminhoPublica, err)
	}
	absPub, _ := filepath.Abs(caminhoPublica)
	fmt.Printf("Chave publica salva em %s\n", absPub)

	return privateKey, nil
}

func carregarChavePrivada(caminhoChavePrivada string) (*rsa.PrivateKey, error) {
	pemData, err := os.ReadFile(caminhoChavePrivada)
	if err != nil {
		return nil, fmt.Errorf("erro ao ler %s: %v", caminhoChavePrivada, err)
	}

	block, _ := pem.Decode(pemData)
	if block == nil {
		return nil, fmt.Errorf("nao foi possivel decodificar PEM de %s", caminhoChavePrivada)
	}

	key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("erro ao parsear chave privada: %v", err)
	}

	rsaKey, ok := key.(*rsa.PrivateKey)
	if !ok {
		return nil, fmt.Errorf("chave nao eh RSA")
	}
	return rsaKey, nil
}

func descriptografarRSA(caminhoChavePrivada, dadosCifradosBase64 string) ([]byte, error) {
	privateKey, err := carregarChavePrivada(caminhoChavePrivada)
	if err != nil {
		return nil, err
	}
	dadosCifrados, err := base64.StdEncoding.DecodeString(dadosCifradosBase64)
	if err != nil {
		return nil, fmt.Errorf("erro ao decodificar base64: %v", err)
	}
	return rsa.DecryptPKCS1v15(rand.Reader, privateKey, dadosCifrados)
}

func descriptografarAES(chaveAES []byte, textoCifradoBase64 string) (string, error) {
	textoCifrado, err := base64.StdEncoding.DecodeString(textoCifradoBase64)
	if err != nil {
		return "", fmt.Errorf("erro ao decodificar base64: %v", err)
	}

	block, err := aes.NewCipher(chaveAES)
	if err != nil {
		return "", fmt.Errorf("erro ao criar cipher AES: %v", err)
	}

	iv := make([]byte, aes.BlockSize)
	mode := cipher.NewCBCDecrypter(block, iv)
	textoDecifrado := make([]byte, len(textoCifrado))
	mode.CryptBlocks(textoDecifrado, textoCifrado)

	padding := int(textoDecifrado[len(textoDecifrado)-1])
	textoDecifrado = textoDecifrado[:len(textoDecifrado)-padding]

	return string(textoDecifrado), nil
}

func (c CertificadoDinamico) DescriptografarCredenciais(
	clientIdCifrado, tokenCifrado, chaveSessaoCifrada, caminhoChavePrivada string,
) (string, string, error) {
	fmt.Println("\n=========================================")
	fmt.Println("    Processo de Descriptografia           ")
	fmt.Println("=========================================")

	chaveSessaoDecifrada, err := descriptografarRSA(caminhoChavePrivada, chaveSessaoCifrada)
	if err != nil {
		return "", "", fmt.Errorf("erro ao descriptografar chave de sessao: %v", err)
	}

	clientIdDecifrado, err := descriptografarAES(chaveSessaoDecifrada, clientIdCifrado)
	if err != nil {
		return "", "", fmt.Errorf("erro ao descriptografar client_id: %v", err)
	}
	fmt.Printf("\nClient ID decifrado com a chave de sessao AES:\n[ %s ]\n", mascarar(clientIdDecifrado))

	tokenDecifrado, err := descriptografarAES(chaveSessaoDecifrada, tokenCifrado)
	if err != nil {
		return "", "", fmt.Errorf("erro ao descriptografar token: %v", err)
	}
	fmt.Printf("\nToken decifrado com a chave de sessao AES:\n[ %s ]\n", mascarar(tokenDecifrado))

	return clientIdDecifrado, tokenDecifrado, nil
}

func (c CertificadoDinamico) GerarCSR(
	clientId, organizacao, cidade, estado, pais string,
	caminhoCSR, caminhoChavePrivadaCSR string,
) error {
	csrKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return fmt.Errorf("erro ao gerar chave RSA para CSR: %v", err)
	}

	privBytes, err := x509.MarshalPKCS8PrivateKey(csrKey)
	if err != nil {
		return fmt.Errorf("erro ao serializar chave privada do CSR: %v", err)
	}
	privPem := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
	if err := os.WriteFile(caminhoChavePrivadaCSR, privPem, 0600); err != nil {
		return fmt.Errorf("erro ao salvar %s: %v", caminhoChavePrivadaCSR, err)
	}
	absCSRKey, _ := filepath.Abs(caminhoChavePrivadaCSR)
	fmt.Printf("\nChave privada do CSR salva em %s\n", absCSRKey)

	template := &x509.CertificateRequest{
		Subject: pkix.Name{
			CommonName:         clientId,
			OrganizationalUnit: []string{organizacao},
			Locality:           []string{cidade},
			Province:           []string{estado},
			Country:            []string{pais},
		},
		SignatureAlgorithm: x509.SHA512WithRSA,
	}

	csrDer, err := x509.CreateCertificateRequest(rand.Reader, template, csrKey)
	if err != nil {
		return fmt.Errorf("erro ao criar CSR: %v", err)
	}

	csrPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDer})
	if err := os.WriteFile(caminhoCSR, csrPem, 0644); err != nil {
		return fmt.Errorf("erro ao salvar %s: %v", caminhoCSR, err)
	}
	absCSR, _ := filepath.Abs(caminhoCSR)
	fmt.Printf("CSR salvo em %s\n", absCSR)

	return nil
}

func (c CertificadoDinamico) EnviarCertificado(token, caminhoCSR, caminhoCRT string) (string, error) {
	conteudoCSR, err := os.ReadFile(caminhoCSR)
	if err != nil {
		return "", fmt.Errorf("erro ao ler %s: %v", caminhoCSR, err)
	}

	url := "https://sts.itau.com.br/seguranca/v1/certificado/solicitacao"
	req, err := http.NewRequest("POST", url, strings.NewReader(string(conteudoCSR)))
	if err != nil {
		return "", fmt.Errorf("erro ao criar requisicao: %v", err)
	}
	req.Header.Set("Content-Type", "text/plain")
	req.Header.Set("Authorization", "Bearer "+token)

	registrarLog("INFO", "=== DETALHES DA REQUISICAO HTTP (Enviar CSR) ===")
	registrarLog("INFO", "URL: "+url)
	registrarLog("INFO", "Metodo: POST")
	registrarLog("INFO", "Headers: Content-Type=text/plain, Authorization=Bearer "+mascarar(token))
	csrPreview := string(conteudoCSR)
	if len(csrPreview) > 200 {
		csrPreview = csrPreview[:200] + "..."
	}
	registrarLog("INFO", "Body (CSR): "+csrPreview)
	registrarLog("INFO", fmt.Sprintf("Tamanho do body: %d bytes", len(conteudoCSR)))
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("erro ao enviar CSR: %v", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("erro ao ler resposta: %v", err)
	}

	registrarLog("INFO", "=== DETALHES DA RESPOSTA HTTP (Enviar CSR) ===")
	registrarLog("INFO", fmt.Sprintf("Status code: %d", resp.StatusCode))
	respHeaders := fmt.Sprintf("%v", resp.Header)
	registrarLog("INFO", "Response headers: "+respHeaders)
	corpoResp := string(body)
	if len(corpoResp) > 500 {
		corpoResp = corpoResp[:500]
	}
	registrarLog("INFO", "Response body (primeiros 500 chars): "+corpoResp)

	if resp.StatusCode != http.StatusOK {
		corpoErro := string(body)
		if len(corpoErro) > 500 {
			corpoErro = corpoErro[:500]
		}
		detalhe := detalharErroHTTP(resp.StatusCode)
		return "", fmt.Errorf("erro na geracao do certificado. Status: %d\n  Possivel causa: %s\n  Resposta do servidor: %s", resp.StatusCode, detalhe, corpoErro)
	}

	linhas := strings.Split(string(body), "\n")
	var secret string
	var crtContent strings.Builder

	uuidPattern := regexp.MustCompile(`[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`)
	for _, linha := range linhas {
		if strings.Contains(linha, "Secret:") {
			match := uuidPattern.FindString(linha)
			if match != "" {
				secret = match
			} else {
				secret = linha
			}
		} else {
			crtContent.WriteString(linha + "\n")
		}
	}

	if err := os.WriteFile(caminhoCRT, []byte(crtContent.String()), 0644); err != nil {
		return "", fmt.Errorf("erro ao salvar %s: %v", caminhoCRT, err)
	}

	absCRT, _ := filepath.Abs(caminhoCRT)
	fmt.Printf("\nCertificado salvo em %s\n", absCRT)
	fmt.Printf("Client Secret: %s\n", mascarar(secret))

	return secret, nil
}

func (c CertificadoDinamico) ObterTokenOAuth(clientId, clientSecret, caminhoCRT, caminhoKey string) (string, error) {
	cert, err := tls.LoadX509KeyPair(caminhoCRT, caminhoKey)
	if err != nil {
		return "", fmt.Errorf("erro ao carregar certificado/chave mTLS: %v", err)
	}

	tlsConfig := &tls.Config{
		Certificates:       []tls.Certificate{cert},
		InsecureSkipVerify: true,
	}
	transport := &http.Transport{TLSClientConfig: tlsConfig}
	client := &http.Client{Transport: transport}

	body := fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s",
		url.QueryEscape(clientId), url.QueryEscape(clientSecret))

	registrarLog("INFO", "=== DETALHES DA REQUISICAO HTTP (Token OAuth) ===")
	registrarLog("INFO", "URL: https://sts.itau.com.br/api/oauth/token")
	registrarLog("INFO", "Metodo: POST")
	registrarLog("INFO", "Headers: Content-Type=application/x-www-form-urlencoded")
	registrarLog("INFO", "Certificado mTLS: "+caminhoCRT)
	registrarLog("INFO", "Chave mTLS: "+caminhoKey)
	registrarLog("INFO", "Body: grant_type=client_credentials&client_id="+mascarar(clientId)+"&client_secret="+mascarar(clientSecret))

	req, err := http.NewRequest("POST", "https://sts.itau.com.br/api/oauth/token", strings.NewReader(body))
	if err != nil {
		return "", fmt.Errorf("erro ao criar requisicao: %v", err)
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("erro ao enviar requisicao OAuth: %v", err)
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("erro ao ler resposta: %v", err)
	}

	registrarLog("INFO", "=== DETALHES DA RESPOSTA HTTP (Token OAuth) ===")
	registrarLog("INFO", fmt.Sprintf("Status code: %d", resp.StatusCode))
	respHeaders6 := fmt.Sprintf("%v", resp.Header)
	registrarLog("INFO", "Response headers: "+respHeaders6)
	corpoResp6 := string(respBody)
	if len(corpoResp6) > 500 {
		corpoResp6 = corpoResp6[:500]
	}
	registrarLog("INFO", "Response body (primeiros 500 chars): "+corpoResp6)

	if resp.StatusCode != http.StatusOK {
		corpoErro := string(respBody)
		if len(corpoErro) > 500 {
			corpoErro = corpoErro[:500]
		}
		detalhe := detalharErroHTTP(resp.StatusCode)
		return "", fmt.Errorf("erro ao obter token OAuth. Status: %d\n  Possivel causa: %s\n  Resposta do servidor: %s", resp.StatusCode, detalhe, corpoErro)
	}

	return string(respBody), nil
}

func (c CertificadoDinamico) RenovarCertificado(tokenSTS, caminhoCSR, caminhoCRT, caminhoCertMtls, caminhoKeyMtls string) error {
	conteudoCSR, err := os.ReadFile(caminhoCSR)
	if err != nil {
		return fmt.Errorf("erro ao ler %s: %v", caminhoCSR, err)
	}

	url := "https://sts.itau.com.br/seguranca/v2/certificado/renovacao"
	req, err := http.NewRequest("POST", url, strings.NewReader(string(conteudoCSR)))
	if err != nil {
		return fmt.Errorf("erro ao criar requisicao: %v", err)
	}
	correlationId := generateUUID()
	req.Header.Set("Content-Type", "text/plain")
	req.Header.Set("Authorization", "Bearer "+tokenSTS)
	req.Header.Set("x-itau-correlationID", correlationId)

	registrarLog("INFO", "=== DETALHES DA REQUISICAO HTTP (Renovacao) ===")
	registrarLog("INFO", "URL: "+url)
	registrarLog("INFO", "Metodo: POST")
	registrarLog("INFO", "Headers: Content-Type=text/plain, Authorization=Bearer "+mascarar(tokenSTS)+", x-itau-correlationID="+correlationId)
	csrPreview5 := string(conteudoCSR)
	if len(csrPreview5) > 200 {
		csrPreview5 = csrPreview5[:200] + "..."
	}
	registrarLog("INFO", "Body (CSR): "+csrPreview5)
	registrarLog("INFO", fmt.Sprintf("Tamanho do body: %d bytes", len(conteudoCSR)))

	var client *http.Client
	if caminhoCertMtls != "" && caminhoKeyMtls != "" {
		certTls, err := tls.LoadX509KeyPair(caminhoCertMtls, caminhoKeyMtls)
		if err != nil {
			return fmt.Errorf("erro ao carregar certificado/chave para mTLS: %v", err)
		}
		tlsConfig := &tls.Config{
			Certificates:       []tls.Certificate{certTls},
			InsecureSkipVerify: true,
		}
		transport := &http.Transport{TLSClientConfig: tlsConfig}
		client = &http.Client{Transport: transport}
		registrarLog("INFO", "mTLS cert: "+caminhoCertMtls)
		registrarLog("INFO", "mTLS key: "+caminhoKeyMtls)
	} else {
		client = &http.Client{}
	}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("erro ao enviar CSR para renovacao: %v", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("erro ao ler resposta: %v", err)
	}

	registrarLog("INFO", "=== DETALHES DA RESPOSTA HTTP (Renovacao) ===")
	registrarLog("INFO", fmt.Sprintf("Status code: %d", resp.StatusCode))
	respHeaders5 := fmt.Sprintf("%v", resp.Header)
	registrarLog("INFO", "Response headers: "+respHeaders5)
	corpoResp5 := string(body)
	if len(corpoResp5) > 500 {
		corpoResp5 = corpoResp5[:500]
	}
	registrarLog("INFO", "Response body (primeiros 500 chars): "+corpoResp5)

	if resp.StatusCode != http.StatusOK {
		corpoErro := string(body)
		if len(corpoErro) > 500 {
			corpoErro = corpoErro[:500]
		}
		detalhe := detalharErroHTTP(resp.StatusCode)
		return fmt.Errorf("erro na renovacao do certificado. Status: %d\n  Possivel causa: %s\n  Resposta do servidor: %s", resp.StatusCode, detalhe, corpoErro)
	}

	if err := os.WriteFile(caminhoCRT, body, 0644); err != nil {
		return fmt.Errorf("erro ao salvar %s: %v", caminhoCRT, err)
	}

	absCRTRenov, _ := filepath.Abs(caminhoCRT)
	fmt.Printf("\nCertificado renovado salvo em %s\n", absCRTRenov)
	return nil
}

var (
	_, _arquivoAtual, _, _ = runtime.Caller(0)
	_dirRaiz               = filepath.Join(filepath.Dir(_arquivoAtual), "..") // sobe de go/ para certificado-dinamico/
	pastaSaida             = filepath.Join(_dirRaiz, "output")
	pastaEtapa1            = filepath.Join(pastaSaida, "etapa1")
	pastaEtapa2            = filepath.Join(pastaSaida, "etapa2")
	pastaEtapa4            = filepath.Join(pastaSaida, "etapa4")
	pastaLogs              = filepath.Join(pastaSaida, "logs")
	arquivoEstado          = filepath.Join(pastaSaida, "estado_certificado.json")
)

var logFile *os.File

func generateUUID() string {
	b := make([]byte, 16)
	_, _ = rand.Read(b)
	return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}

func detalharErroHTTP(statusCode int) string {
	switch statusCode {
	case 400:
		return "Requisicao invalida. O CSR pode estar malformado ou os dados enviados estao incorretos."
	case 401:
		return "Nao autorizado. O token pode estar invalido, expirado ou nao foi informado corretamente. Tente executar novamente a partir da etapa 2."
	case 403:
		return "Acesso negado. O token nao tem permissao para esta operacao ou esta expirado. Tente executar novamente a partir da etapa 2 para obter um novo token."
	case 404:
		return "Endpoint nao encontrado. Verifique se a URL do STS Itau esta correta."
	case 500:
		return "Erro interno do servidor STS Itau. Tente novamente mais tarde."
	case 502:
		return "Bad Gateway. O servidor STS Itau esta temporariamente indisponivel. Tente novamente mais tarde."
	case 503:
		return "Servico indisponivel. O servidor STS Itau esta em manutencao. Tente novamente mais tarde."
	default:
		return fmt.Sprintf("Erro HTTP %d. Verifique a documentacao do STS Itau.", statusCode)
	}
}

func mascarar(valor string) string {
	valor = strings.TrimSpace(valor)
	if valor == "" || len(valor) <= 6 {
		return "******"
	}
	return valor[:3] + "******" + valor[len(valor)-3:]
}

func configurarLog() string {
	os.MkdirAll(pastaLogs, 0755)
	timestamp := time.Now().Format("20060102_150405")
	arquivoLog := filepath.Join(pastaLogs, "certificado_dinamico_"+timestamp+".log")
	var err error
	logFile, err = os.OpenFile(arquivoLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Erro ao criar arquivo de log: %v\n", err)
	}
	return arquivoLog
}

func registrarLog(nivel, mensagem string) {
	if logFile == nil {
		return
	}
	ts := time.Now().Format("2006-01-02 15:04:05")
	fmt.Fprintf(logFile, "%s [%s] %s\n", ts, nivel, mensagem)
}

var etapasDesc = map[int]string{
	1: "Gerar par de chaves e exibir chave publica",
	2: "Descriptografar credenciais, gerar e enviar certificado",
	3: "Testar geracao de token",
}

func carregarEstado() map[string]interface{} {
	estado := map[string]interface{}{"etapaConcluida": float64(0)}
	for _, pasta := range []string{pastaSaida, pastaEtapa1, pastaEtapa2, pastaEtapa4, pastaLogs} {
		os.MkdirAll(pasta, 0755)
	}
	data, err := os.ReadFile(arquivoEstado)
	if err != nil {
		return estado
	}
	if err := json.Unmarshal(data, &estado); err != nil {
		return map[string]interface{}{"etapaConcluida": float64(0)}
	}
	return estado
}

func salvarEstado(estado map[string]interface{}) {
	data, err := json.MarshalIndent(estado, "", "  ")
	if err != nil {
		fmt.Fprintf(os.Stderr, "Erro ao serializar estado: %v\n", err)
		return
	}
	os.WriteFile(arquivoEstado, data, 0644)
}

func getEtapaConcluida(estado map[string]interface{}) int {
	if v, ok := estado["etapaConcluida"]; ok {
		switch val := v.(type) {
		case float64:
			return int(val)
		case int:
			return val
		}
	}
	return 0
}

func getEstadoStr(estado map[string]interface{}, chave, padrao string) string {
	if v, ok := estado[chave]; ok {
		if s, ok := v.(string); ok {
			return s
		}
	}
	return padrao
}

func lerLinha(scanner *bufio.Scanner, prompt string) string {
	fmt.Print(prompt)
	scanner.Scan()
	return strings.TrimSpace(scanner.Text())
}

func statusEtapa(n, etapaConcluida int) string {
	if n <= etapaConcluida {
		return "[CONCLUIDA]"
	} else if n == etapaConcluida+1 {
		return "[PROXIMA >>] <------ voce esta aqui"
	}
	return "[PENDENTE]"
}

func exibirMenu(estado map[string]interface{}) int {
	etapaConcluida := getEtapaConcluida(estado)

	fmt.Println("\n=============================================")
	fmt.Println("  Certificado Dinamico Itau - Menu Principal ")
	fmt.Println("=============================================")
	fmt.Printf("\n  Progresso atual: Etapa %d de 3 concluida(s)\n\n", etapaConcluida)

	fmt.Printf("  1. %s %s\n", etapasDesc[1], statusEtapa(1, etapaConcluida))

	fmt.Printf("  2. %s %s\n", etapasDesc[2], statusEtapa(2, etapaConcluida))

	fmt.Printf("  3. %s %s\n", etapasDesc[3], statusEtapa(3, etapaConcluida))

	fmt.Println("  4. Renovar certificado")

	fmt.Println("\n  0. Sair")
	fmt.Println("  9. Recomecar do zero (limpa progresso)")

	if etapaConcluida+1 <= 3 {
		return etapaConcluida + 1
	}
	return 3
}

func maxInt(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func executarEtapa(cert *CertificadoDinamico, scanner *bufio.Scanner, estado map[string]interface{}, escolha int, clientSecretMemoria *string) (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%v", r)
		}
	}()

	switch escolha {
	case 1:
		registrarLog("INFO", "Inicio da Etapa 1: Gerar par de chaves e exibir chave publica")
		os.MkdirAll(pastaEtapa1, 0755)
		fmt.Println("\n[PASSO 1.1] Gerando par de chaves RSA 2048...")
		caminhoPrivada := filepath.Join(pastaEtapa1, "private.pem")
		caminhoPublica := filepath.Join(pastaEtapa1, "public.pem")
		if _, err := cert.GerarParDeChaves(caminhoPrivada, caminhoPublica); err != nil {
			return err
		}

		caminhoAbsPriv, _ := filepath.Abs(caminhoPrivada)
		caminhoAbsPub, _ := filepath.Abs(caminhoPublica)
		registrarLog("INFO", "Chave privada gerada: "+caminhoAbsPriv)
		registrarLog("INFO", "Chave publica gerada: "+caminhoAbsPub)

		fmt.Println("\n[PASSO 1.2] Exibindo chave publica...")
		conteudoChavePublica, err := os.ReadFile(caminhoPublica)
		if err != nil {
			return fmt.Errorf("Arquivo nao encontrado: %s", caminhoPublica)
		}
		fmt.Println("\n=============================================")
		fmt.Println("  Chave Publica Gerada                       ")
		fmt.Println("=============================================")
		fmt.Println(string(conteudoChavePublica))

		estado["etapaConcluida"] = float64(maxInt(getEtapaConcluida(estado), 1))
		estado["caminhoChavePrivada"] = caminhoAbsPriv
		estado["caminhoChavePublica"] = caminhoAbsPub
		salvarEstado(estado)
		registrarLog("INFO", "Etapa 1 concluida com sucesso")
		fmt.Println("\n>> Etapa 1 concluida. Progresso salvo.")
		fmt.Println("\n  O que fazer agora:")
		fmt.Println("  1. Copie o conteudo da chave publica exibido acima")
		fmt.Println("  2. Envie a chave publica para o Analista de operacoes Itau")
		fmt.Println("  3. Aguarde o e-mail do Itau com as credenciais cifradas")
		fmt.Println("  4. Quando receber o e-mail, execute a etapa 2")

	case 2:
		if getEtapaConcluida(estado) < 1 {
			fmt.Println("\n[AVISO] A etapa 1 (gerar chaves) precisa ser concluida antes.")
			registrarLog("WARN", "Tentativa de executar Etapa 2 sem concluir Etapa 1")
			return nil
		}

		registrarLog("INFO", "Inicio da Etapa 2: Descriptografar credenciais, gerar e enviar certificado")
		os.MkdirAll(pastaEtapa2, 0755)

		fmt.Println("\n[PASSO 2.1] Descriptografar credenciais recebidas por e-mail...")
		caminhoChavePrivada := getEstadoStr(estado, "caminhoChavePrivada", "private.pem")
		caminhoChavePrivada, _ = filepath.Abs(caminhoChavePrivada)
		fmt.Printf("  Chave privada: %s\n", caminhoChavePrivada)
		absCaminhoChavePrivada, _ := filepath.Abs(caminhoChavePrivada)
		registrarLog("INFO", "Chave privada utilizada: "+absCaminhoChavePrivada)

		clientIdCifrado := lerLinha(scanner, "Client ID cifrado: ")
		tokenCifrado := lerLinha(scanner, "Token temporario cifrado: ")
		chaveSessaoCifrada := lerLinha(scanner, "Chave de sessao cifrada: ")
		registrarLog("INFO", "Client ID cifrado: "+mascarar(clientIdCifrado))
		registrarLog("INFO", "Token cifrado: "+mascarar(tokenCifrado))
		registrarLog("INFO", "Chave de sessao cifrada: "+mascarar(chaveSessaoCifrada))

		clientId, tokenDecifrado, err := cert.DescriptografarCredenciais(
			clientIdCifrado, tokenCifrado, chaveSessaoCifrada, caminhoChavePrivada)
		if err != nil {
			return err
		}

		caminhoClientId := filepath.Join(pastaEtapa2, "client_id.txt")
		os.WriteFile(caminhoClientId, []byte(clientId), 0644)
		absCaminhoClientId, _ := filepath.Abs(caminhoClientId)
		fmt.Printf("  Client ID salvo em %s\n", absCaminhoClientId)
		registrarLog("INFO", "Client ID decifrado: "+mascarar(clientId))

		caminhoToken := filepath.Join(pastaEtapa2, "token_temporario.txt")
		os.WriteFile(caminhoToken, []byte(tokenDecifrado), 0644)
		absCaminhoToken, _ := filepath.Abs(caminhoToken)
		fmt.Printf("  Token temporario salvo em %s\n", absCaminhoToken)
		registrarLog("INFO", "Token decifrado: "+mascarar(tokenDecifrado))

		fmt.Println("\n[PASSO 2.2] Gerando CSR (Certificate Sign Request)...")
		fmt.Printf("  Client ID: %s\n", mascarar(clientId))

		organizacao := ""
		for organizacao == "" {
			organizacao = lerLinha(scanner, "Nome da Empresa: ")
			if organizacao == "" {
				fmt.Println("  Campo obrigatorio. Informe o nome da empresa.")
			}
		}
		cidade := ""
		for cidade == "" {
			cidade = lerLinha(scanner, "Cidade: ")
			if cidade == "" {
				fmt.Println("  Campo obrigatorio. Informe a cidade.")
			}
		}
		estadoUF := ""
		for estadoUF == "" {
			estadoUF = lerLinha(scanner, "Estado - UF: ")
			if estadoUF == "" {
				fmt.Println("  Campo obrigatorio. Informe o estado (UF).")
			}
		}
		pais := ""
		for pais == "" {
			pais = lerLinha(scanner, "Pais: ")
			if pais == "" {
				fmt.Println("  Campo obrigatorio. Informe o pais.")
			}
		}

		registrarLog("INFO", "Client ID: "+mascarar(clientId))
		registrarLog("INFO", fmt.Sprintf("Organizacao: %s, Cidade: %s, Estado: %s, Pais: %s", organizacao, cidade, estadoUF, pais))

		caminhoCSR := filepath.Join(pastaEtapa2, "ARQUIVO_REQUEST_CERTIFICADO.csr")
		caminhoChaveCSR := filepath.Join(pastaEtapa2, "ARQUIVO_CHAVE_PRIVADA.key")
		if err := cert.GerarCSR(clientId, organizacao, cidade, estadoUF, pais,
			caminhoCSR, caminhoChaveCSR); err != nil {
			return err
		}

		fmt.Println("\n[PASSO 2.3] Enviando CSR ao STS Itau...")
		registrarLog("INFO", "Token: "+mascarar(tokenDecifrado))
		absCSR2, _ := filepath.Abs(caminhoCSR)
		registrarLog("INFO", "CSR: "+absCSR2)
		registrarLog("INFO", "Enviando requisicao para https://sts.itau.com.br/seguranca/v1/certificado/solicitacao")

		caminhoCRT := filepath.Join(pastaEtapa2, "certificado.crt")
		secret, err := cert.EnviarCertificado(tokenDecifrado, caminhoCSR, caminhoCRT)
		if err != nil {
			return err
		}

		fmt.Println("\n=============================================")
		fmt.Println("  Certificado gerado com sucesso!            ")
		fmt.Println("=============================================")
		fmt.Println("Arquivos gerados:")
		absE1Priv, _ := filepath.Abs(filepath.Join(pastaEtapa1, "private.pem"))
		absE1Pub, _ := filepath.Abs(filepath.Join(pastaEtapa1, "public.pem"))
		absE2ClientId, _ := filepath.Abs(filepath.Join(pastaEtapa2, "client_id.txt"))
		absE2Key, _ := filepath.Abs(filepath.Join(pastaEtapa2, "ARQUIVO_CHAVE_PRIVADA.key"))
		absE2CSR, _ := filepath.Abs(filepath.Join(pastaEtapa2, "ARQUIVO_REQUEST_CERTIFICADO.csr"))
		absE2CRT, _ := filepath.Abs(filepath.Join(pastaEtapa2, "certificado.crt"))
		fmt.Printf("  - %s (chave privada para descriptografia)\n", absE1Priv)
		fmt.Printf("  - %s (chave publica)\n", absE1Pub)
		fmt.Printf("  - %s (Client ID)\n", absE2ClientId)
		fmt.Printf("  - %s (chave privada do certificado)\n", absE2Key)
		fmt.Printf("  - %s (CSR)\n", absE2CSR)
		fmt.Printf("  - %s (certificado assinado)\n", absE2CRT)
		fmt.Println("\n==============================================")
		fmt.Println("  ATENCAO - CLIENT SECRET                     ")
		fmt.Println("==============================================")
		fmt.Println("\nO Client Secret e equivalente a uma SENHA. Trate-o com o mesmo cuidado.")
		fmt.Println("O valor do Client Secret sera exibido APENAS NESTE MOMENTO.")
		fmt.Println("Copie e salve em um local seguro. Este valor NAO sera armazenado pelo programa.")
		fmt.Printf("\n  Client Secret: %s\n", secret)
		fmt.Println("\n==============================================")
		fmt.Println("Guarde essas informacoes em local seguro. O Client ID e o Client Secret sao equivalentes a senhas")
		fmt.Println("e serao necessarios para gerar tokens OAuth e consumir as APIs do Itau.")
		fmt.Println("NAO compartilhe essas credenciais. Quem tiver acesso a elas podera acessar as APIs em seu nome.")
		fmt.Println("\nO Itau nao se responsabiliza pelo armazenamento local das credenciais.")
		fmt.Println("A guarda correta e de responsabilidade exclusiva do cliente.")

		expiracaoCert := time.Now().AddDate(0, 0, 365)
		inicioRenovacao := expiracaoCert.AddDate(0, 0, -30)
		fimRenovacao := expiracaoCert.AddDate(0, 0, -1)
		fmt.Println("\n===============================================")
		fmt.Println("  VALIDADE DO CERTIFICADO                      ")
		fmt.Println("===============================================")
		fmt.Println("  Validade: 365 dias")
		fmt.Printf("  Data de expiracao: %s\n", expiracaoCert.Format("02/01/2006"))
			fmt.Printf("  Periodo de renovacao: de %s ate %s\n", inicioRenovacao.Format("02/01/2006"), fimRenovacao.Format("02/01/2006"))
		fmt.Println("\n  O processo de renovacao (etapa 4) pode ser realizado a partir de")
		fmt.Println("  30 dias antes da expiracao ate um dia antes da data de expiracao do certificado.")
		fmt.Println("\n  ATENCAO: A responsabilidade de renovar o certificado dentro do prazo")
		fmt.Println("  e exclusivamente sua. Certificados expirados impossibilitam o acesso")
		fmt.Println("  as APIs do Itau e exigem a geracao de um novo certificado do zero.")

		estado["etapaConcluida"] = float64(maxInt(getEtapaConcluida(estado), 2))
		estado["caminhoClientId"] = absCaminhoClientId
		estado["caminhoChavePrivada"] = caminhoChavePrivada
		absCSR2, _ = filepath.Abs(caminhoCSR)
		estado["caminhoCSR"] = absCSR2
		absCRT2, _ := filepath.Abs(caminhoCRT)
		estado["caminhoCRT"] = absCRT2
		salvarEstado(estado)
		absCRTSalvo, _ := filepath.Abs(caminhoCRT)
		registrarLog("INFO", "Certificado salvo: "+absCRTSalvo)
		registrarLog("INFO", "Client Secret: "+mascarar(secret))
		registrarLog("INFO", "Etapa 2 concluida com sucesso")
		fmt.Println("\n>> Etapa 2 concluida. Progresso salvo.")
		fmt.Println("\n  O que fazer agora:")
		fmt.Println("  1. Guarde o Client ID e o Client Secret em local seguro - eles sao equivalentes a SENHAS")
		absPastaEtapa2, _ := filepath.Abs(pastaEtapa2)
		fmt.Printf("  2. O certificado (.crt) e a chave privada (.key) estao salvos na pasta %s\n", absPastaEtapa2)
		fmt.Println("  3. Execute a etapa 3 para validar que tudo esta funcionando corretamente")
		fmt.Println("  4. Apos validar, utilize o client_id, client_secret, certificado e chave privada na sua aplicacao")
		fmt.Println("\n  LEMBRE-SE: Client ID, Client Secret, certificado e chave privada sao equivalentes a senhas.")
		fmt.Println("  NAO compartilhe, NAO envie por e-mail e NAO deixe exposto em codigo-fonte.")
		fmt.Println("  O Itau NAO se responsabiliza pelo armazenamento local. A guarda e responsabilidade exclusiva do cliente.")
		*clientSecretMemoria = secret

		respPfx2 := lerLinha(scanner, "\nDeseja gerar o arquivo PFX (PKCS#12)? O PFX combina o certificado e a chave privada em um unico arquivo protegido por senha. (s/N): ")
		if strings.EqualFold(strings.TrimSpace(respPfx2), "s") {
			senhaPfx2 := lerLinha(scanner, "  Informe a senha para proteger o PFX: ")
			if strings.TrimSpace(senhaPfx2) == "" {
				fmt.Println("  Senha nao informada. PFX nao gerado.")
				registrarLog("WARN", "PFX nao gerado - senha nao informada")
			} else {
				caminhoPfx2 := filepath.Join(pastaEtapa2, "certificado.pfx")
				caminhoKeyPfx2, _ := filepath.Abs(filepath.Join(pastaEtapa2, "ARQUIVO_CHAVE_PRIVADA.key"))
				cmd := exec.Command("openssl", "pkcs12", "-export", "-out", caminhoPfx2, "-inkey", caminhoKeyPfx2, "-in", caminhoCRT, "-passout", "pass:"+senhaPfx2)
				if err := cmd.Run(); err != nil {
					fmt.Printf("  Erro ao gerar PFX: %v\n", err)
					registrarLog("ERROR", fmt.Sprintf("Erro ao gerar PFX: %v", err))
				} else {
					absPfx2, _ := filepath.Abs(caminhoPfx2)
					fmt.Printf("  PFX gerado: %s\n", absPfx2)
					registrarLog("INFO", "PFX gerado: "+absPfx2)
				}
			}
		}

	case 3:
		if getEtapaConcluida(estado) < 2 {
			fmt.Println("\n[AVISO] A etapa 2 (gerar e enviar certificado) precisa ser concluida antes.")
			registrarLog("WARN", "Tentativa de executar Etapa 3 sem concluir Etapa 2")
			return nil
		}
				registrarLog("INFO", "Inicio da Etapa 3: Testar geracao de token")
				fmt.Println("\n[PASSO 3] Testando geracao de token...")
		caminhoClientId := getEstadoStr(estado, "caminhoClientId", filepath.Join(pastaEtapa2, "client_id.txt"))
		if _, errStat := os.Stat(caminhoClientId); os.IsNotExist(errStat) {
			return fmt.Errorf("Arquivo de client_id nao encontrado: %s. Execute as etapas anteriores.", caminhoClientId)
		}
		clientIdBytes, _ := os.ReadFile(caminhoClientId)
		clientIdOAuth := strings.TrimSpace(string(clientIdBytes))
		var clientSecretOAuth string
		if *clientSecretMemoria != "" {
			clientSecretOAuth = *clientSecretMemoria
			fmt.Printf("  Client Secret: %s (obtido da sessao atual)\n", mascarar(clientSecretOAuth))
		} else {
			clientSecretOAuth = lerLinha(scanner, "  Informe o Client Secret: ")
		}
		caminhoCRTOAuth := getEstadoStr(estado, "caminhoCRT", filepath.Join(pastaEtapa2, "certificado.crt"))
		caminhoKeyOAuth, _ := filepath.Abs(filepath.Join(pastaEtapa2, "ARQUIVO_CHAVE_PRIVADA.key"))

		fmt.Println("\n  Arquivos utilizados na requisicao:")
		absClientId, _ := filepath.Abs(caminhoClientId)
		fmt.Printf("  - %s (Client ID: %s)\n", absClientId, mascarar(clientIdOAuth))
		absCRT, _ := filepath.Abs(caminhoCRTOAuth)
		fmt.Printf("  - %s (certificado assinado)\n", absCRT)
		absKey, _ := filepath.Abs(caminhoKeyOAuth)
		fmt.Printf("  - %s (chave privada do certificado)\n", absKey)

		respostaOAuth, err := cert.ObterTokenOAuth(clientIdOAuth, clientSecretOAuth, caminhoCRTOAuth, caminhoKeyOAuth)
		if err != nil {
			return err
		}
		registrarLog("INFO", "Token OAuth obtido com sucesso")
		registrarLog("INFO", "Etapa 3 concluida com sucesso")

		accessToken := ""
		var dadosOAuth map[string]interface{}
		if err := json.Unmarshal([]byte(respostaOAuth), &dadosOAuth); err == nil {
			if at, ok := dadosOAuth["access_token"].(string); ok {
				accessToken = at
			}
		}

		fmt.Println("\n==============================================")
		fmt.Println("  Credenciais e certificado VALIDADOS!        ")
		fmt.Println("==============================================")
		fmt.Println("\nAccess Token:")
		if accessToken != "" {
			fmt.Println(accessToken)
		} else {
			fmt.Println(respostaOAuth)
		}
		fmt.Println("\n  Validade: 300 segundos (5 minutos)")
		fmt.Println("  Uso: Informe este token no header 'Authorization: Bearer <access_token>' das requisicoes as APIs do Itau.")
		fmt.Println("\nAs credenciais (client_id e client_secret) e o certificado digital estao validados e funcionais.")
		fmt.Println("Voce ja pode utiliza-los em suas integracoes.")
		fmt.Println("\n>> Etapa 3 concluida.")
		fmt.Println("\n  O que fazer agora:")
		fmt.Println("  1. Suas credenciais e certificado estao validados e prontos para uso")
		fmt.Println("  2. Na sua aplicacao, implemente o fluxo OAuth usando:")
		fmt.Println("     - Client ID e Client Secret (da etapa 2)")
		absPastaEtapa2b, _ := filepath.Abs(pastaEtapa2)
		fmt.Printf("     - Certificado .crt e chave privada .key (da pasta %s)\n", absPastaEtapa2b)
		fmt.Println("  3. O token gerado tem validade de 5 minutos - sua aplicacao deve renova-lo periodicamente")
		fmt.Println("  4. Lembre-se de renovar o certificado antes da data de expiracao (etapa 4)")

	case 4:
		registrarLog("INFO", "Inicio da Etapa 4: Renovar certificado")
		os.MkdirAll(pastaEtapa4, 0755)

		caminhoCRTValidacao := getEstadoStr(estado, "caminhoCRT", filepath.Join(pastaEtapa2, "certificado.crt"))
		if _, errStat := os.Stat(caminhoCRTValidacao); errStat == nil {
			certPemBytes, errRead := os.ReadFile(caminhoCRTValidacao)
			if errRead == nil {
				block, _ := pem.Decode(certPemBytes)
				if block != nil {
					certObj, errParse := x509.ParseCertificate(block.Bytes)
					if errParse == nil {
						dataExpiracao := certObj.NotAfter
						agora := time.Now()
						diasRestantes := int(dataExpiracao.Sub(agora).Hours() / 24)
						absCRTVal, _ := filepath.Abs(caminhoCRTValidacao)
						registrarLog("INFO", fmt.Sprintf("Certificado encontrado: %s", absCRTVal))
						registrarLog("INFO", fmt.Sprintf("Data de expiracao: %s | Dias restantes: %d", dataExpiracao.Format("02/01/2006"), diasRestantes))
						fmt.Printf("\n  Certificado encontrado: %s\n", absCRTVal)
						fmt.Printf("  Data de expiracao: %s (%d dias restantes)\n", dataExpiracao.Format("02/01/2006"), diasRestantes)
						if diasRestantes > 30 {
							fmt.Println("\n  [AVISO] O certificado ainda nao esta no periodo de renovacao.")
							fmt.Println("  A renovacao e permitida nos ultimos 30 dias antes da expiracao.")
							fmt.Printf("  Faltam %d dias para o inicio do periodo de renovacao.\n", diasRestantes-30)
							registrarLog("WARN", fmt.Sprintf("Certificado fora do periodo de renovacao. Dias restantes: %d", diasRestantes))
							prosseguir := strings.TrimSpace(lerLinha(scanner, "\n  Deseja prosseguir mesmo assim? (s/N): "))
							if !strings.EqualFold(prosseguir, "s") {
								fmt.Println("  Renovacao cancelada pelo usuario.")
								registrarLog("INFO", "Renovacao cancelada pelo usuario - fora do periodo")
								return nil
							}
							registrarLog("INFO", "Usuario optou por prosseguir com a renovacao fora do periodo")
						} else if diasRestantes < 0 {
							absDias := diasRestantes
							if absDias < 0 {
								absDias = -absDias
							}
							fmt.Printf("\n  [AVISO] O certificado ja esta EXPIRADO ha %d dias.\n", absDias)
							registrarLog("WARN", fmt.Sprintf("Certificado expirado ha %d dias", absDias))
						} else {
							fmt.Println("  Certificado dentro do periodo de renovacao.")
						}
					} else {
						registrarLog("WARN", fmt.Sprintf("Nao foi possivel ler o certificado para validacao: %v", errParse))
						fmt.Println("\n  [AVISO] Nao foi possivel ler o certificado para validar a data de expiracao.")
						fmt.Println("  Prosseguindo com a renovacao...")
					}
				} else {
					registrarLog("WARN", "Nao foi possivel decodificar o PEM do certificado")
					fmt.Println("\n  [AVISO] Nao foi possivel ler o certificado para validar a data de expiracao.")
					fmt.Println("  Prosseguindo com a renovacao...")
				}
			} else {
				registrarLog("WARN", fmt.Sprintf("Nao foi possivel ler o arquivo do certificado: %v", errRead))
				fmt.Println("\n  [AVISO] Nao foi possivel ler o certificado para validar a data de expiracao.")
				fmt.Println("  Prosseguindo com a renovacao...")
			}
		} else {
			absCRTVal, _ := filepath.Abs(caminhoCRTValidacao)
			registrarLog("INFO", "Certificado nao encontrado para validacao de periodo. Prosseguindo com a renovacao.")
			fmt.Printf("\n  [INFO] Certificado nao encontrado em: %s\n", absCRTVal)
			fmt.Println("  Nao foi possivel validar o periodo de renovacao (30 dias antes da expiracao).")
			fmt.Println("  Prosseguindo com a renovacao...")
		}

		fmt.Println("\n=============================================")
		fmt.Println("  Renovacao de Certificado - Escolha a opcao ")
		fmt.Println("=============================================")
		fmt.Println("\n  A. Renovacao completa (gera novo CSR e nova chave privada) - recomendada")
		fmt.Println("     - Gera nova chave privada e novo CSR antes de renovar")
		fmt.Println("     - Recomendado para maior seguranca ou quando a chave pode ter sido comprometida")
		fmt.Println("\n  B. Renovacao simples (reutiliza o CSR existente)")
		fmt.Println("     - Mais rapido, mantem a mesma chave privada do certificado")
		fmt.Println("     - Recomendado quando nao ha necessidade de trocar a chave")

		opcaoRenov := strings.ToUpper(strings.TrimSpace(lerLinha(scanner, "\nEscolha a opcao (A ou B): ")))
		if opcaoRenov != "A" && opcaoRenov != "B" {
			fmt.Println("Opcao invalida. Escolha A ou B.")
			return nil
		}

		obterTokenRenovacao := func() (string, string, string, error) {
			fmt.Println("\n=============================================")
			fmt.Println("  Como deseja obter o token para renovacao?  ")
			fmt.Println("=============================================")
			fmt.Println("  1. Automatico (via mTLS com certificado atual)")
			fmt.Println("  2. Manual (informar token manualmente)")
			opcaoToken := strings.TrimSpace(lerLinha(scanner, "\nEscolha (1 ou 2): "))

				if opcaoToken == "1" {
				registrarLog("INFO", "Obtencao de token automatica via mTLS")
				fmt.Println("\n[PASSO 2.1] Obtendo token automaticamente via mTLS...")
				caminhoClientId := getEstadoStr(estado, "caminhoClientId", filepath.Join(pastaEtapa2, "client_id.txt"))
				var clientId string
				if _, err := os.Stat(caminhoClientId); err == nil {
					clientIdBytes, _ := os.ReadFile(caminhoClientId)
					clientId = strings.TrimSpace(string(clientIdBytes))
					fmt.Printf("  Client ID encontrado: %s\n", mascarar(clientId))
				} else if cidVal := getEstadoStr(estado, "clientId", ""); cidVal != "" {
					clientId = cidVal
					fmt.Printf("  Client ID recuperado do estado salvo: %s\n", mascarar(clientId))
				} else {
					absClientId, _ := filepath.Abs(caminhoClientId)
					fmt.Printf("  [AVISO] Arquivo de Client ID nao encontrado em: %s\n", absClientId)
					for clientId == "" {
						clientId = strings.TrimSpace(lerLinha(scanner, "  Informe o Client ID: "))
						if clientId == "" {
							fmt.Println("  Campo obrigatorio. Informe o Client ID.")
						}
					}
				}
				var clientSecret string
				if *clientSecretMemoria != "" {
					clientSecret = *clientSecretMemoria
					fmt.Printf("  Client Secret: %s (obtido da sessao atual)\n", mascarar(clientSecret))
				} else {
					for clientSecret == "" {
						clientSecret = strings.TrimSpace(lerLinha(scanner, "  Informe o Client Secret: "))
						if clientSecret == "" {
							fmt.Println("  Campo obrigatorio. Informe o Client Secret.")
						}
					}
				}
				caminhoCRTAuth := getEstadoStr(estado, "caminhoCRT", filepath.Join(pastaEtapa2, "certificado.crt"))
				if _, err := os.Stat(caminhoCRTAuth); os.IsNotExist(err) {
					absCRT, _ := filepath.Abs(caminhoCRTAuth)
					fmt.Printf("  [AVISO] Certificado nao encontrado em: %s\n", absCRT)
					for {
						caminhoCRTAuth = strings.TrimSpace(lerLinha(scanner, "  Informe o caminho do certificado (.crt): "))
						if _, err := os.Stat(caminhoCRTAuth); err == nil {
							break
						}
						fmt.Printf("  Arquivo nao encontrado: %s. Informe um caminho valido.\n", caminhoCRTAuth)
					}
				}
				absCRT, _ := filepath.Abs(caminhoCRTAuth)
				fmt.Printf("  Certificado: %s\n", absCRT)
				caminhoKeyAuth, _ := filepath.Abs(filepath.Join(pastaEtapa2, "ARQUIVO_CHAVE_PRIVADA.key"))
				if _, err := os.Stat(caminhoKeyAuth); os.IsNotExist(err) {
					fmt.Printf("  [AVISO] Chave privada nao encontrada em: %s\n", caminhoKeyAuth)
					for {
						caminhoKeyAuth = strings.TrimSpace(lerLinha(scanner, "  Informe o caminho da chave privada (.key): "))
						if _, err := os.Stat(caminhoKeyAuth); err == nil {
							break
						}
						fmt.Printf("  Arquivo nao encontrado: %s. Informe um caminho valido.\n", caminhoKeyAuth)
					}
				}
				absKey, _ := filepath.Abs(caminhoKeyAuth)
				fmt.Printf("  Chave privada: %s\n", absKey)
				respostaToken, err := cert.ObterTokenOAuth(clientId, clientSecret, caminhoCRTAuth, caminhoKeyAuth)
				if err != nil {
					return "", "", "", err
				}
				tokenSTS := ""
				var dadosToken map[string]interface{}
				if err := json.Unmarshal([]byte(respostaToken), &dadosToken); err == nil {
					if at, ok := dadosToken["access_token"].(string); ok {
						tokenSTS = at
					}
				}
				if tokenSTS == "" {
					return "", "", "", fmt.Errorf("Nao foi possivel obter o token automaticamente. Tente a opcao manual.")
				}
				fmt.Printf("  Token obtido com sucesso: %s\n", mascarar(tokenSTS))
				registrarLog("INFO", "Token obtido automaticamente via mTLS: "+mascarar(tokenSTS))
				return tokenSTS, caminhoCRTAuth, caminhoKeyAuth, nil
			}
			registrarLog("INFO", "Obtencao de token manual")
			tokenSTS := lerLinha(scanner, "Token STS para renovacao: ")
			if tokenSTS == "" {
				return "", "", "", fmt.Errorf("Token STS e obrigatorio para renovacao do certificado.")
			}
			fmt.Println("\n  Para a requisicao de renovacao, e necessario o certificado e a chave privada (mTLS).")
			caminhoCertMtls := getEstadoStr(estado, "caminhoCRT", filepath.Join(pastaEtapa2, "certificado.crt"))
			if _, err := os.Stat(caminhoCertMtls); os.IsNotExist(err) {
				absCert, _ := filepath.Abs(caminhoCertMtls)
				fmt.Printf("  [AVISO] Certificado nao encontrado em: %s\n", absCert)
				for {
					caminhoCertMtls = strings.TrimSpace(lerLinha(scanner, "  Informe o caminho do certificado (.crt): "))
					if _, err := os.Stat(caminhoCertMtls); err == nil {
						break
					}
					fmt.Printf("  Arquivo nao encontrado: %s. Informe um caminho valido.\n", caminhoCertMtls)
				}
			}
			absCertMtls, _ := filepath.Abs(caminhoCertMtls)
			fmt.Printf("  Certificado: %s\n", absCertMtls)
			caminhoKeyMtls, _ := filepath.Abs(filepath.Join(pastaEtapa2, "ARQUIVO_CHAVE_PRIVADA.key"))
			if _, err := os.Stat(caminhoKeyMtls); os.IsNotExist(err) {
				fmt.Printf("  [AVISO] Chave privada nao encontrada em: %s\n", caminhoKeyMtls)
				for {
					caminhoKeyMtls = strings.TrimSpace(lerLinha(scanner, "  Informe o caminho da chave privada (.key): "))
					if _, err := os.Stat(caminhoKeyMtls); err == nil {
						break
					}
					fmt.Printf("  Arquivo nao encontrado: %s. Informe um caminho valido.\n", caminhoKeyMtls)
				}
			}
			absKeyMtls, _ := filepath.Abs(caminhoKeyMtls)
			fmt.Printf("  Chave privada: %s\n", absKeyMtls)
			return tokenSTS, caminhoCertMtls, caminhoKeyMtls, nil
		}

		exibirValidadeRenovacao := func(caminhoCRT string) {
			expiracaoRenov := time.Now().AddDate(0, 0, 365)
			inicioRenov := expiracaoRenov.AddDate(0, 0, -30)
			fimRenov := expiracaoRenov.AddDate(0, 0, -1)
			fmt.Println("\n===============================================")
			fmt.Println("  VALIDADE DO CERTIFICADO RENOVADO             ")
			fmt.Println("===============================================")
			fmt.Println("  Validade: 365 dias")
			fmt.Printf("  Data de expiracao: %s\n", expiracaoRenov.Format("02/01/2006"))
				fmt.Printf("  Periodo de renovacao: de %s ate %s\n", inicioRenov.Format("02/01/2006"), fimRenov.Format("02/01/2006"))
				fmt.Println("\n  O processo de renovacao (etapa 4) pode ser realizado a partir de")
				fmt.Println("  30 dias antes da expiracao ate um dia antes da data de expiracao do certificado.")
			fmt.Println("\n  ATENCAO: A responsabilidade de renovar o certificado dentro do prazo")
			fmt.Println("  e exclusivamente sua. Certificados expirados impossibilitam o acesso")
			fmt.Println("  as APIs do Itau e exigem a geracao de um novo certificado do zero.")
			absCRTR, _ := filepath.Abs(caminhoCRT)
			registrarLog("INFO", "Certificado renovado: "+absCRTR)
			registrarLog("INFO", "Etapa 4 concluida com sucesso")
			fmt.Println("\n>> Etapa 4 concluida. Certificado renovado.")
		}

		oferecerPfxRenovacao := func(caminhoCRT, caminhoKey string) {
			respPfx := lerLinha(scanner, "\nDeseja gerar o arquivo PFX (PKCS#12)? O PFX combina o certificado e a chave privada em um unico arquivo protegido por senha. (s/N): ")
			if strings.EqualFold(strings.TrimSpace(respPfx), "s") {
				senhaPfx := lerLinha(scanner, "  Informe a senha para proteger o PFX: ")
				if strings.TrimSpace(senhaPfx) == "" {
					fmt.Println("  Senha nao informada. PFX nao gerado.")
					registrarLog("WARN", "PFX nao gerado - senha nao informada")
				} else {
					caminhoPfx := filepath.Join(pastaEtapa4, "certificado.pfx")
					cmd := exec.Command("openssl", "pkcs12", "-export", "-out", caminhoPfx, "-inkey", caminhoKey, "-in", caminhoCRT, "-passout", "pass:"+senhaPfx)
					if err := cmd.Run(); err != nil {
						fmt.Printf("  Erro ao gerar PFX: %v\n", err)
						registrarLog("ERROR", fmt.Sprintf("Erro ao gerar PFX: %v", err))
					} else {
						absPfx, _ := filepath.Abs(caminhoPfx)
						fmt.Printf("  PFX gerado: %s\n", absPfx)
						registrarLog("INFO", "PFX gerado: "+absPfx)
					}
				}
			}
		}

		if opcaoRenov == "A" {
			registrarLog("INFO", "Opcao A: Renovacao completa (novo CSR e nova chave privada)")
			fmt.Println("\n[PASSO 1] Gerando nova chave privada e novo CSR...")
			caminhoClientId := getEstadoStr(estado, "caminhoClientId", filepath.Join(pastaEtapa2, "client_id.txt"))
				var clientId string
			if _, err := os.Stat(caminhoClientId); err == nil {
				clientIdBytes, _ := os.ReadFile(caminhoClientId)
				clientId = strings.TrimSpace(string(clientIdBytes))
			} else if val := getEstadoStr(estado, "clientId", ""); val != "" {
				clientId = val
				fmt.Println("  Client ID recuperado do estado salvo.")
			} else {
				fmt.Println("  Nao foi possivel recuperar o Client ID automaticamente.")
				fmt.Println("  Informe o Client ID manualmente para continuar.")
				for clientId == "" {
					clientId = strings.TrimSpace(lerLinha(scanner, "Client ID: "))
					if clientId == "" {
						fmt.Println("  Campo obrigatorio. Informe o Client ID.")
					}
				}
			}
			fmt.Printf("  Client ID: %s\n", mascarar(clientId))

			organizacao := ""
			for organizacao == "" {
				organizacao = strings.TrimSpace(lerLinha(scanner, "Nome da Empresa: "))
				if organizacao == "" {
					fmt.Println("  Campo obrigatorio. Informe o nome da empresa.")
				}
			}
			cidade := ""
			for cidade == "" {
				cidade = strings.TrimSpace(lerLinha(scanner, "Cidade: "))
				if cidade == "" {
					fmt.Println("  Campo obrigatorio. Informe a cidade.")
				}
			}
			estadoUF := ""
			for estadoUF == "" {
				estadoUF = strings.TrimSpace(lerLinha(scanner, "Estado - UF: "))
				if estadoUF == "" {
					fmt.Println("  Campo obrigatorio. Informe o estado (UF).")
				}
			}
			pais := ""
			for pais == "" {
				pais = strings.TrimSpace(lerLinha(scanner, "Pais: "))
				if pais == "" {
					fmt.Println("  Campo obrigatorio. Informe o pais.")
				}
			}

			caminhoCSR := filepath.Join(pastaEtapa4, "ARQUIVO_REQUEST_CERTIFICADO.csr")
			caminhoChaveCSR := filepath.Join(pastaEtapa4, "ARQUIVO_CHAVE_PRIVADA.key")
			if err := cert.GerarCSR(clientId, organizacao, cidade, estadoUF, pais, caminhoCSR, caminhoChaveCSR); err != nil {
				return err
			}
			absCSRB, _ := filepath.Abs(caminhoCSR)
			absChaveB, _ := filepath.Abs(caminhoChaveCSR)
			registrarLog("INFO", "Novo CSR gerado: "+absCSRB)
			registrarLog("INFO", "Nova chave privada gerada: "+absChaveB)

			fmt.Println("\n[PASSO 2] Obtendo token para renovacao...")
			tokenSTS, caminhoCertMtls, caminhoKeyMtls, err := obterTokenRenovacao()
			if err != nil {
				return err
			}

			fmt.Println("\n[PASSO 3] Enviando novo CSR para renovacao...")
			caminhoCRT := filepath.Join(pastaEtapa4, "certificado.crt")
			registrarLog("INFO", "Token STS: "+mascarar(tokenSTS))
			registrarLog("INFO", "CSR: "+absCSRB)
			registrarLog("INFO", "Enviando requisicao para https://sts.itau.com.br/seguranca/v2/certificado/renovacao")
			if err := cert.RenovarCertificado(tokenSTS, caminhoCSR, caminhoCRT, caminhoCertMtls, caminhoKeyMtls); err != nil {
				return err
			}

			exibirValidadeRenovacao(caminhoCRT)
			fmt.Println("\n  O que fazer agora:")
			absPastaEtapa4, _ := filepath.Abs(pastaEtapa4)
			fmt.Printf("  1. O novo certificado esta salvo na pasta %s\n", absPastaEtapa4)
			fmt.Println("  2. Substitua o certificado antigo E a chave privada pelos novos na sua aplicacao")
			fmt.Printf("  3. A nova chave privada (.key) esta em %s\n", absChaveB)
			fmt.Println("  4. Teste a geracao de token (etapa 3) para confirmar que o novo certificado esta funcionando")

			oferecerPfxRenovacao(caminhoCRT, absChaveB)

		} else {
			registrarLog("INFO", "Opcao B: Renovacao simples (reutiliza CSR existente)")
			fmt.Println("\n[PASSO 1] Localizando CSR existente...")
			caminhoCSR := getEstadoStr(estado, "caminhoCSR", "")
			if caminhoCSR == "" || func() bool { _, err := os.Stat(caminhoCSR); return os.IsNotExist(err) }() {
				abs, _ := filepath.Abs(filepath.Join(pastaEtapa2, "ARQUIVO_REQUEST_CERTIFICADO.csr"))
				if _, err := os.Stat(abs); err == nil {
					caminhoCSR = abs
				} else {
					caminhoCSR = lerLinha(scanner, "Caminho do arquivo CSR: ")
					if _, err := os.Stat(caminhoCSR); os.IsNotExist(err) {
						return fmt.Errorf("Arquivo CSR nao encontrado: %s", caminhoCSR)
					}
				}
			}
			absCSR, _ := filepath.Abs(caminhoCSR)
			fmt.Printf("  CSR encontrado: %s\n", absCSR)

			fmt.Println("\n[PASSO 2] Obtendo token para renovacao...")
			tokenSTS, caminhoCertMtls, caminhoKeyMtls, err := obterTokenRenovacao()
			if err != nil {
				return err
			}

			fmt.Println("\n[PASSO 3] Enviando CSR para renovacao...")
			caminhoCRT := filepath.Join(pastaEtapa4, "certificado.crt")
			registrarLog("INFO", "Token STS: "+mascarar(tokenSTS))
			registrarLog("INFO", "CSR: "+absCSR)
			registrarLog("INFO", "Enviando requisicao para https://sts.itau.com.br/seguranca/v2/certificado/renovacao")
			if err := cert.RenovarCertificado(tokenSTS, caminhoCSR, caminhoCRT, caminhoCertMtls, caminhoKeyMtls); err != nil {
				return err
			}

			exibirValidadeRenovacao(caminhoCRT)
			fmt.Println("\n  O que fazer agora:")
			absPastaEtapa4, _ := filepath.Abs(pastaEtapa4)
			fmt.Printf("  1. O novo certificado esta salvo na pasta %s\n", absPastaEtapa4)
			fmt.Println("  2. Substitua o certificado antigo pelo novo na sua aplicacao")
			fmt.Println("  3. A chave privada (.key) da etapa 2 continua sendo a mesma - nao muda na renovacao")
			fmt.Println("  4. Teste a geracao de token (etapa 3) para confirmar que o novo certificado esta funcionando")

			caminhoKeyPfx, _ := filepath.Abs(filepath.Join(pastaEtapa2, "ARQUIVO_CHAVE_PRIVADA.key"))
			oferecerPfxRenovacao(caminhoCRT, caminhoKeyPfx)
		}

	default:
		fmt.Println("Opcao invalida.")
	}
	return nil
}

func main() {
	arquivoLog := configurarLog()
	registrarLog("INFO", "Programa iniciado")
	fmt.Printf("  Log desta execucao: %s\n", arquivoLog)
	defer func() {
		if logFile != nil {
			logFile.Close()
		}
	}()

	cert := CertificadoDinamico{}
	scanner := bufio.NewScanner(os.Stdin)
	estado := carregarEstado()
	clientSecretMemoria := ""

	for {
		proxima := exibirMenu(estado)

		escolhaStr := lerLinha(scanner, fmt.Sprintf("\nEscolha a etapa (padrao: %d): ", proxima))
		var escolha int
		if escolhaStr == "" {
			escolha = proxima
		} else {
			var err error
			escolha, err = strconv.Atoi(escolhaStr)
			if err != nil {
				fmt.Println("Opcao invalida.")
				continue
			}
		}

		registrarLog("INFO", fmt.Sprintf("Opcao escolhida pelo usuario: %d", escolha))

		if escolha == 0 {
			registrarLog("INFO", "Programa encerrado pelo usuario")
			fmt.Println("\nSaindo. Seu progresso foi salvo.")
			return
		}

		if escolha == 9 {
			registrarLog("INFO", "Limpeza de progresso e arquivos gerados iniciada")
			for _, pasta := range []string{pastaEtapa1, pastaEtapa2, pastaEtapa4} {
				if info, err := os.Stat(pasta); err == nil && info.IsDir() {
					entries, _ := os.ReadDir(pasta)
					for _, entry := range entries {
						if !entry.IsDir() {
							os.Remove(filepath.Join(pasta, entry.Name()))
						}
					}
					absPasta, _ := filepath.Abs(pasta)
					fmt.Printf("  Arquivos removidos da pasta: %s\n", absPasta)
					registrarLog("INFO", "Arquivos removidos da pasta: "+absPasta)
				}
			}
			if _, err := os.Stat(arquivoEstado); err == nil {
				os.Remove(arquivoEstado)
				absEstado, _ := filepath.Abs(arquivoEstado)
				fmt.Printf("  Removido: %s\n", absEstado)
				registrarLog("INFO", "Removido: "+absEstado)
			}
				fmt.Println("\nProgresso e arquivos gerados removidos.")
			registrarLog("INFO", "Limpeza concluida")
				estado = make(map[string]interface{})
				clientSecretMemoria = ""
				continue
		}

		if err := executarEtapa(&cert, scanner, estado, escolha, &clientSecretMemoria); err != nil {
			registrarLog("ERROR", fmt.Sprintf("%v", err))
			registrarLog("WARN", "Etapa NAO foi marcada como concluida devido ao erro")
			fmt.Printf("\n[ERRO] %v\n", err)
			fmt.Println("A etapa NAO foi marcada como concluida. Verifique o erro acima e tente novamente. Caso persista entre em contato com o suporte.")
		}

		lerLinha(scanner, "\nPressione ENTER para voltar ao menu...")
	}
}
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.security.auth.x500.X500Principal;

public class CertificadoDinamico {

    public static KeyPair gerarParDeChaves(String caminhoPrivada, String caminhoPublica) throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(2048);
        KeyPair keyPair = keyGen.generateKeyPair();

        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        String privateKeyPem = toPem("PRIVATE KEY", privateKeyBytes);
        escreverArquivo(caminhoPrivada, privateKeyPem);
        System.out.println("Chave privada salva em " + new File(caminhoPrivada).getAbsolutePath());

        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        String publicKeyPem = toPem("PUBLIC KEY", publicKeyBytes);
        escreverArquivo(caminhoPublica, publicKeyPem);
        System.out.println("Chave publica salva em " + new File(caminhoPublica).getAbsolutePath());

        return keyPair;
    }

    private static String extrairChaveRsaPem(String tipoChave, String arquivoChavesRsa) throws Exception {
        InputStream is = new FileInputStream(arquivoChavesRsa);
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();
        boolean inKey = false;
        for (String line = br.readLine(); line != null; line = br.readLine()) {
            if (!inKey) {
                if (line.startsWith("-----BEGIN ") && line.endsWith(" " + tipoChave + " KEY-----")) {
                    inKey = true;
                }
            } else {
                if (line.startsWith("-----END ") && line.endsWith(" " + tipoChave + " KEY-----")) {
                    inKey = false;
                    break;
                }
                sb.append(line);
            }
        }
        br.close();
        if (sb.length() == 0) {
            throw new Exception("Chave " + tipoChave + " nao encontrada em " + arquivoChavesRsa);
        }
        return sb.toString();
    }

    
    public static String descriptografiaAes(SecretKey key, String cipherText) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        IvParameterSpec iv = new IvParameterSpec(new byte[16]);
        cipher.init(Cipher.DECRYPT_MODE, key, iv);
        byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText));
        return new String(plainText);
    }

    
    private static byte[] descriptografiaRsa(String caminhoChavePrivada, String dadosCifrados) throws Exception {
        String chavePrivada = extrairChaveRsaPem("PRIVATE", caminhoChavePrivada);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(chavePrivada));
        KeyFactory kf = KeyFactory.getInstance("RSA");
        RSAPrivateKey privateKey = (RSAPrivateKey) kf.generatePrivate(spec);
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(Base64.getDecoder().decode(dadosCifrados));
    }

    
    public static String[] descriptografarCredenciais(
            String clientIdCifrado, String tokenCifrado,
            String chaveSessaoCifrada, String caminhoChavePrivada) throws Exception {
        System.out.println("\n=========================================");
        System.out.println("    Processo de Descriptografia           ");
        System.out.println("=========================================");

        byte[] chaveSessaoDecifrada = descriptografiaRsa(caminhoChavePrivada, chaveSessaoCifrada);
        SecretKey chaveSessao = new SecretKeySpec(chaveSessaoDecifrada, 0, chaveSessaoDecifrada.length, "AES");

        String clientIdDecifrado = descriptografiaAes(chaveSessao, clientIdCifrado);
        System.out.println("\nClient ID decifrado com a chave de sessao AES:\n[ " + mascarar(clientIdDecifrado) + " ]");

        String tokenDecifrado = descriptografiaAes(chaveSessao, tokenCifrado);
        System.out.println("\nToken decifrado com a chave de sessao AES:\n[ " + mascarar(tokenDecifrado) + " ]");

        return new String[]{clientIdDecifrado, tokenDecifrado};
    }

    public static void gerarCSR(String clientId, String organizacao, String cidade,
                                 String estado, String pais,
                                 String caminhoCSR, String caminhoChavePrivadaCSR) throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(2048);
        KeyPair keyPair = keyGen.generateKeyPair();

        String privateKeyPem = toPem("PRIVATE KEY", keyPair.getPrivate().getEncoded());
        escreverArquivo(caminhoChavePrivadaCSR, privateKeyPem);
        System.out.println("Chave privada do CSR salva em " + new File(caminhoChavePrivadaCSR).getAbsolutePath());

        X500Principal subject = new X500Principal(
                "CN=" + clientId + ", OU=" + organizacao + ", L=" + cidade + ", ST=" + estado + ", C=" + pais);

        byte[] version = derEncode(0x02, new byte[]{0x00});
        byte[] subjectDer = subject.getEncoded();
        byte[] publicKeyInfo = keyPair.getPublic().getEncoded();
        byte[] attributes = new byte[]{(byte) 0xA0, 0x00};

        byte[] certReqInfo = derSequence(version, subjectDer, publicKeyInfo, attributes);

        Signature sig = Signature.getInstance("SHA512withRSA");
        sig.initSign(keyPair.getPrivate());
        sig.update(certReqInfo);
        byte[] signatureBytes = sig.sign();

        byte[] sha512WithRSA = {
                0x30, 0x0D, 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86,
                (byte) 0xF7, 0x0D, 0x01, 0x01, 0x0D, 0x05, 0x00
        };

        byte[] sigBitString = derBitString(signatureBytes);
        byte[] csrDer = derSequence(certReqInfo, sha512WithRSA, sigBitString);

        String csrBase64 = Base64.getMimeEncoder(64, "\n".getBytes()).encodeToString(csrDer);
        String csrPem = "-----BEGIN CERTIFICATE REQUEST-----\n" + csrBase64 + "\n-----END CERTIFICATE REQUEST-----\n";

        escreverArquivo(caminhoCSR, csrPem);
        System.out.println("CSR salvo em " + new File(caminhoCSR).getAbsolutePath());
    }

    public static String enviarCertificado(String token, String caminhoCSR, String caminhoCRT) throws Exception {
        HttpURLConnection httpConnection = null;
        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(new KeyManager[0], new TrustManager[]{new DefaultTrustManager()}, new SecureRandom());
        SSLContext.setDefault(ctx);

        try {
            String conteudoCSR = new String(Files.readAllBytes(Paths.get(caminhoCSR)), StandardCharsets.UTF_8);

            URL targetUrl = URI.create("https://sts.itau.com.br/seguranca/v1/certificado/solicitacao").toURL();
            httpConnection = (HttpURLConnection) targetUrl.openConnection();
            httpConnection.setDoOutput(true);
            httpConnection.setRequestMethod("POST");
            httpConnection.setRequestProperty("Content-Type", "text/plain");
            httpConnection.setRequestProperty("Authorization", "Bearer " + token);

            log("INFO", "=== DETALHES DA REQUISICAO HTTP (Enviar CSR) ===");
            log("INFO", "URL: https://sts.itau.com.br/seguranca/v1/certificado/solicitacao");
            log("INFO", "Metodo: POST");
            log("INFO", "Headers: Content-Type=text/plain, Authorization=Bearer " + mascarar(token));
            String csrPreview = conteudoCSR.length() > 200 ? conteudoCSR.substring(0, 200) + "..." : conteudoCSR;
            log("INFO", "Body (CSR): " + csrPreview);
            log("INFO", "Tamanho do body: " + conteudoCSR.length() + " bytes");
            DataOutputStream wr = new DataOutputStream(httpConnection.getOutputStream());
            wr.writeBytes(conteudoCSR);
            wr.flush();
            wr.close();

            int statusCode = httpConnection.getResponseCode();
            log("INFO", "=== DETALHES DA RESPOSTA HTTP (Enviar CSR) ===");
            log("INFO", "Status code: " + statusCode);
            StringBuilder respHeaders = new StringBuilder();
            for (Map.Entry<String, java.util.List<String>> entry : httpConnection.getHeaderFields().entrySet()) {
                respHeaders.append(entry.getKey()).append("=").append(String.join(", ", entry.getValue())).append("; ");
            }
            log("INFO", "Response headers: " + respHeaders.toString());

            if (statusCode != HttpURLConnection.HTTP_OK) {
                String corpoErro = "";
                try {
                    java.io.InputStream errorStream = httpConnection.getErrorStream();
                    if (errorStream != null) {
                        corpoErro = new String(errorStream.readAllBytes(), StandardCharsets.UTF_8);
                        if (corpoErro.length() > 500) corpoErro = corpoErro.substring(0, 500);
                    }
                } catch (Exception ignored) {}
                log("INFO", "Response body (erro): " + corpoErro);
                String detalhe = detalharErroHttp(statusCode);
                throw new Exception("Erro na geracao do certificado. Status: " + statusCode
                    + "\n  Possivel causa: " + detalhe
                    + "\n  Resposta do servidor: " + corpoErro);
            }

            StringBuilder crt = new StringBuilder();
            try (Reader reader = new BufferedReader(new InputStreamReader(
                    httpConnection.getInputStream(), StandardCharsets.UTF_8))) {
                int c;
                while ((c = reader.read()) != -1) {
                    if (c != 1) crt.append((char) c);
                }
            }

            String resposta = crt.toString();
            String[] linhas = resposta.split("\n");
            StringBuilder crtContent = new StringBuilder();
            String secret = "";

            Pattern uuidPattern = Pattern.compile("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}");
            for (String linha : linhas) {
                if (linha.contains("Secret:")) {
                    Matcher matcher = uuidPattern.matcher(linha);
                    secret = matcher.find() ? matcher.group() : linha;
                } else {
                    crtContent.append(linha).append("\n");
                }
            }

            escreverArquivo(caminhoCRT, crtContent.toString());
            System.out.println("\nCertificado salvo em " + new File(caminhoCRT).getAbsolutePath());
            System.out.println("Client Secret: " + mascarar(secret));

            return secret;
        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
        }
    }

    public static String obterTokenOAuth(String clientId, String clientSecret, String caminhoCRT, String caminhoKey) throws Exception {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert;
        try (FileInputStream fis = new FileInputStream(caminhoCRT)) {
            cert = (X509Certificate) cf.generateCertificate(fis);
        }

        String keyPem = new String(Files.readAllBytes(Paths.get(caminhoKey)), StandardCharsets.UTF_8);
        String keyBase64 = keyPem
            .replace("-----BEGIN PRIVATE KEY-----", "")
            .replace("-----END PRIVATE KEY-----", "")
            .replace("-----BEGIN RSA PRIVATE KEY-----", "")
            .replace("-----END RSA PRIVATE KEY-----", "")
            .replaceAll("\\s", "");
        byte[] keyBytes = Base64.getDecoder().decode(keyBase64);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = kf.generatePrivate(keySpec);

        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(null, "changeit".toCharArray());
        ks.setKeyEntry("client", privateKey, "changeit".toCharArray(), new java.security.cert.Certificate[]{cert});

        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, "changeit".toCharArray());

        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(kmf.getKeyManagers(), new TrustManager[]{new DefaultTrustManager()}, new SecureRandom());

        String body = "grant_type=client_credentials&client_id=" + java.net.URLEncoder.encode(clientId, "UTF-8")
                + "&client_secret=" + java.net.URLEncoder.encode(clientSecret, "UTF-8");

        URL targetUrl = URI.create("https://sts.itau.com.br/api/oauth/token").toURL();
        HttpsURLConnection conn = (HttpsURLConnection) targetUrl.openConnection();
        conn.setSSLSocketFactory(ctx.getSocketFactory());
        conn.setDoOutput(true);
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

        log("INFO", "=== DETALHES DA REQUISICAO HTTP (Token OAuth) ===");
        log("INFO", "URL: https://sts.itau.com.br/api/oauth/token");
        log("INFO", "Metodo: POST");
        log("INFO", "Headers: Content-Type=application/x-www-form-urlencoded");
        log("INFO", "Certificado mTLS: " + caminhoCRT);
        log("INFO", "Chave mTLS: " + caminhoKey);
        log("INFO", "Body: grant_type=client_credentials&client_id=" + mascarar(clientId) + "&client_secret=" + mascarar(clientSecret));

        try (DataOutputStream wr = new DataOutputStream(conn.getOutputStream())) {
            wr.writeBytes(body);
            wr.flush();
        }

        int statusCode = conn.getResponseCode();
        log("INFO", "=== DETALHES DA RESPOSTA HTTP (Token OAuth) ===");
        log("INFO", "Status code: " + statusCode);

        if (statusCode != HttpURLConnection.HTTP_OK) {
            String corpoErro = "";
            try {
                java.io.InputStream errorStream = conn.getErrorStream();
                if (errorStream != null) {
                    corpoErro = new String(errorStream.readAllBytes(), StandardCharsets.UTF_8);
                    if (corpoErro.length() > 500) corpoErro = corpoErro.substring(0, 500);
                }
            } catch (Exception ignored) {}
            log("INFO", "Response body (erro): " + corpoErro);
            String detalhe = detalharErroHttp(statusCode);
            throw new Exception("Erro ao obter token OAuth. Status: " + statusCode
                + "\n  Possivel causa: " + detalhe
                + "\n  Resposta do servidor: " + corpoErro);
        }

        String resposta;
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) sb.append(line);
            resposta = sb.toString();
        }
        log("INFO", "Response body: " + resposta.substring(0, Math.min(resposta.length(), 200)));

        conn.disconnect();
        return resposta;
    }

    public static String renovarCertificado(String tokenSTS, String caminhoCSR, String caminhoCRT, String caminhoCertMtls, String caminhoKeyMtls) throws Exception {
        HttpURLConnection httpConnection = null;
        SSLContext ctx;
        if (caminhoCertMtls != null && !caminhoCertMtls.isEmpty() && caminhoKeyMtls != null && !caminhoKeyMtls.isEmpty()) {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            X509Certificate cert;
            try (FileInputStream fis = new FileInputStream(caminhoCertMtls)) {
                cert = (X509Certificate) cf.generateCertificate(fis);
            }
            String keyPem = new String(Files.readAllBytes(Paths.get(caminhoKeyMtls)), StandardCharsets.UTF_8);
            String keyBase64Mtls = keyPem
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replace("-----BEGIN RSA PRIVATE KEY-----", "")
                .replace("-----END RSA PRIVATE KEY-----", "")
                .replaceAll("\\s", "");
            byte[] keyBytes = Base64.getDecoder().decode(keyBase64Mtls);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = kf.generatePrivate(keySpec);
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(null, "changeit".toCharArray());
            ks.setKeyEntry("client", privateKey, "changeit".toCharArray(), new java.security.cert.Certificate[]{cert});
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(ks, "changeit".toCharArray());
            ctx = SSLContext.getInstance("TLS");
            ctx.init(kmf.getKeyManagers(), new TrustManager[]{new DefaultTrustManager()}, new SecureRandom());
            log("INFO", "mTLS cert: " + caminhoCertMtls);
            log("INFO", "mTLS key: " + caminhoKeyMtls);
        } else {
            ctx = SSLContext.getInstance("TLS");
            ctx.init(new KeyManager[0], new TrustManager[]{new DefaultTrustManager()}, new SecureRandom());
        }

        try {
            String conteudoCSR = new String(Files.readAllBytes(Paths.get(caminhoCSR)), StandardCharsets.UTF_8);

            URL targetUrl = URI.create("https://sts.itau.com.br/seguranca/v2/certificado/renovacao").toURL();
            HttpsURLConnection httpsConn = (HttpsURLConnection) targetUrl.openConnection();
            httpsConn.setSSLSocketFactory(ctx.getSocketFactory());
            httpConnection = httpsConn;
            httpConnection.setDoOutput(true);
            httpConnection.setRequestMethod("POST");
            String correlationId = java.util.UUID.randomUUID().toString();
            httpConnection.setRequestProperty("Content-Type", "text/plain");
            httpConnection.setRequestProperty("Authorization", "Bearer " + tokenSTS);
            httpConnection.setRequestProperty("x-itau-correlationID", correlationId);

            log("INFO", "=== DETALHES DA REQUISICAO HTTP (Renovacao) ===");
            log("INFO", "URL: https://sts.itau.com.br/seguranca/v2/certificado/renovacao");
            log("INFO", "Metodo: POST");
            log("INFO", "Headers: Content-Type=text/plain, Authorization=Bearer " + mascarar(tokenSTS) + ", x-itau-correlationID=" + correlationId);
            String csrPreview5 = conteudoCSR.length() > 200 ? conteudoCSR.substring(0, 200) + "..." : conteudoCSR;
            log("INFO", "Body (CSR): " + csrPreview5);
            log("INFO", "Tamanho do body: " + conteudoCSR.length() + " bytes");
            DataOutputStream wr = new DataOutputStream(httpConnection.getOutputStream());
            wr.writeBytes(conteudoCSR);
            wr.flush();
            wr.close();

            int statusCode = httpConnection.getResponseCode();
            log("INFO", "=== DETALHES DA RESPOSTA HTTP (Renovacao) ===");
            log("INFO", "Status code: " + statusCode);
            StringBuilder respHeaders5 = new StringBuilder();
            for (Map.Entry<String, java.util.List<String>> entry : httpConnection.getHeaderFields().entrySet()) {
                respHeaders5.append(entry.getKey()).append("=").append(String.join(", ", entry.getValue())).append("; ");
            }
            log("INFO", "Response headers: " + respHeaders5.toString());

            if (statusCode != HttpURLConnection.HTTP_OK) {
                String corpoErro = "";
                try {
                    java.io.InputStream errorStream = httpConnection.getErrorStream();
                    if (errorStream != null) {
                        corpoErro = new String(errorStream.readAllBytes(), StandardCharsets.UTF_8);
                        if (corpoErro.length() > 500) corpoErro = corpoErro.substring(0, 500);
                    }
                } catch (Exception ignored) {}
                log("INFO", "Response body (erro): " + corpoErro);
                String detalhe = detalharErroHttp(statusCode);
                throw new Exception("Erro na renovacao do certificado. Status: " + statusCode
                    + "\n  Possivel causa: " + detalhe
                    + "\n  Resposta do servidor: " + corpoErro);
            }

            StringBuilder response = new StringBuilder();
            try (Reader reader = new BufferedReader(new InputStreamReader(
                    httpConnection.getInputStream(), StandardCharsets.UTF_8))) {
                int c;
                while ((c = reader.read()) != -1) {
                    response.append((char) c);
                }
            }

            escreverArquivo(caminhoCRT, response.toString());
            System.out.println("\nCertificado renovado salvo em " + new File(caminhoCRT).getAbsolutePath());

            return response.toString();
        } finally {
            if (httpConnection != null) {
                httpConnection.disconnect();
            }
        }
    }

    private static String detalharErroHttp(int statusCode) {
        switch (statusCode) {
            case 400: return "Requisicao invalida. O CSR pode estar malformado ou os dados enviados estao incorretos.";
            case 401: return "Nao autorizado. O token pode estar invalido, expirado ou nao foi informado corretamente. Tente executar novamente a partir da etapa 2.";
            case 403: return "Acesso negado. O token nao tem permissao para esta operacao ou esta expirado. Tente executar novamente a partir da etapa 2 para obter um novo token.";
            case 404: return "Endpoint nao encontrado. Verifique se a URL do STS Itau esta correta.";
            case 500: return "Erro interno do servidor STS Itau. Tente novamente mais tarde.";
            case 502: return "Bad Gateway. O servidor STS Itau esta temporariamente indisponivel. Tente novamente mais tarde.";
            case 503: return "Servico indisponivel. O servidor STS Itau esta em manutencao. Tente novamente mais tarde.";
            default: return "Erro HTTP " + statusCode + ". Verifique a documentacao do STS Itau.";
        }
    }

    private static String toPem(String type, byte[] derBytes) {
        String base64 = Base64.getMimeEncoder(64, "\n".getBytes()).encodeToString(derBytes);
        return "-----BEGIN " + type + "-----\n" + base64 + "\n-----END " + type + "-----\n";
    }

    private static void escreverArquivo(String filename, String content) throws IOException {
        try (FileWriter writer = new FileWriter(filename)) {
            writer.write(content);
        }
    }

    private static byte[] derEncode(int tag, byte[] content) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        baos.write(tag);
        int len = content.length;
        if (len < 128) {
            baos.write(len);
        } else if (len < 256) {
            baos.write(0x81);
            baos.write(len);
        } else {
            baos.write(0x82);
            baos.write(len >> 8);
            baos.write(len & 0xFF);
        }
        baos.write(content, 0, content.length);
        return baos.toByteArray();
    }

    private static byte[] derSequence(byte[]... elements) {
        ByteArrayOutputStream content = new ByteArrayOutputStream();
        for (byte[] e : elements) {
            content.write(e, 0, e.length);
        }
        return derEncode(0x30, content.toByteArray());
    }

    private static byte[] derBitString(byte[] data) {
        byte[] content = new byte[data.length + 1];
        content[0] = 0x00;
        System.arraycopy(data, 0, content, 1, data.length);
        return derEncode(0x03, content);
    }

    private static void gerarPFX(String caminhoCRT, String caminhoKey, String caminhoPFX, String senha) throws Exception {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert;
        try (FileInputStream fis = new FileInputStream(caminhoCRT)) {
            cert = (X509Certificate) cf.generateCertificate(fis);
        }
        String keyPem = new String(Files.readAllBytes(Paths.get(caminhoKey)), StandardCharsets.UTF_8);
        String keyBase64 = keyPem.replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replace("-----BEGIN RSA PRIVATE KEY-----", "")
                .replace("-----END RSA PRIVATE KEY-----", "")
                .replaceAll("\\s", "");
        byte[] keyBytes = Base64.getDecoder().decode(keyBase64);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = kf.generatePrivate(spec);
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(null, null);
        ks.setKeyEntry("certificado", privateKey, senha.toCharArray(), new java.security.cert.Certificate[]{cert});
        new File(caminhoPFX).getParentFile().mkdirs();
        try (FileOutputStream fos = new FileOutputStream(caminhoPFX)) {
            ks.store(fos, senha.toCharArray());
        }
    }

    private static class DefaultTrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {}
        @Override
        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {}
        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; }
    }

    private static final String PASTA_SAIDA;
    private static final String PASTA_ETAPA1;
    private static final String PASTA_ETAPA2;
    private static final String PASTA_ETAPA4;
    private static final String PASTA_LOGS;
    private static final String ARQUIVO_ESTADO;

    static {
        String dirRaiz;
        try {
            File codeSource = new File(CertificadoDinamico.class
                .getProtectionDomain().getCodeSource().getLocation().toURI());
            // Quando executado a partir de .class, codeSource e a pasta java/; quando em .jar, e o proprio arquivo.
            File pastaJava = codeSource.isDirectory() ? codeSource : codeSource.getParentFile();
            // Sobe um nivel: de java/ para certificado-dinamico/
            File raiz = pastaJava.getParentFile();
            dirRaiz = (raiz != null) ? raiz.getAbsolutePath() : "..";
        } catch (Exception e) {
            dirRaiz = ".."; // fallback: funciona se executado a partir da pasta java/
        }
        PASTA_SAIDA    = dirRaiz + File.separator + "output";
        PASTA_ETAPA1   = PASTA_SAIDA + File.separator + "etapa1";
        PASTA_ETAPA2   = PASTA_SAIDA + File.separator + "etapa2";
        PASTA_ETAPA4   = PASTA_SAIDA + File.separator + "etapa4";
        PASTA_LOGS     = PASTA_SAIDA + File.separator + "logs";
        ARQUIVO_ESTADO = PASTA_SAIDA + File.separator + "estado_certificado.json";
    }

    private static PrintWriter logWriter = null;

    private static String mascarar(String valor) {
        if (valor == null || valor.trim().isEmpty()) return "******";
        valor = valor.trim();
        if (valor.length() <= 6) return "******";
        return valor.substring(0, 3) + "******" + valor.substring(valor.length() - 3);
    }

    private static String configurarLog() {
        new File(PASTA_LOGS).mkdirs();
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
        String arquivoLog = PASTA_LOGS + File.separator + "certificado_dinamico_" + timestamp + ".log";
        try {
            logWriter = new PrintWriter(new FileWriter(arquivoLog, true), true);
        } catch (IOException e) {
            System.err.println("Erro ao criar arquivo de log: " + e.getMessage());
        }
        return arquivoLog;
    }

    private static void log(String nivel, String mensagem) {
        if (logWriter == null) return;
        String ts = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        logWriter.println(ts + " [" + nivel + "] " + mensagem);
    }

    private static final String[] ETAPAS_DESC = {
            "",
            "Gerar par de chaves e exibir chave publica",
            "Descriptografar credenciais, gerar e enviar certificado",
            "Testar geracao de token",
            "Renovar certificado"
    };

    // Chaves cujo valor deve ser serializado como numero JSON (sem aspas).
    // Demais chaves sao tratadas como strings.
    private static final java.util.Set<String> CHAVES_NUMERICAS_ESTADO =
            new java.util.HashSet<>(java.util.Arrays.asList("etapaConcluida"));

    private static Map<String, String> carregarEstado() {
        Map<String, String> estado = new LinkedHashMap<>();
        estado.put("etapaConcluida", "0");
        for (String pasta : new String[]{PASTA_SAIDA, PASTA_ETAPA1, PASTA_ETAPA2, PASTA_ETAPA4, PASTA_LOGS}) {
            new File(pasta).mkdirs();
        }
        File f = new File(ARQUIVO_ESTADO);
        if (f.exists()) {
            try {
                String conteudo = new String(Files.readAllBytes(f.toPath()), StandardCharsets.UTF_8);
                Map<String, String> parsed = parseJsonObject(conteudo);
                if (parsed != null) {
                    estado.putAll(parsed);
                }
            } catch (Exception e) {
            }
        }
        return estado;
    }

    private static void salvarEstado(Map<String, String> estado) {
        StringBuilder sb = new StringBuilder();
        sb.append("{\n");
        int i = 0;
        int total = estado.size();
        for (Map.Entry<String, String> entry : estado.entrySet()) {
            String chave = entry.getKey();
            String valor = entry.getValue() == null ? "" : entry.getValue();
            sb.append("  \"").append(escaparJson(chave)).append("\": ");
            if (CHAVES_NUMERICAS_ESTADO.contains(chave) && valor.matches("-?\\d+")) {
                sb.append(valor);
            } else {
                sb.append("\"").append(escaparJson(valor)).append("\"");
            }
            if (i < total - 1) sb.append(",");
            sb.append("\n");
            i++;
        }
        sb.append("}");
        try {
            escreverArquivo(ARQUIVO_ESTADO, sb.toString());
        } catch (IOException e) {
            System.err.println("Erro ao salvar estado: " + e.getMessage());
        }
    }

    // Escapa caracteres especiais para producao de string JSON valida.
    private static String escaparJson(String s) {
        StringBuilder out = new StringBuilder(s.length() + 2);
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            switch (c) {
                case '\\': out.append("\\\\"); break;
                case '"':  out.append("\\\""); break;
                case '\n': out.append("\\n"); break;
                case '\r': out.append("\\r"); break;
                case '\t': out.append("\\t"); break;
                case '\b': out.append("\\b"); break;
                case '\f': out.append("\\f"); break;
                default:
                    if (c < 0x20) {
                        out.append(String.format("\\u%04x", (int) c));
                    } else {
                        out.append(c);
                    }
            }
        }
        return out.toString();
    }

    // Parser minimo de objeto JSON com chaves string e valores string/numero/boolean/null.
    // Suporta valores tipados como numero (sem aspas) e os mantem como string para uso interno.
    private static Map<String, String> parseJsonObject(String json) {
        if (json == null) return null;
        int[] pos = {0};
        pularEspacos(json, pos);
        if (pos[0] >= json.length() || json.charAt(pos[0]) != '{') return null;
        pos[0]++;
        Map<String, String> resultado = new LinkedHashMap<>();
        pularEspacos(json, pos);
        if (pos[0] < json.length() && json.charAt(pos[0]) == '}') {
            return resultado;
        }
        while (pos[0] < json.length()) {
            pularEspacos(json, pos);
            String chave = lerStringJson(json, pos);
            if (chave == null) return resultado;
            pularEspacos(json, pos);
            if (pos[0] >= json.length() || json.charAt(pos[0]) != ':') return resultado;
            pos[0]++;
            pularEspacos(json, pos);
            String valor = lerValorJson(json, pos);
            if (valor == null) valor = "";
            resultado.put(chave, valor);
            pularEspacos(json, pos);
            if (pos[0] >= json.length()) break;
            char c = json.charAt(pos[0]);
            if (c == ',') {
                pos[0]++;
                continue;
            }
            if (c == '}') break;
            pos[0]++;
        }
        return resultado;
    }

    private static void pularEspacos(String s, int[] pos) {
        while (pos[0] < s.length() && Character.isWhitespace(s.charAt(pos[0]))) {
            pos[0]++;
        }
    }

    private static String lerStringJson(String s, int[] pos) {
        if (pos[0] >= s.length() || s.charAt(pos[0]) != '"') return null;
        pos[0]++;
        StringBuilder sb = new StringBuilder();
        while (pos[0] < s.length()) {
            char c = s.charAt(pos[0]);
            if (c == '"') {
                pos[0]++;
                return sb.toString();
            }
            if (c == '\\' && pos[0] + 1 < s.length()) {
                char n = s.charAt(pos[0] + 1);
                switch (n) {
                    case '"':  sb.append('"'); pos[0] += 2; break;
                    case '\\': sb.append('\\'); pos[0] += 2; break;
                    case '/':  sb.append('/'); pos[0] += 2; break;
                    case 'n':  sb.append('\n'); pos[0] += 2; break;
                    case 'r':  sb.append('\r'); pos[0] += 2; break;
                    case 't':  sb.append('\t'); pos[0] += 2; break;
                    case 'b':  sb.append('\b'); pos[0] += 2; break;
                    case 'f':  sb.append('\f'); pos[0] += 2; break;
                    case 'u':
                        if (pos[0] + 5 < s.length()) {
                            try {
                                int cp = Integer.parseInt(s.substring(pos[0] + 2, pos[0] + 6), 16);
                                sb.append((char) cp);
                            } catch (NumberFormatException ex) {
                                sb.append(n);
                            }
                            pos[0] += 6;
                        } else {
                            pos[0] += 2;
                        }
                        break;
                    default:
                        sb.append(n);
                        pos[0] += 2;
                }
            } else {
                sb.append(c);
                pos[0]++;
            }
        }
        return sb.toString();
    }

    private static String lerValorJson(String s, int[] pos) {
        if (pos[0] >= s.length()) return null;
        char c = s.charAt(pos[0]);
        if (c == '"') return lerStringJson(s, pos);
        // numero, boolean, null: le ate proxima virgula, fecha-chave ou espaco
        StringBuilder sb = new StringBuilder();
        while (pos[0] < s.length()) {
            char ch = s.charAt(pos[0]);
            if (ch == ',' || ch == '}' || ch == ']' || Character.isWhitespace(ch)) break;
            sb.append(ch);
            pos[0]++;
        }
        return sb.toString();
    }

    private static int getEtapaConcluida(Map<String, String> estado) {
        try {
            return Integer.parseInt(estado.getOrDefault("etapaConcluida", "0"));
        } catch (NumberFormatException e) {
            return 0;
        }
    }

    private static String statusEtapa(int n, int etapaConcluida) {
        if (n <= etapaConcluida) return "[CONCLUIDA]";
        if (n == etapaConcluida + 1) return "[PROXIMA >>] <------ voce esta aqui";
        return "[PENDENTE]";
    }

    private static int exibirMenu(Map<String, String> estado) {
        int etapaConcluida = getEtapaConcluida(estado);

        System.out.println("\n=============================================");
        System.out.println("  Certificado Dinamico Itau - Menu Principal ");
        System.out.println("=============================================");
        System.out.println("\n  Progresso atual: Etapa " + etapaConcluida + " de 3 concluida(s)\n");

        System.out.println("  1. " + ETAPAS_DESC[1] + " " + statusEtapa(1, etapaConcluida));

        System.out.println("  2. " + ETAPAS_DESC[2] + " " + statusEtapa(2, etapaConcluida));

        System.out.println("  3. " + ETAPAS_DESC[3] + " " + statusEtapa(3, etapaConcluida));

        System.out.println("  4. " + ETAPAS_DESC[4]);

        System.out.println("\n  0. Sair");
        System.out.println("  9. Recomecar do zero (limpa progresso)");

        return etapaConcluida + 1 <= 3 ? etapaConcluida + 1 : 3;
    }

    public static void main(String[] args) {
        String arquivoLog = configurarLog();
        log("INFO", "Programa iniciado");
        System.out.println("  Log desta execucao: " + arquivoLog);
        Scanner scanner = new Scanner(System.in);

        Map<String, String> estado = carregarEstado();
        String clientSecretMemoria = "";
        while (true) {
                int proxima = exibirMenu(estado);

                System.out.print("\nEscolha a etapa (padrao: " + proxima + "): ");
                String escolhaStr = scanner.nextLine().trim();
                int escolha;
                try {
                    escolha = escolhaStr.isEmpty() ? proxima : Integer.parseInt(escolhaStr);
                } catch (NumberFormatException e) {
                    System.out.println("Opcao invalida.");
                    continue;
                }
                log("INFO", "Opcao escolhida pelo usuario: " + escolha);

                if (escolha == 0) {
                    log("INFO", "Programa encerrado pelo usuario");
                    System.out.println("\nSaindo. Seu progresso foi salvo.");
                    break;
                } else if (escolha == 9) {
                    log("INFO", "Limpeza de progresso e arquivos gerados iniciada");
                    for (String pasta : new String[]{PASTA_ETAPA1, PASTA_ETAPA2, PASTA_ETAPA4}) {
                        File dir = new File(pasta);
                        if (dir.isDirectory()) {
                            for (File arq : dir.listFiles()) {
                                if (arq.isFile()) arq.delete();
                            }
                            System.out.println("  Arquivos removidos da pasta: " + dir.getAbsolutePath());
                            log("INFO", "Arquivos removidos da pasta: " + dir.getAbsolutePath());
                        }
                    }
                    File estadoFile = new File(ARQUIVO_ESTADO);
                    if (estadoFile.exists()) {
                        estadoFile.delete();
                        System.out.println("  Removido: " + new File(ARQUIVO_ESTADO).getAbsolutePath());
                        log("INFO", "Removido: " + new File(ARQUIVO_ESTADO).getAbsolutePath());
                    }
                    System.out.println("\nProgresso e arquivos gerados removidos.");
                    log("INFO", "Limpeza concluida");
                    estado = new java.util.HashMap<>();
                    clientSecretMemoria = "";
                    continue;
                }

                try {
                    if (escolha == 1) {
                    log("INFO", "Inicio da Etapa 1: Gerar par de chaves e exibir chave publica");
                    new File(PASTA_ETAPA1).mkdirs();
                    System.out.println("\n[PASSO 1.1] Gerando par de chaves RSA 2048...");
                    String caminhoPrivada = PASTA_ETAPA1 + File.separator + "private.pem";
                    String caminhoPublica = PASTA_ETAPA1 + File.separator + "public.pem";
                    gerarParDeChaves(caminhoPrivada, caminhoPublica);

                    log("INFO", "Chave privada gerada: " + new File(caminhoPrivada).getAbsolutePath());
                    log("INFO", "Chave publica gerada: " + new File(caminhoPublica).getAbsolutePath());

                    System.out.println("\n[PASSO 1.2] Exibindo chave publica...");
                    String conteudoChavePublica = new String(Files.readAllBytes(Paths.get(caminhoPublica)), StandardCharsets.UTF_8);
                    System.out.println("\n=============================================");
                    System.out.println("  Chave Publica Gerada                       ");
                    System.out.println("=============================================");
                    System.out.println(conteudoChavePublica);

                    estado.put("etapaConcluida", String.valueOf(Math.max(getEtapaConcluida(estado), 1)));
                    estado.put("caminhoChavePrivada", new File(caminhoPrivada).getAbsolutePath());
                    estado.put("caminhoChavePublica", new File(caminhoPublica).getAbsolutePath());
                    salvarEstado(estado);
                    log("INFO", "Etapa 1 concluida com sucesso");
                    System.out.println("\n>> Etapa 1 concluida. Progresso salvo.");
                    System.out.println("\n  O que fazer agora:");
                    System.out.println("  1. Copie o conteudo da chave publica exibido acima");
                    System.out.println("  2. Envie a chave publica para o Analista de operacoes Itau");
                    System.out.println("  3. Aguarde o e-mail do Itau com as credenciais cifradas");
                    System.out.println("  4. Quando receber o e-mail, execute a etapa 2");

                } else if (escolha == 2) {
                    if (getEtapaConcluida(estado) < 1) {
                        System.out.println("\n[AVISO] A etapa 1 (gerar chaves) precisa ser concluida antes.");
                        log("WARN", "Tentativa de executar Etapa 2 sem concluir Etapa 1");
                        continue;
                    }

                    log("INFO", "Inicio da Etapa 2: Descriptografar credenciais, gerar e enviar certificado");
                    new File(PASTA_ETAPA2).mkdirs();

                    System.out.println("\n[PASSO 2.1] Descriptografar credenciais recebidas por e-mail...");
                    String caminhoChavePrivada = new File(estado.getOrDefault("caminhoChavePrivada", "private.pem")).getAbsolutePath();
                    System.out.println("  Chave privada: " + caminhoChavePrivada);
                    log("INFO", "Chave privada utilizada: " + caminhoChavePrivada);

                    System.out.print("Client ID cifrado: ");
                    String clientIdCifrado = scanner.nextLine().trim();
                    System.out.print("Token temporario cifrado: ");
                    String tokenCifrado = scanner.nextLine().trim();
                    System.out.print("Chave de sessao cifrada: ");
                    String chaveSessaoCifrada = scanner.nextLine().trim();
                    log("INFO", "Client ID cifrado: " + mascarar(clientIdCifrado));
                    log("INFO", "Token cifrado: " + mascarar(tokenCifrado));
                    log("INFO", "Chave de sessao cifrada: " + mascarar(chaveSessaoCifrada));

                    String[] credenciais = descriptografarCredenciais(
                            clientIdCifrado, tokenCifrado, chaveSessaoCifrada, caminhoChavePrivada);

                    String caminhoClientId = PASTA_ETAPA2 + File.separator + "client_id.txt";
                    escreverArquivo(caminhoClientId, credenciais[0]);
                    System.out.println("  Client ID salvo em " + new File(caminhoClientId).getAbsolutePath());
                    log("INFO", "Client ID decifrado: " + mascarar(credenciais[0]));

                    String caminhoToken = PASTA_ETAPA2 + File.separator + "token_temporario.txt";
                    escreverArquivo(caminhoToken, credenciais[1]);
                    System.out.println("  Token temporario salvo em " + new File(caminhoToken).getAbsolutePath());
                    log("INFO", "Token decifrado: " + mascarar(credenciais[1]));

                    System.out.println("\n[PASSO 2.2] Gerando CSR (Certificate Sign Request)...");
                    String clientId = credenciais[0];
                    System.out.println("  Client ID: " + mascarar(clientId));

                    String organizacao = "";
                    while (organizacao.isEmpty()) {
                        System.out.print("Nome da Empresa: ");
                        organizacao = scanner.nextLine().trim();
                        if (organizacao.isEmpty()) System.out.println("  Campo obrigatorio. Informe o nome da empresa.");
                    }
                    String cidade = "";
                    while (cidade.isEmpty()) {
                        System.out.print("Cidade: ");
                        cidade = scanner.nextLine().trim();
                        if (cidade.isEmpty()) System.out.println("  Campo obrigatorio. Informe a cidade.");
                    }
                    String estadoUF = "";
                    while (estadoUF.isEmpty()) {
                        System.out.print("Estado - UF: ");
                        estadoUF = scanner.nextLine().trim();
                        if (estadoUF.isEmpty()) System.out.println("  Campo obrigatorio. Informe o estado (UF).");
                    }
                    String pais = "";
                    while (pais.isEmpty()) {
                        System.out.print("Pais: ");
                        pais = scanner.nextLine().trim();
                        if (pais.isEmpty()) System.out.println("  Campo obrigatorio. Informe o pais.");
                    }
                    log("INFO", "Client ID: " + mascarar(clientId));
                    log("INFO", "Organizacao: " + organizacao + ", Cidade: " + cidade + ", Estado: " + estadoUF + ", Pais: " + pais);

                    String caminhoCSR3 = PASTA_ETAPA2 + File.separator + "ARQUIVO_REQUEST_CERTIFICADO.csr";
                    String caminhoChaveCSR = PASTA_ETAPA2 + File.separator + "ARQUIVO_CHAVE_PRIVADA.key";
                    gerarCSR(clientId, organizacao, cidade, estadoUF, pais, caminhoCSR3, caminhoChaveCSR);

                    System.out.println("\n[PASSO 2.3] Enviando CSR ao STS Itau...");
                    String tokenDecifrado = credenciais[1];
                    log("INFO", "Token: " + mascarar(tokenDecifrado));
                    log("INFO", "CSR: " + new File(caminhoCSR3).getAbsolutePath());
                    log("INFO", "Enviando requisicao para https://sts.itau.com.br/seguranca/v1/certificado/solicitacao");

                    String caminhoCRT = PASTA_ETAPA2 + File.separator + "certificado.crt";
                    String secret = enviarCertificado(tokenDecifrado, caminhoCSR3, caminhoCRT);
                    clientSecretMemoria = secret;

                    System.out.println("\n=============================================");
                    System.out.println("  Certificado gerado com sucesso!            ");
                    System.out.println("=============================================");
                    System.out.println("Arquivos gerados:");
                    System.out.println("  - " + new File(PASTA_ETAPA1 + File.separator + "private.pem").getAbsolutePath() + " (chave privada para descriptografia)");
                    System.out.println("  - " + new File(PASTA_ETAPA1 + File.separator + "public.pem").getAbsolutePath() + " (chave publica)");
                    System.out.println("  - " + new File(PASTA_ETAPA2 + File.separator + "client_id.txt").getAbsolutePath() + " (Client ID)");
                    System.out.println("  - " + new File(PASTA_ETAPA2 + File.separator + "ARQUIVO_CHAVE_PRIVADA.key").getAbsolutePath() + " (chave privada do certificado)");
                    System.out.println("  - " + new File(PASTA_ETAPA2 + File.separator + "ARQUIVO_REQUEST_CERTIFICADO.csr").getAbsolutePath() + " (CSR)");
                    System.out.println("  - " + new File(PASTA_ETAPA2 + File.separator + "certificado.crt").getAbsolutePath() + " (certificado assinado)");
                    System.out.println("\n==============================================");
                    System.out.println("  ATENCAO - CLIENT SECRET                     ");
                    System.out.println("==============================================");
                    System.out.println("\nO Client Secret e equivalente a uma SENHA. Trate-o com o mesmo cuidado.");
                    System.out.println("O valor do Client Secret sera exibido APENAS NESTE MOMENTO.");
                    System.out.println("Copie e salve em um local seguro. Este valor NAO sera armazenado pelo programa.");
                    System.out.println("\n  Client Secret: " + secret);
                    System.out.println("\n==============================================");
                    System.out.println("Guarde essas informacoes em local seguro. O Client ID e o Client Secret sao equivalentes a senhas");
                    System.out.println("e serao necessarios para gerar tokens OAuth e consumir as APIs do Itau.");
                    System.out.println("NAO compartilhe essas credenciais. Quem tiver acesso a elas podera acessar as APIs em seu nome.");
                    System.out.println("\nO Itau nao se responsabiliza pelo armazenamento local das credenciais.");
                    System.out.println("A guarda correta e de responsabilidade exclusiva do cliente.");

                    LocalDateTime expiracao = LocalDateTime.now().plusDays(365);
                    LocalDateTime inicioRenovacao = expiracao.minusDays(30);
                    LocalDateTime fimRenovacao = expiracao.minusDays(1);
                    DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy");
                    System.out.println("\n===============================================");
                    System.out.println("  VALIDADE DO CERTIFICADO                      ");
                    System.out.println("===============================================");
                    System.out.println("  Validade: 365 dias");
                    System.out.println("  Data de expiracao: " + expiracao.format(fmt));
                    System.out.println("  Periodo de renovacao: de " + inicioRenovacao.format(fmt) + " ate " + fimRenovacao.format(fmt));
                    System.out.println("\n  O processo de renovacao (etapa 4) pode ser realizado a partir de");
                    System.out.println("  30 dias antes da expiracao ate um dia antes da data de expiracao do certificado.");
                    System.out.println("\n  ATENCAO: A responsabilidade de renovar o certificado dentro do prazo");
                    System.out.println("  e exclusivamente sua. Certificados expirados impossibilitam o acesso");
                    System.out.println("  as APIs do Itau e exigem a geracao de um novo certificado do zero.");

                    estado.put("etapaConcluida", String.valueOf(Math.max(getEtapaConcluida(estado), 2)));
                    estado.put("caminhoClientId", new File(caminhoClientId).getAbsolutePath());
                    estado.put("caminhoChavePrivada", caminhoChavePrivada);
                    estado.put("caminhoCSR", new File(caminhoCSR3).getAbsolutePath());
                    estado.put("caminhoCRT", new File(caminhoCRT).getAbsolutePath());
                    salvarEstado(estado);
                    log("INFO", "Certificado salvo: " + new File(caminhoCRT).getAbsolutePath());
                    log("INFO", "Client Secret: " + mascarar(secret));
                    log("INFO", "Etapa 2 concluida com sucesso");
                    System.out.println("\n>> Etapa 2 concluida. Progresso salvo.");
                    System.out.println("\n  O que fazer agora:");
                    System.out.println("  1. Guarde o Client ID e o Client Secret em local seguro - eles sao equivalentes a SENHAS");
                    System.out.println("  2. O certificado (.crt) e a chave privada (.key) estao salvos na pasta " + new File(PASTA_ETAPA2).getAbsolutePath());
                    System.out.println("  3. Execute a etapa 3 para validar que tudo esta funcionando corretamente");
                    System.out.println("  4. Apos validar, utilize o client_id, client_secret, certificado e chave privada na sua aplicacao");
                    System.out.println("\n  LEMBRE-SE: Client ID, Client Secret, certificado e chave privada sao equivalentes a senhas.");
                    System.out.println("  NAO compartilhe, NAO envie por e-mail e NAO deixe exposto em codigo-fonte.");
                    System.out.println("  O Itau NAO se responsabiliza pelo armazenamento local. A guarda e responsabilidade exclusiva do cliente.");

                    System.out.print("\nDeseja gerar o arquivo PFX (PKCS#12)? O PFX combina o certificado e a chave privada em um unico arquivo protegido por senha. (s/N): ");
                    String respPfx2 = scanner.nextLine().trim();
                    if (respPfx2.equalsIgnoreCase("s")) {
                        System.out.print("  Informe a senha para proteger o PFX: ");
                        String senhaPfx2 = scanner.nextLine().trim();
                        if (senhaPfx2.isEmpty()) {
                            System.out.println("  Senha nao informada. PFX nao gerado.");
                            log("WARN", "PFX nao gerado - senha nao informada");
                        } else {
                            String caminhoPfx2 = PASTA_ETAPA2 + File.separator + "certificado.pfx";
                            String caminhoKeyPfx2 = new File(PASTA_ETAPA2 + File.separator + "ARQUIVO_CHAVE_PRIVADA.key").getAbsolutePath();
                            gerarPFX(caminhoCRT, caminhoKeyPfx2, caminhoPfx2, senhaPfx2);
                            System.out.println("  PFX gerado: " + new File(caminhoPfx2).getAbsolutePath());
                            log("INFO", "PFX gerado: " + new File(caminhoPfx2).getAbsolutePath());
                        }
                    }

                } else if (escolha == 3) {
                    if (getEtapaConcluida(estado) < 2) {
                        System.out.println("\n[AVISO] A etapa 2 (gerar e enviar certificado) precisa ser concluida antes.");
                        log("WARN", "Tentativa de executar Etapa 3 sem concluir Etapa 2");
                        continue;
                    }
                                        log("INFO", "Inicio da Etapa 3: Testar geracao de token");
                                        System.out.println("\n[PASSO 3] Testando geracao de token...");
                    String caminhoClientId7 = estado.getOrDefault("caminhoClientId", PASTA_ETAPA2 + File.separator + "client_id.txt");
                    if (!new File(caminhoClientId7).exists()) {
                        throw new Exception("Arquivo de client_id nao encontrado: " + caminhoClientId7 + ". Execute as etapas anteriores.");
                    }
                    String clientIdOAuth = new String(Files.readAllBytes(Paths.get(caminhoClientId7)), StandardCharsets.UTF_8).trim();
                    String clientSecretOAuth;
                    if (!clientSecretMemoria.isEmpty()) {
                        clientSecretOAuth = clientSecretMemoria;
                        System.out.println("  Client Secret: " + mascarar(clientSecretOAuth) + " (obtido da sessao atual)");
                    } else {
                        System.out.print("  Informe o Client Secret: ");
                        clientSecretOAuth = scanner.nextLine().trim();
                    }
                    String caminhoCRTOAuth = estado.getOrDefault("caminhoCRT", PASTA_ETAPA2 + File.separator + "certificado.crt");
                    String caminhoKeyOAuth = new File(PASTA_ETAPA2 + File.separator + "ARQUIVO_CHAVE_PRIVADA.key").getAbsolutePath();

                    if (clientIdOAuth.isEmpty() || clientSecretOAuth.isEmpty()) {
                        throw new Exception("client_id ou client_secret nao informados. Verifique e tente novamente.");
                    }

                    System.out.println("\n  Arquivos utilizados na requisicao:");
                    System.out.println("  - " + new File(caminhoClientId7).getAbsolutePath() + " (Client ID: " + mascarar(clientIdOAuth) + ")");
                    System.out.println("  - " + new File(caminhoCRTOAuth).getAbsolutePath() + " (certificado assinado)");
                    System.out.println("  - " + new File(caminhoKeyOAuth).getAbsolutePath() + " (chave privada do certificado)");

                    String respostaOAuth = obterTokenOAuth(clientIdOAuth, clientSecretOAuth, caminhoCRTOAuth, caminhoKeyOAuth);
                    log("INFO", "Token OAuth obtido com sucesso");
                    log("INFO", "Etapa 3 concluida com sucesso");

                    String accessToken = "";
                    try {
                        int idx = respostaOAuth.indexOf("\"access_token\"");
                        if (idx >= 0) {
                            int start = respostaOAuth.indexOf(':', idx) + 1;
                            int startQuote = respostaOAuth.indexOf('"', start);
                            int endQuote = respostaOAuth.indexOf('"', startQuote + 1);
                            accessToken = respostaOAuth.substring(startQuote + 1, endQuote);
                        }
                    } catch (Exception ignored) {}

                    System.out.println("\n==============================================");
                    System.out.println("  Credenciais e certificado VALIDADOS!        ");
                    System.out.println("==============================================");
                    System.out.println("\nAccess Token:");
                    System.out.println(accessToken.isEmpty() ? respostaOAuth : accessToken);
                    System.out.println("\n  Validade: 300 segundos (5 minutos)");
                    System.out.println("  Uso: Informe este token no header 'Authorization: Bearer <access_token>' das requisicoes as APIs do Itau.");
                    System.out.println("\nAs credenciais (client_id e client_secret) e o certificado digital estao validados e funcionais.");
                    System.out.println("Voce ja pode utiliza-los em suas integracoes.");
                    System.out.println("\n>> Etapa 3 concluida.");
                    System.out.println("\n  O que fazer agora:");
                    System.out.println("  1. Suas credenciais e certificado estao validados e prontos para uso");
                    System.out.println("  2. Na sua aplicacao, implemente o fluxo OAuth usando:");
                    System.out.println("     - Client ID e Client Secret (da etapa 2)");
                    System.out.println("     - Certificado .crt e chave privada .key (da pasta " + new File(PASTA_ETAPA2).getAbsolutePath() + ")");
                    System.out.println("  3. O token gerado tem validade de 5 minutos - sua aplicacao deve renova-lo periodicamente");
                    System.out.println("  4. Lembre-se de renovar o certificado antes da data de expiracao (etapa 4)");

                } else if (escolha == 4) {
                    log("INFO", "Inicio da Etapa 4: Renovar certificado");
                    new File(PASTA_ETAPA4).mkdirs();

                    String caminhoCRTValidacao = estado.getOrDefault("caminhoCRT", PASTA_ETAPA2 + File.separator + "certificado.crt");
                    if (new File(caminhoCRTValidacao).isFile()) {
                        try {
                            CertificateFactory cf = CertificateFactory.getInstance("X.509");
                            X509Certificate certObj = (X509Certificate) cf.generateCertificate(new FileInputStream(caminhoCRTValidacao));
                            java.util.Date dataExpiracao = certObj.getNotAfter();
                            long diasRestantes = java.time.temporal.ChronoUnit.DAYS.between(
                                java.time.LocalDate.now(),
                                dataExpiracao.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate()
                            );
                            DateTimeFormatter fmtVal = DateTimeFormatter.ofPattern("dd/MM/yyyy");
                            String dataExpStr = dataExpiracao.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate().format(fmtVal);
                            log("INFO", "Certificado encontrado: " + new File(caminhoCRTValidacao).getAbsolutePath());
                            log("INFO", "Data de expiracao: " + dataExpStr + " | Dias restantes: " + diasRestantes);
                            System.out.println("\n  Certificado encontrado: " + new File(caminhoCRTValidacao).getAbsolutePath());
                            System.out.println("  Data de expiracao: " + dataExpStr + " (" + diasRestantes + " dias restantes)");
                            if (diasRestantes > 30) {
                                System.out.println("\n  [AVISO] O certificado ainda nao esta no periodo de renovacao.");
                                System.out.println("  A renovacao e permitida nos ultimos 30 dias antes da expiracao.");
                                System.out.println("  Faltam " + (diasRestantes - 30) + " dias para o inicio do periodo de renovacao.");
                                log("WARN", "Certificado fora do periodo de renovacao. Dias restantes: " + diasRestantes);
                                System.out.print("\n  Deseja prosseguir mesmo assim? (s/N): ");
                                String prosseguir = scanner.nextLine().trim();
                                if (!prosseguir.equalsIgnoreCase("s")) {
                                    System.out.println("  Renovacao cancelada pelo usuario.");
                                    log("INFO", "Renovacao cancelada pelo usuario - fora do periodo");
                                    continue;
                                }
                                log("INFO", "Usuario optou por prosseguir com a renovacao fora do periodo");
                            } else if (diasRestantes < 0) {
                                System.out.println("\n  [AVISO] O certificado ja esta EXPIRADO ha " + Math.abs(diasRestantes) + " dias.");
                                log("WARN", "Certificado expirado ha " + Math.abs(diasRestantes) + " dias");
                            } else {
                                System.out.println("  Certificado dentro do periodo de renovacao.");
                            }
                        } catch (Exception exCert) {
                            log("WARN", "Nao foi possivel ler o certificado para validacao: " + exCert.getMessage());
                            System.out.println("\n  [AVISO] Nao foi possivel ler o certificado para validar a data de expiracao.");
                            System.out.println("  Prosseguindo com a renovacao...");
                        }
                    } else {
                        log("INFO", "Certificado nao encontrado para validacao de periodo. Prosseguindo com a renovacao.");
                        System.out.println("\n  [INFO] Certificado nao encontrado em: " + new File(caminhoCRTValidacao).getAbsolutePath());
                        System.out.println("  Nao foi possivel validar o periodo de renovacao (30 dias antes da expiracao).");
                        System.out.println("  Prosseguindo com a renovacao...");
                    }

                    System.out.println("\n=============================================");
                    System.out.println("  Renovacao de Certificado - Escolha a opcao ");
                    System.out.println("=============================================");
                    System.out.println("\n  A. Renovacao completa (gera novo CSR e nova chave privada) - recomendada");
                    System.out.println("     - Gera nova chave privada e novo CSR antes de renovar");
                    System.out.println("     - Recomendado para maior seguranca ou quando a chave pode ter sido comprometida");
                    System.out.println("\n  B. Renovacao simples (reutiliza o CSR existente)");
                    System.out.println("     - Mais rapido, mantem a mesma chave privada do certificado");
                    System.out.println("     - Recomendado quando nao ha necessidade de trocar a chave");

                    System.out.print("\nEscolha a opcao (A ou B): ");
                    String opcaoRenov = scanner.nextLine().trim().toUpperCase();
                    if (!opcaoRenov.equals("A") && !opcaoRenov.equals("B")) {
                        System.out.println("Opcao invalida. Escolha A ou B.");
                        continue;
                    }

                    if (opcaoRenov.equals("A")) {
                        log("INFO", "Opcao A: Renovacao completa (novo CSR e nova chave privada)");
                        System.out.println("\n[PASSO 1] Gerando nova chave privada e novo CSR...");
                        String caminhoClientId4b = estado.getOrDefault("caminhoClientId", PASTA_ETAPA2 + File.separator + "client_id.txt");
                        String clientId4b;
                        if (new File(caminhoClientId4b).exists()) {
                            clientId4b = new String(Files.readAllBytes(Paths.get(caminhoClientId4b)), StandardCharsets.UTF_8).trim();
                        } else if (estado.containsKey("clientId") && !estado.get("clientId").isEmpty()) {
                            clientId4b = estado.get("clientId");
                            System.out.println("  Client ID recuperado do estado salvo.");
                        } else {
                            System.out.println("  Nao foi possivel recuperar o Client ID automaticamente.");
                            System.out.println("  Informe o Client ID manualmente para continuar.");
                            clientId4b = "";
                            while (clientId4b.isEmpty()) {
                                System.out.print("Client ID: ");
                                clientId4b = scanner.nextLine().trim();
                                if (clientId4b.isEmpty()) System.out.println("  Campo obrigatorio. Informe o Client ID.");
                            }
                        }
                        System.out.println("  Client ID: " + mascarar(clientId4b));

                        String organizacao4 = "";
                        while (organizacao4.isEmpty()) {
                            System.out.print("Nome da Empresa: ");
                            organizacao4 = scanner.nextLine().trim();
                            if (organizacao4.isEmpty()) System.out.println("  Campo obrigatorio. Informe o nome da empresa.");
                        }
                        String cidade4 = "";
                        while (cidade4.isEmpty()) {
                            System.out.print("Cidade: ");
                            cidade4 = scanner.nextLine().trim();
                            if (cidade4.isEmpty()) System.out.println("  Campo obrigatorio. Informe a cidade.");
                        }
                        String estadoUF4 = "";
                        while (estadoUF4.isEmpty()) {
                            System.out.print("Estado - UF: ");
                            estadoUF4 = scanner.nextLine().trim();
                            if (estadoUF4.isEmpty()) System.out.println("  Campo obrigatorio. Informe o estado (UF).");
                        }
                        String pais4 = "";
                        while (pais4.isEmpty()) {
                            System.out.print("Pais: ");
                            pais4 = scanner.nextLine().trim();
                            if (pais4.isEmpty()) System.out.println("  Campo obrigatorio. Informe o pais.");
                        }

                        String caminhoCSR4b = PASTA_ETAPA4 + File.separator + "ARQUIVO_REQUEST_CERTIFICADO.csr";
                        String caminhoChaveCSR4b = PASTA_ETAPA4 + File.separator + "ARQUIVO_CHAVE_PRIVADA.key";
                        gerarCSR(clientId4b, organizacao4, cidade4, estadoUF4, pais4, caminhoCSR4b, caminhoChaveCSR4b);
                        log("INFO", "Novo CSR gerado: " + new File(caminhoCSR4b).getAbsolutePath());
                        log("INFO", "Nova chave privada gerada: " + new File(caminhoChaveCSR4b).getAbsolutePath());

                        System.out.println("\n[PASSO 2] Obtendo token para renovacao...");
                        System.out.println("\n=============================================");
                        System.out.println("  Como deseja obter o token para renovacao?  ");
                        System.out.println("=============================================");
                        System.out.println("  1. Automatico (via mTLS com certificado atual)");
                        System.out.println("  2. Manual (informar token manualmente)");
                        System.out.print("\nEscolha (1 ou 2): ");
                        String opcaoTokenB = scanner.nextLine().trim();
                        String tokenSTSB;
                        String caminhoCRTB = "";
                        String caminhoKeyB = "";
                        if (opcaoTokenB.equals("1")) {
                            log("INFO", "Obtencao de token automatica via mTLS");
                            System.out.println("\n[PASSO 2.1] Obtendo token automaticamente via mTLS...");
                            String caminhoClientIdB = estado.getOrDefault("caminhoClientId", PASTA_ETAPA2 + File.separator + "client_id.txt");
                            String clientIdB;
                            if (new File(caminhoClientIdB).exists()) {
                                clientIdB = new String(Files.readAllBytes(Paths.get(caminhoClientIdB)), StandardCharsets.UTF_8).trim();
                                System.out.println("  Client ID encontrado: " + mascarar(clientIdB));
                            } else if (estado.containsKey("clientId") && !estado.get("clientId").isEmpty()) {
                                clientIdB = estado.get("clientId");
                                System.out.println("  Client ID recuperado do estado salvo: " + mascarar(clientIdB));
                            } else {
                                System.out.println("  [AVISO] Arquivo de Client ID nao encontrado em: " + new File(caminhoClientIdB).getAbsolutePath());
                                clientIdB = "";
                                while (clientIdB.isEmpty()) {
                                    System.out.print("  Informe o Client ID: ");
                                    clientIdB = scanner.nextLine().trim();
                                    if (clientIdB.isEmpty()) System.out.println("  Campo obrigatorio. Informe o Client ID.");
                                }
                            }
                            String clientSecretB;
                            if (!clientSecretMemoria.isEmpty()) {
                                clientSecretB = clientSecretMemoria;
                                System.out.println("  Client Secret: " + mascarar(clientSecretB) + " (obtido da sessao atual)");
                            } else {
                                clientSecretB = "";
                                while (clientSecretB.isEmpty()) {
                                    System.out.print("  Informe o Client Secret: ");
                                    clientSecretB = scanner.nextLine().trim();
                                    if (clientSecretB.isEmpty()) System.out.println("  Campo obrigatorio. Informe o Client Secret.");
                                }
                            }
                            caminhoCRTB = estado.getOrDefault("caminhoCRT", PASTA_ETAPA2 + File.separator + "certificado.crt");
                            if (!new File(caminhoCRTB).isFile()) {
                                System.out.println("  [AVISO] Certificado nao encontrado em: " + new File(caminhoCRTB).getAbsolutePath());
                                caminhoCRTB = "";
                                while (caminhoCRTB.isEmpty() || !new File(caminhoCRTB).isFile()) {
                                    System.out.print("  Informe o caminho do certificado (.crt): ");
                                    caminhoCRTB = scanner.nextLine().trim();
                                    if (caminhoCRTB.isEmpty() || !new File(caminhoCRTB).isFile()) System.out.println("  Arquivo nao encontrado: " + caminhoCRTB + ". Informe um caminho valido.");
                                }
                            }
                            System.out.println("  Certificado: " + new File(caminhoCRTB).getAbsolutePath());
                            caminhoKeyB = new File(PASTA_ETAPA2 + File.separator + "ARQUIVO_CHAVE_PRIVADA.key").getAbsolutePath();
                            if (!new File(caminhoKeyB).isFile()) {
                                System.out.println("  [AVISO] Chave privada nao encontrada em: " + caminhoKeyB);
                                caminhoKeyB = "";
                                while (caminhoKeyB.isEmpty() || !new File(caminhoKeyB).isFile()) {
                                    System.out.print("  Informe o caminho da chave privada (.key): ");
                                    caminhoKeyB = scanner.nextLine().trim();
                                    if (caminhoKeyB.isEmpty() || !new File(caminhoKeyB).isFile()) System.out.println("  Arquivo nao encontrado: " + caminhoKeyB + ". Informe um caminho valido.");
                                }
                            }
                            System.out.println("  Chave privada: " + new File(caminhoKeyB).getAbsolutePath());
                            String respostaTokenB = obterTokenOAuth(clientIdB, clientSecretB, caminhoCRTB, caminhoKeyB);
                            tokenSTSB = "";
                            try {
                                int idx = respostaTokenB.indexOf("\"access_token\"");
                                if (idx >= 0) {
                                    int start = respostaTokenB.indexOf(':', idx) + 1;
                                    int startQuote = respostaTokenB.indexOf('"', start);
                                    int endQuote = respostaTokenB.indexOf('"', startQuote + 1);
                                    tokenSTSB = respostaTokenB.substring(startQuote + 1, endQuote);
                                }
                            } catch (Exception ignored) {}
                            if (tokenSTSB.isEmpty()) throw new Exception("Nao foi possivel obter o token automaticamente. Tente a opcao manual.");
                            System.out.println("  Token obtido com sucesso: " + mascarar(tokenSTSB));
                            log("INFO", "Token obtido automaticamente via mTLS: " + mascarar(tokenSTSB));
                        } else {
                            log("INFO", "Obtencao de token manual");
                            System.out.print("Token STS para renovacao: ");
                            tokenSTSB = scanner.nextLine().trim();
                            if (tokenSTSB.isEmpty()) throw new Exception("Token STS e obrigatorio para renovacao do certificado.");
                            System.out.println("\n  Para a requisicao de renovacao, e necessario o certificado e a chave privada (mTLS).");
                            caminhoCRTB = estado.getOrDefault("caminhoCRT", PASTA_ETAPA2 + File.separator + "certificado.crt");
                            if (!new File(caminhoCRTB).isFile()) {
                                System.out.println("  [AVISO] Certificado nao encontrado em: " + new File(caminhoCRTB).getAbsolutePath());
                                caminhoCRTB = "";
                                while (caminhoCRTB.isEmpty() || !new File(caminhoCRTB).isFile()) {
                                    System.out.print("  Informe o caminho do certificado (.crt): ");
                                    caminhoCRTB = scanner.nextLine().trim();
                                    if (caminhoCRTB.isEmpty() || !new File(caminhoCRTB).isFile()) System.out.println("  Arquivo nao encontrado: " + caminhoCRTB + ". Informe um caminho valido.");
                                }
                            }
                            System.out.println("  Certificado: " + new File(caminhoCRTB).getAbsolutePath());
                            caminhoKeyB = new File(PASTA_ETAPA2 + File.separator + "ARQUIVO_CHAVE_PRIVADA.key").getAbsolutePath();
                            if (!new File(caminhoKeyB).isFile()) {
                                System.out.println("  [AVISO] Chave privada nao encontrada em: " + caminhoKeyB);
                                caminhoKeyB = "";
                                while (caminhoKeyB.isEmpty() || !new File(caminhoKeyB).isFile()) {
                                    System.out.print("  Informe o caminho da chave privada (.key): ");
                                    caminhoKeyB = scanner.nextLine().trim();
                                    if (caminhoKeyB.isEmpty() || !new File(caminhoKeyB).isFile()) System.out.println("  Arquivo nao encontrado: " + caminhoKeyB + ". Informe um caminho valido.");
                                }
                            }
                            System.out.println("  Chave privada: " + new File(caminhoKeyB).getAbsolutePath());
                        }

                        System.out.println("\n[PASSO 3] Enviando novo CSR para renovacao...");
                        String caminhoCRT4b = PASTA_ETAPA4 + File.separator + "certificado.crt";
                        log("INFO", "Token STS: " + mascarar(tokenSTSB));
                        log("INFO", "CSR: " + new File(caminhoCSR4b).getAbsolutePath());
                        log("INFO", "Enviando requisicao para https://sts.itau.com.br/seguranca/v2/certificado/renovacao");
                        renovarCertificado(tokenSTSB, caminhoCSR4b, caminhoCRT4b, caminhoCRTB, caminhoKeyB);

                        LocalDateTime expiracaoRenovB = LocalDateTime.now().plusDays(365);
                        LocalDateTime inicioRenovB = expiracaoRenovB.minusDays(30);
                        LocalDateTime fimRenovB = expiracaoRenovB.minusDays(1);
                        DateTimeFormatter fmtRenovB = DateTimeFormatter.ofPattern("dd/MM/yyyy");
                        System.out.println("\n===============================================");
                        System.out.println("  VALIDADE DO CERTIFICADO RENOVADO             ");
                        System.out.println("===============================================");
                        System.out.println("  Validade: 365 dias");
                        System.out.println("  Data de expiracao: " + expiracaoRenovB.format(fmtRenovB));
                        System.out.println("  Periodo de renovacao: de " + inicioRenovB.format(fmtRenovB) + " ate " + fimRenovB.format(fmtRenovB));
                        System.out.println("\n  O processo de renovacao (etapa 4) pode ser realizado a partir de");
                        System.out.println("  30 dias antes da expiracao ate um dia antes da data de expiracao do certificado.");
                        System.out.println("\n  ATENCAO: A responsabilidade de renovar o certificado dentro do prazo");
                        System.out.println("  e exclusivamente sua. Certificados expirados impossibilitam o acesso");
                        System.out.println("  as APIs do Itau e exigem a geracao de um novo certificado do zero.");
                        log("INFO", "Certificado renovado: " + new File(caminhoCRT4b).getAbsolutePath());
                        log("INFO", "Etapa 4 concluida com sucesso");
                        System.out.println("\n>> Etapa 4 concluida. Certificado renovado.");
                        System.out.println("\n  O que fazer agora:");
                        System.out.println("  1. O novo certificado esta salvo na pasta " + new File(PASTA_ETAPA4).getAbsolutePath());
                        System.out.println("  2. Substitua o certificado antigo E a chave privada pelos novos na sua aplicacao");
                        System.out.println("  3. A nova chave privada (.key) esta em " + new File(caminhoChaveCSR4b).getAbsolutePath());
                        System.out.println("  4. Teste a geracao de token (etapa 3) para confirmar que o novo certificado esta funcionando");

                        System.out.print("\nDeseja gerar o arquivo PFX (PKCS#12)? O PFX combina o certificado e a chave privada em um unico arquivo protegido por senha. (s/N): ");
                        String respPfx4b = scanner.nextLine().trim();
                        if (respPfx4b.equalsIgnoreCase("s")) {
                            System.out.print("  Informe a senha para proteger o PFX: ");
                            String senhaPfx4b = scanner.nextLine().trim();
                            if (senhaPfx4b.isEmpty()) {
                                System.out.println("  Senha nao informada. PFX nao gerado.");
                                log("WARN", "PFX nao gerado - senha nao informada");
                            } else {
                                String caminhoPfx4b = PASTA_ETAPA4 + File.separator + "certificado.pfx";
                                gerarPFX(caminhoCRT4b, new File(caminhoChaveCSR4b).getAbsolutePath(), caminhoPfx4b, senhaPfx4b);
                                System.out.println("  PFX gerado: " + new File(caminhoPfx4b).getAbsolutePath());
                                log("INFO", "PFX gerado: " + new File(caminhoPfx4b).getAbsolutePath());
                            }
                        }

                    } else {
                        log("INFO", "Opcao B: Renovacao simples (reutiliza CSR existente)");
                        System.out.println("\n[PASSO 1] Localizando CSR existente...");
                        String caminhoCSR = estado.getOrDefault("caminhoCSR", "");
                        if (caminhoCSR.isEmpty() || !new File(caminhoCSR).isFile()) {
                            String caminhoCSRPadrao = new File(PASTA_ETAPA2, "ARQUIVO_REQUEST_CERTIFICADO.csr").getAbsolutePath();
                            if (new File(caminhoCSRPadrao).isFile()) {
                                caminhoCSR = caminhoCSRPadrao;
                            } else {
                                System.out.print("Caminho do arquivo CSR: ");
                                caminhoCSR = scanner.nextLine().trim();
                                if (caminhoCSR.isEmpty() || !new File(caminhoCSR).isFile()) throw new Exception("Arquivo CSR nao encontrado: " + caminhoCSR);
                            }
                        }
                        System.out.println("  CSR encontrado: " + new File(caminhoCSR).getAbsolutePath());

                        System.out.println("\n[PASSO 2] Obtendo token para renovacao...");
                        System.out.println("\n=============================================");
                        System.out.println("  Como deseja obter o token para renovacao?  ");
                        System.out.println("=============================================");
                        System.out.println("  1. Automatico (via mTLS com certificado atual)");
                        System.out.println("  2. Manual (informar token manualmente)");
                        System.out.print("\nEscolha (1 ou 2): ");
                        String opcaoTokenB2 = scanner.nextLine().trim();
                        String tokenSTSB2;
                        String caminhoCertMtlsB2 = "";
                        String caminhoKeyMtlsB2 = "";
                        if (opcaoTokenB2.equals("1")) {
                            log("INFO", "Obtencao de token automatica via mTLS");
                            System.out.println("\n[PASSO 2.1] Obtendo token automaticamente via mTLS...");
                            String caminhoClientIdB2 = estado.getOrDefault("caminhoClientId", PASTA_ETAPA2 + File.separator + "client_id.txt");
                            String clientIdB2;
                            if (new File(caminhoClientIdB2).exists()) {
                                clientIdB2 = new String(Files.readAllBytes(Paths.get(caminhoClientIdB2)), StandardCharsets.UTF_8).trim();
                                System.out.println("  Client ID encontrado: " + mascarar(clientIdB2));
                            } else if (estado.containsKey("clientId") && !estado.get("clientId").isEmpty()) {
                                clientIdB2 = estado.get("clientId");
                                System.out.println("  Client ID recuperado do estado salvo: " + mascarar(clientIdB2));
                            } else {
                                System.out.println("  [AVISO] Arquivo de Client ID nao encontrado em: " + new File(caminhoClientIdB2).getAbsolutePath());
                                clientIdB2 = "";
                                while (clientIdB2.isEmpty()) {
                                    System.out.print("  Informe o Client ID: ");
                                    clientIdB2 = scanner.nextLine().trim();
                                    if (clientIdB2.isEmpty()) System.out.println("  Campo obrigatorio. Informe o Client ID.");
                                }
                            }
                            String clientSecretB2;
                            if (!clientSecretMemoria.isEmpty()) {
                                clientSecretB2 = clientSecretMemoria;
                                System.out.println("  Client Secret: " + mascarar(clientSecretB2) + " (obtido da sessao atual)");
                            } else {
                                clientSecretB2 = "";
                                while (clientSecretB2.isEmpty()) {
                                    System.out.print("  Informe o Client Secret: ");
                                    clientSecretB2 = scanner.nextLine().trim();
                                    if (clientSecretB2.isEmpty()) System.out.println("  Campo obrigatorio. Informe o Client Secret.");
                                }
                            }
                            String caminhoCRTB2Tok = estado.getOrDefault("caminhoCRT", PASTA_ETAPA2 + File.separator + "certificado.crt");
                            if (!new File(caminhoCRTB2Tok).isFile()) {
                                System.out.println("  [AVISO] Certificado nao encontrado em: " + new File(caminhoCRTB2Tok).getAbsolutePath());
                                caminhoCRTB2Tok = "";
                                while (caminhoCRTB2Tok.isEmpty() || !new File(caminhoCRTB2Tok).isFile()) {
                                    System.out.print("  Informe o caminho do certificado (.crt): ");
                                    caminhoCRTB2Tok = scanner.nextLine().trim();
                                    if (caminhoCRTB2Tok.isEmpty() || !new File(caminhoCRTB2Tok).isFile()) System.out.println("  Arquivo nao encontrado: " + caminhoCRTB2Tok + ". Informe um caminho valido.");
                                }
                            }
                            System.out.println("  Certificado: " + new File(caminhoCRTB2Tok).getAbsolutePath());
                            String caminhoKeyB2 = new File(PASTA_ETAPA2 + File.separator + "ARQUIVO_CHAVE_PRIVADA.key").getAbsolutePath();
                            if (!new File(caminhoKeyB2).isFile()) {
                                System.out.println("  [AVISO] Chave privada nao encontrada em: " + caminhoKeyB2);
                                caminhoKeyB2 = "";
                                while (caminhoKeyB2.isEmpty() || !new File(caminhoKeyB2).isFile()) {
                                    System.out.print("  Informe o caminho da chave privada (.key): ");
                                    caminhoKeyB2 = scanner.nextLine().trim();
                                    if (caminhoKeyB2.isEmpty() || !new File(caminhoKeyB2).isFile()) System.out.println("  Arquivo nao encontrado: " + caminhoKeyB2 + ". Informe um caminho valido.");
                                }
                            }
                            System.out.println("  Chave privada: " + new File(caminhoKeyB2).getAbsolutePath());
                            caminhoCertMtlsB2 = caminhoCRTB2Tok;
                            caminhoKeyMtlsB2 = caminhoKeyB2;
                            String respostaTokenB2 = obterTokenOAuth(clientIdB2, clientSecretB2, caminhoCRTB2Tok, caminhoKeyB2);
                            tokenSTSB2 = "";
                            try {
                                int idx = respostaTokenB2.indexOf("\"access_token\"");
                                if (idx >= 0) {
                                    int start = respostaTokenB2.indexOf(':', idx) + 1;
                                    int startQuote = respostaTokenB2.indexOf('"', start);
                                    int endQuote = respostaTokenB2.indexOf('"', startQuote + 1);
                                    tokenSTSB2 = respostaTokenB2.substring(startQuote + 1, endQuote);
                                }
                            } catch (Exception ignored) {}
                            if (tokenSTSB2.isEmpty()) throw new Exception("Nao foi possivel obter o token automaticamente. Tente a opcao manual.");
                            System.out.println("  Token obtido com sucesso: " + mascarar(tokenSTSB2));
                            log("INFO", "Token obtido automaticamente via mTLS: " + mascarar(tokenSTSB2));
                        } else {
                            log("INFO", "Obtencao de token manual");
                            System.out.print("Token STS para renovacao: ");
                            tokenSTSB2 = scanner.nextLine().trim();
                            if (tokenSTSB2.isEmpty()) throw new Exception("Token STS e obrigatorio para renovacao do certificado.");
                            System.out.println("\n  Para a requisicao de renovacao, e necessario o certificado e a chave privada (mTLS).");
                            caminhoCertMtlsB2 = estado.getOrDefault("caminhoCRT", PASTA_ETAPA2 + File.separator + "certificado.crt");
                            if (!new File(caminhoCertMtlsB2).isFile()) {
                                System.out.println("  [AVISO] Certificado nao encontrado em: " + new File(caminhoCertMtlsB2).getAbsolutePath());
                                caminhoCertMtlsB2 = "";
                                while (caminhoCertMtlsB2.isEmpty() || !new File(caminhoCertMtlsB2).isFile()) {
                                    System.out.print("  Informe o caminho do certificado (.crt): ");
                                    caminhoCertMtlsB2 = scanner.nextLine().trim();
                                    if (caminhoCertMtlsB2.isEmpty() || !new File(caminhoCertMtlsB2).isFile()) System.out.println("  Arquivo nao encontrado: " + caminhoCertMtlsB2 + ". Informe um caminho valido.");
                                }
                            }
                            System.out.println("  Certificado: " + new File(caminhoCertMtlsB2).getAbsolutePath());
                            caminhoKeyMtlsB2 = new File(PASTA_ETAPA2 + File.separator + "ARQUIVO_CHAVE_PRIVADA.key").getAbsolutePath();
                            if (!new File(caminhoKeyMtlsB2).isFile()) {
                                System.out.println("  [AVISO] Chave privada nao encontrada em: " + caminhoKeyMtlsB2);
                                caminhoKeyMtlsB2 = "";
                                while (caminhoKeyMtlsB2.isEmpty() || !new File(caminhoKeyMtlsB2).isFile()) {
                                    System.out.print("  Informe o caminho da chave privada (.key): ");
                                    caminhoKeyMtlsB2 = scanner.nextLine().trim();
                                    if (caminhoKeyMtlsB2.isEmpty() || !new File(caminhoKeyMtlsB2).isFile()) System.out.println("  Arquivo nao encontrado: " + caminhoKeyMtlsB2 + ". Informe um caminho valido.");
                                }
                            }
                            System.out.println("  Chave privada: " + new File(caminhoKeyMtlsB2).getAbsolutePath());
                        }

                        System.out.println("\n[PASSO 3] Enviando CSR para renovacao...");
                        String caminhoCRTB2 = PASTA_ETAPA4 + File.separator + "certificado.crt";
                        log("INFO", "Token STS: " + mascarar(tokenSTSB2));
                        log("INFO", "CSR: " + new File(caminhoCSR).getAbsolutePath());
                        log("INFO", "Enviando requisicao para https://sts.itau.com.br/seguranca/v2/certificado/renovacao");
                        renovarCertificado(tokenSTSB2, caminhoCSR, caminhoCRTB2, caminhoCertMtlsB2, caminhoKeyMtlsB2);

                        LocalDateTime expiracaoRenovB2 = LocalDateTime.now().plusDays(365);
                        LocalDateTime inicioRenovB2 = expiracaoRenovB2.minusDays(30);
                        LocalDateTime fimRenovB2 = expiracaoRenovB2.minusDays(1);
                        DateTimeFormatter fmtRenovB2 = DateTimeFormatter.ofPattern("dd/MM/yyyy");
                        System.out.println("\n===============================================");
                        System.out.println("  VALIDADE DO CERTIFICADO RENOVADO             ");
                        System.out.println("===============================================");
                        System.out.println("  Validade: 365 dias");
                        System.out.println("  Data de expiracao: " + expiracaoRenovB2.format(fmtRenovB2));
                        System.out.println("  Periodo de renovacao: de " + inicioRenovB2.format(fmtRenovB2) + " ate " + fimRenovB2.format(fmtRenovB2));
                        System.out.println("\n  O processo de renovacao (etapa 4) pode ser realizado a partir de");
                        System.out.println("  30 dias antes da expiracao ate um dia antes da data de expiracao do certificado.");
                        System.out.println("\n  ATENCAO: A responsabilidade de renovar o certificado dentro do prazo");
                        System.out.println("  e exclusivamente sua. Certificados expirados impossibilitam o acesso");
                        System.out.println("  as APIs do Itau e exigem a geracao de um novo certificado do zero.");
                        log("INFO", "Certificado renovado: " + new File(caminhoCRTB2).getAbsolutePath());
                        log("INFO", "Etapa 4 concluida com sucesso");
                        System.out.println("\n>> Etapa 4 concluida. Certificado renovado.");
                        System.out.println("\n  O que fazer agora:");
                        System.out.println("  1. O novo certificado esta salvo na pasta " + new File(PASTA_ETAPA4).getAbsolutePath());
                        System.out.println("  2. Substitua o certificado antigo pelo novo na sua aplicacao");
                        System.out.println("  3. A chave privada (.key) da etapa 2 continua sendo a mesma - nao muda na renovacao");
                        System.out.println("  4. Teste a geracao de token (etapa 3) para confirmar que o novo certificado esta funcionando");

                        System.out.print("\nDeseja gerar o arquivo PFX (PKCS#12)? O PFX combina o certificado e a chave privada em um unico arquivo protegido por senha. (s/N): ");
                        String respPfxB2 = scanner.nextLine().trim();
                        if (respPfxB2.equalsIgnoreCase("s")) {
                            System.out.print("  Informe a senha para proteger o PFX: ");
                            String senhaPfxB2 = scanner.nextLine().trim();
                            if (senhaPfxB2.isEmpty()) {
                                System.out.println("  Senha nao informada. PFX nao gerado.");
                                log("WARN", "PFX nao gerado - senha nao informada");
                            } else {
                                String caminhoKeyPfxB2 = new File(PASTA_ETAPA2 + File.separator + "ARQUIVO_CHAVE_PRIVADA.key").getAbsolutePath();
                                String caminhoPfxB2 = PASTA_ETAPA4 + File.separator + "certificado.pfx";
                                gerarPFX(caminhoCRTB2, caminhoKeyPfxB2, caminhoPfxB2, senhaPfxB2);
                                System.out.println("  PFX gerado: " + new File(caminhoPfxB2).getAbsolutePath());
                                log("INFO", "PFX gerado: " + new File(caminhoPfxB2).getAbsolutePath());
                            }
                        }
                    }

                } else {
                    System.out.println("Opcao invalida.");
                    continue;
                }
                } catch (Exception e) {
                    log("ERROR", e.getClass().getSimpleName() + ": " + e.getMessage());
                    log("WARN", "Etapa NAO foi marcada como concluida devido ao erro");
                    System.out.println("\n[ERRO] " + e.getClass().getSimpleName() + ": " + e.getMessage());
                    System.out.println("A etapa NAO foi marcada como concluida. Verifique o erro acima e tente novamente. Caso persista entre em contato com o suporte.");
                }

                System.out.print("\nPressione ENTER para voltar ao menu...");
                scanner.nextLine();
            }

            scanner.close();
            if (logWriter != null) logWriter.close();
    }
}
const crypto = require('crypto');
const { execSync } = require('child_process');
const fs = require('fs');
const https = require('https');
const path = require('path');
const readline = require('readline');

const _DIR_RAIZ = path.join(__dirname, '..'); // sobe de node/ para certificado-dinamico/

const PASTA_SAIDA = path.join(_DIR_RAIZ, 'output');
const PASTA_ETAPA1 = path.join(PASTA_SAIDA, 'etapa1');
const PASTA_ETAPA2 = path.join(PASTA_SAIDA, 'etapa2');
const PASTA_ETAPA4 = path.join(PASTA_SAIDA, 'etapa4');
const PASTA_LOGS = path.join(PASTA_SAIDA, 'logs');
const ARQUIVO_ESTADO = path.join(PASTA_SAIDA, 'estado_certificado.json');

function mascarar(valor) {
    if (!valor || typeof valor !== 'string') return '******';
    valor = valor.trim();
    if (valor.length <= 6) return '******';
    return valor.substring(0, 3) + '******' + valor.substring(valor.length - 3);
}

let logStream = null;

function configurarLog() {
    if (!fs.existsSync(PASTA_LOGS)) fs.mkdirSync(PASTA_LOGS, { recursive: true });
    const now = new Date();
    const timestamp = now.getFullYear()
        + String(now.getMonth() + 1).padStart(2, '0')
        + String(now.getDate()).padStart(2, '0') + '_'
        + String(now.getHours()).padStart(2, '0')
        + String(now.getMinutes()).padStart(2, '0')
        + String(now.getSeconds()).padStart(2, '0');
    const arquivoLog = path.join(PASTA_LOGS, `certificado_dinamico_${timestamp}.log`);
    logStream = fs.createWriteStream(arquivoLog, { flags: 'a' });
    return arquivoLog;
}

function log(nivel, mensagem) {
    if (!logStream) return;
    const now = new Date();
    const ts = now.toISOString().replace('T', ' ').substring(0, 19);
    logStream.write(`${ts} [${nivel}] ${mensagem}\n`);
}

function detalharErroHttp(statusCode) {
    const descricoes = {
        400: 'Requisicao invalida. O CSR pode estar malformado ou os dados enviados estao incorretos.',
        401: 'Nao autorizado. O token pode estar invalido, expirado ou nao foi informado corretamente. Tente executar novamente a partir da etapa 2.',
        403: 'Acesso negado. O token nao tem permissao para esta operacao ou esta expirado. Tente executar novamente a partir da etapa 2 para obter um novo token.',
        404: 'Endpoint nao encontrado. Verifique se a URL do STS Itau esta correta.',
        500: 'Erro interno do servidor STS Itau. Tente novamente mais tarde.',
        502: 'Bad Gateway. O servidor STS Itau esta temporariamente indisponivel. Tente novamente mais tarde.',
        503: 'Servico indisponivel. O servidor STS Itau esta em manutencao. Tente novamente mais tarde.',
    };
    return descricoes[statusCode] || `Erro HTTP ${statusCode}. Verifique a documentacao do STS Itau.`;
}

const ETAPAS = {
    1: 'Gerar par de chaves e exibir chave publica',
    2: 'Descriptografar credenciais, gerar e enviar certificado',
    3: 'Testar geracao de token',
};

function carregarEstado() {
    for (const pasta of [PASTA_SAIDA, PASTA_ETAPA1, PASTA_ETAPA2, PASTA_ETAPA4, PASTA_LOGS]) {
        if (!fs.existsSync(pasta)) fs.mkdirSync(pasta, { recursive: true });
    }
    if (fs.existsSync(ARQUIVO_ESTADO)) {
        return JSON.parse(fs.readFileSync(ARQUIVO_ESTADO, 'utf8'));
    }
    return { etapaConcluida: 0 };
}

function salvarEstado(estado) {
    fs.writeFileSync(ARQUIVO_ESTADO, JSON.stringify(estado, null, 2), 'utf8');
}

class CertificadoDinamico {

    static gerarParDeChaves(caminhoPrivada = 'private.pem', caminhoPublica = 'public.pem') {
        const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
            modulusLength: 2048,
            privateKeyEncoding: {
                type: 'pkcs8',
                format: 'pem',
            },
            publicKeyEncoding: {
                type: 'spki',
                format: 'pem',
            },
        });

        fs.writeFileSync(caminhoPrivada, privateKey);
        console.log(`Chave privada salva em ${path.resolve(caminhoPrivada)}`);

        fs.writeFileSync(caminhoPublica, publicKey);
        console.log(`Chave publica salva em ${path.resolve(caminhoPublica)}`);

        return { privateKey, publicKey };
    }

    static descriptografarRSA(caminhoChavePrivada, dadosCifradosBase64) {
        const chavePrivadaPem = fs.readFileSync(caminhoChavePrivada, 'utf8');
        const dadosCifrados = Buffer.from(dadosCifradosBase64, 'base64');

        const decrypted = crypto.privateDecrypt(
            {
                key: chavePrivadaPem,
                padding: crypto.constants.RSA_PKCS1_PADDING,
            },
            dadosCifrados
        );

        return decrypted;
    }

    
    static descriptografarAES(chaveAES, textoCifradoBase64) {
        const textoCifrado = Buffer.from(textoCifradoBase64, 'base64');
        const iv = Buffer.alloc(16);

        const decipher = crypto.createDecipheriv('aes-256-cbc', chaveAES, iv);
        let decrypted = decipher.update(textoCifrado);
        decrypted = Buffer.concat([decrypted, decipher.final()]);

        return decrypted.toString('utf8');
    }

    
    static descriptografarCredenciais(clientIdCifrado, tokenCifrado, chaveSessaoCifrada, caminhoChavePrivada) {
        console.log('\n=========================================');
        console.log('    Processo de Descriptografia           ');
        console.log('=========================================');

        const chaveSessaoDecifrada = CertificadoDinamico.descriptografarRSA(
            caminhoChavePrivada, chaveSessaoCifrada
        );

        const clientIdDecifrado = CertificadoDinamico.descriptografarAES(
            chaveSessaoDecifrada, clientIdCifrado
        );
        console.log(`\nClient ID decifrado com a chave de sessao AES:\n[ ${mascarar(clientIdDecifrado)} ]`);

        const tokenDecifrado = CertificadoDinamico.descriptografarAES(
            chaveSessaoDecifrada, tokenCifrado
        );
        console.log(`\nToken decifrado com a chave de sessao AES:\n[ ${mascarar(tokenDecifrado)} ]`);

        return { clientId: clientIdDecifrado, token: tokenDecifrado };
    }

    static _derEncode(tag, content) {
        const len = content.length;
        let header;
        if (len < 128) {
            header = Buffer.from([tag, len]);
        } else if (len < 256) {
            header = Buffer.from([tag, 0x81, len]);
        } else {
            header = Buffer.from([tag, 0x82, (len >> 8) & 0xFF, len & 0xFF]);
        }
        return Buffer.concat([header, content]);
    }

    static _derSequence(...elements) {
        return CertificadoDinamico._derEncode(0x30, Buffer.concat(elements));
    }

    static _derSet(...elements) {
        return CertificadoDinamico._derEncode(0x31, Buffer.concat(elements));
    }

    static _derBitString(data) {
        return CertificadoDinamico._derEncode(0x03, Buffer.concat([Buffer.from([0x00]), data]));
    }

    static _derUtf8String(str) {
        return CertificadoDinamico._derEncode(0x0C, Buffer.from(str, 'utf8'));
    }

    static _derPrintableString(str) {
        return CertificadoDinamico._derEncode(0x13, Buffer.from(str, 'ascii'));
    }

    static _buildRDN(oidBytes, value, stringTag) {
        const oid = CertificadoDinamico._derEncode(0x06, Buffer.from(oidBytes));
        const val = stringTag === 0x13
            ? CertificadoDinamico._derPrintableString(value)
            : CertificadoDinamico._derUtf8String(value);
        const atv = CertificadoDinamico._derSequence(oid, val);
        return CertificadoDinamico._derSet(atv);
    }

    static gerarCSR(clientId, organizacao, cidade, estado, pais,
                     caminhoCSR = 'ARQUIVO_REQUEST_CERTIFICADO.csr',
                     caminhoChavePrivadaCSR = 'ARQUIVO_CHAVE_PRIVADA.key') {
        const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
            modulusLength: 2048,
            publicKeyEncoding: { type: 'spki', format: 'der' },
            privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
        });

        fs.writeFileSync(caminhoChavePrivadaCSR, privateKey);
        console.log(`\nChave privada do CSR salva em ${path.resolve(caminhoChavePrivadaCSR)}`);

        const OID_CN = [0x55, 0x04, 0x03];
        const OID_OU = [0x55, 0x04, 0x0B];
        const OID_L  = [0x55, 0x04, 0x07];
        const OID_ST = [0x55, 0x04, 0x08];
        const OID_C  = [0x55, 0x04, 0x06];

        const subject = CertificadoDinamico._derSequence(
            CertificadoDinamico._buildRDN(OID_C, pais, 0x13),
            CertificadoDinamico._buildRDN(OID_ST, estado, 0x0C),
            CertificadoDinamico._buildRDN(OID_L, cidade, 0x0C),
            CertificadoDinamico._buildRDN(OID_OU, organizacao, 0x0C),
            CertificadoDinamico._buildRDN(OID_CN, clientId, 0x0C)
        );

        const version = CertificadoDinamico._derEncode(0x02, Buffer.from([0x00]));
        const spki = Buffer.isBuffer(publicKey) ? publicKey : Buffer.from(publicKey);
        const attributes = Buffer.from([0xA0, 0x00]);

        const certReqInfo = CertificadoDinamico._derSequence(version, subject, spki, attributes);

        const sign = crypto.createSign('SHA512');
        sign.update(certReqInfo);
        const signatureBytes = sign.sign(privateKey);

        const sha512WithRSA = Buffer.from([
            0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86,
            0xF7, 0x0D, 0x01, 0x01, 0x0D, 0x05, 0x00
        ]);

        const sigBitString = CertificadoDinamico._derBitString(signatureBytes);
        const csrDer = CertificadoDinamico._derSequence(certReqInfo, sha512WithRSA, sigBitString);

        const csrBase64 = csrDer.toString('base64');
        const csrPem = '-----BEGIN CERTIFICATE REQUEST-----\n' +
            csrBase64.match(/.{1,64}/g).join('\n') +
            '\n-----END CERTIFICATE REQUEST-----\n';

        fs.writeFileSync(caminhoCSR, csrPem);
        console.log(`CSR salvo em ${path.resolve(caminhoCSR)}`);
    }

    static enviarCertificado(token, caminhoCSR = 'ARQUIVO_REQUEST_CERTIFICADO.csr',
                              caminhoCRT = 'certificado.crt') {
        return new Promise((resolve, reject) => {
            const conteudoCSR = fs.readFileSync(caminhoCSR, 'utf8');

            const options = {
                hostname: 'sts.itau.com.br',
                path: '/seguranca/v1/certificado/solicitacao',
                method: 'POST',
                headers: {
                    'Content-Type': 'text/plain',
                    'Authorization': `Bearer ${token}`,
                },
                rejectUnauthorized: false,
            };

            log('INFO', '=== DETALHES DA REQUISICAO HTTP (Enviar CSR) ===');
            log('INFO', `URL: https://${options.hostname}${options.path}`);
            log('INFO', `Metodo: ${options.method}`);
            log('INFO', `Headers: Content-Type=${options.headers['Content-Type']}, Authorization=Bearer ${mascarar(token)}`);
            log('INFO', `Body (CSR): ${conteudoCSR.substring(0, 200)}${conteudoCSR.length > 200 ? '...' : ''}`);
            log('INFO', `Tamanho do body: ${conteudoCSR.length} bytes`);
            log('INFO', `rejectUnauthorized: ${options.rejectUnauthorized}`);
            const req = https.request(options, (res) => {
                let data = '';
                res.on('data', (chunk) => { data += chunk; });
                res.on('end', () => {
                    log('INFO', '=== DETALHES DA RESPOSTA HTTP (Enviar CSR) ===');
                    log('INFO', `Status code: ${res.statusCode}`);
                    log('INFO', `Response headers: ${JSON.stringify(res.headers)}`);
                    log('INFO', `Response body (primeiros 500 chars): ${data.substring(0, 500)}`);

                    if (res.statusCode !== 200) {
                        const detalhe = detalharErroHttp(res.statusCode);
                        reject(new Error(`Erro na geracao do certificado. Status: ${res.statusCode}\n  Possivel causa: ${detalhe}\n  Resposta do servidor: ${data.substring(0, 500)}`));
                        return;
                    }

                    const linhas = data.split('\n');
                    let secret = '';
                    const crtContent = [];

                    const uuidPattern = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/;
                    for (const linha of linhas) {
                        if (linha.includes('Secret:')) {
                            const match = linha.match(uuidPattern);
                            secret = match ? match[0] : linha;
                        } else {
                            crtContent.push(linha);
                        }
                    }

                    fs.writeFileSync(caminhoCRT, crtContent.join('\n'));
                    console.log(`\nCertificado salvo em ${path.resolve(caminhoCRT)}`);
                    console.log(`Client Secret: ${mascarar(secret)}`);

                    resolve(secret);
                });
            });

            req.on('error', (error) => {
                reject(new Error(`Erro ao enviar CSR: ${error.message}`));
            });

            req.write(conteudoCSR);
            req.end();
        });
    }

    static obterTokenOAuth(clientId, clientSecret, caminhoCRT, caminhoKey) {
        return new Promise((resolve, reject) => {
            const certData = fs.readFileSync(caminhoCRT);
            const keyData = fs.readFileSync(caminhoKey);

            const body = `grant_type=client_credentials&client_id=${encodeURIComponent(clientId)}&client_secret=${encodeURIComponent(clientSecret)}`;

            const options = {
                hostname: 'sts.itau.com.br',
                path: '/api/oauth/token',
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'Content-Length': Buffer.byteLength(body),
                },
                cert: certData,
                key: keyData,
                rejectUnauthorized: false,
            };

            log('INFO', '=== DETALHES DA REQUISICAO HTTP (Token OAuth) ===');
            log('INFO', `URL: https://${options.hostname}${options.path}`);
            log('INFO', `Metodo: ${options.method}`);
            log('INFO', `Headers: Content-Type=${options.headers['Content-Type']}`);
            log('INFO', `Certificado mTLS: ${path.resolve(caminhoCRT)}`);
            log('INFO', `Chave mTLS: ${path.resolve(caminhoKey)}`);
            log('INFO', `Body: grant_type=client_credentials&client_id=${mascarar(clientId)}&client_secret=${mascarar(clientSecret)}`);
            log('INFO', `rejectUnauthorized: ${options.rejectUnauthorized}`);

            const req = https.request(options, (res) => {
                let data = '';
                res.on('data', (chunk) => { data += chunk; });
                res.on('end', () => {
                    log('INFO', '=== DETALHES DA RESPOSTA HTTP (Token OAuth) ===');
                    log('INFO', `Status code: ${res.statusCode}`);
                    log('INFO', `Response headers: ${JSON.stringify(res.headers)}`);
                    log('INFO', `Response body (primeiros 500 chars): ${data.substring(0, 500)}`);

                    if (res.statusCode !== 200) {
                        const detalhe = detalharErroHttp(res.statusCode);
                        reject(new Error(`Erro ao obter token OAuth. Status: ${res.statusCode}\n  Possivel causa: ${detalhe}\n  Resposta do servidor: ${data.substring(0, 500)}`));
                        return;
                    }

                    resolve(data);
                });
            });

            req.on('error', (error) => {
                reject(new Error(`Erro ao obter token OAuth: ${error.message}`));
            });

            req.write(body);
            req.end();
        });
    }

    static renovarCertificado(tokenSTS, caminhoCSR = 'ARQUIVO_REQUEST_CERTIFICADO.csr',
                               caminhoCRT = 'certificado.crt', caminhoCertMtls = '', caminhoKeyMtls = '') {
        return new Promise((resolve, reject) => {
            const conteudoCSR = fs.readFileSync(caminhoCSR, 'utf8');

            const crypto = require('crypto');
            const correlationId = crypto.randomUUID();

            const options = {
                hostname: 'sts.itau.com.br',
                path: '/seguranca/v2/certificado/renovacao',
                method: 'POST',
                headers: {
                    'Content-Type': 'text/plain',
                    'Authorization': `Bearer ${tokenSTS}`,
                    'x-itau-correlationID': correlationId,
                },
                rejectUnauthorized: false,
            };
            if (caminhoCertMtls && caminhoKeyMtls) {
                options.cert = fs.readFileSync(caminhoCertMtls);
                options.key = fs.readFileSync(caminhoKeyMtls);
                log('INFO', `mTLS cert: ${caminhoCertMtls}`);
                log('INFO', `mTLS key: ${caminhoKeyMtls}`);
            }

            log('INFO', '=== DETALHES DA REQUISICAO HTTP (Renovacao) ===');
            log('INFO', `URL: https://${options.hostname}${options.path}`);
            log('INFO', `Metodo: ${options.method}`);
            log('INFO', `Headers: Content-Type=${options.headers['Content-Type']}, Authorization=Bearer ${mascarar(tokenSTS)}, x-itau-correlationID=${correlationId}`);
            log('INFO', `Body (CSR): ${conteudoCSR.substring(0, 200)}${conteudoCSR.length > 200 ? '...' : ''}`);
            log('INFO', `Tamanho do body: ${conteudoCSR.length} bytes`);
            log('INFO', `rejectUnauthorized: ${options.rejectUnauthorized}`);
            const req = https.request(options, (res) => {
                let data = '';
                res.on('data', (chunk) => { data += chunk; });
                res.on('end', () => {
                    log('INFO', '=== DETALHES DA RESPOSTA HTTP (Renovacao) ===');
                    log('INFO', `Status code: ${res.statusCode}`);
                    log('INFO', `Response headers: ${JSON.stringify(res.headers)}`);
                    log('INFO', `Response body (primeiros 500 chars): ${data.substring(0, 500)}`);

                    if (res.statusCode !== 200) {
                        const detalhe = detalharErroHttp(res.statusCode);
                        reject(new Error(`Erro na renovacao do certificado. Status: ${res.statusCode}\n  Possivel causa: ${detalhe}\n  Resposta do servidor: ${data.substring(0, 500)}`));
                        return;
                    }

                    fs.writeFileSync(caminhoCRT, data);
                    console.log(`\nCertificado renovado salvo em ${path.resolve(caminhoCRT)}`);
                    resolve(data);
                });
            });

            req.on('error', (error) => {
                reject(new Error(`Erro ao renovar certificado: ${error.message}`));
            });

            req.write(conteudoCSR);
            req.end();
        });
    }
}

function executarEtapa1(estado) {
    log('INFO', 'Inicio da Etapa 1: Gerar par de chaves e exibir chave publica');
    if (!fs.existsSync(PASTA_ETAPA1)) fs.mkdirSync(PASTA_ETAPA1, { recursive: true });
    console.log('\n[PASSO 1.1] Gerando par de chaves RSA 2048...');
    const caminhoPrivada = path.join(PASTA_ETAPA1, 'private.pem');
    const caminhoPublica = path.join(PASTA_ETAPA1, 'public.pem');
    CertificadoDinamico.gerarParDeChaves(caminhoPrivada, caminhoPublica);

    log('INFO', `Chave privada gerada: ${path.resolve(caminhoPrivada)}`);
    log('INFO', `Chave publica gerada: ${path.resolve(caminhoPublica)}`);

    console.log('\n[PASSO 1.2] Exibindo chave publica...');
    const conteudoChavePublica = fs.readFileSync(caminhoPublica, 'utf8');
    console.log('\n=============================================');
    console.log('  Chave Publica Gerada                       ');
    console.log('=============================================');
    console.log(conteudoChavePublica);

    estado.etapaConcluida = Math.max(estado.etapaConcluida || 0, 1);
    estado.caminhoChavePrivada = path.resolve(caminhoPrivada);
    estado.caminhoChavePublica = path.resolve(caminhoPublica);
    salvarEstado(estado);
    log('INFO', 'Etapa 1 concluida com sucesso');
    console.log('\n>> Etapa 1 concluida. Progresso salvo.');
    console.log('\n  O que fazer agora:');
    console.log('  1. Copie o conteudo da chave publica exibido acima');
    console.log('  2. Envie a chave publica para o Analista de operacoes Itau');
    console.log('  3. Aguarde o e-mail do Itau com as credenciais cifradas');
    console.log('  4. Quando receber o e-mail, execute a etapa 2');
}

async function executarEtapa2(estado, pergunta) {
    if ((estado.etapaConcluida || 0) < 1) {
        console.log('\n[AVISO] A etapa 1 (gerar chaves) precisa ser concluida antes.');
        log('WARN', 'Tentativa de executar Etapa 2 sem concluir Etapa 1');
        return;
    }

    log('INFO', 'Inicio da Etapa 2: Descriptografar credenciais, gerar e enviar certificado');
    if (!fs.existsSync(PASTA_ETAPA2)) fs.mkdirSync(PASTA_ETAPA2, { recursive: true });

    console.log('\n[PASSO 2.1] Descriptografar credenciais recebidas por e-mail...');
    const caminhoChavePrivada = path.resolve(estado.caminhoChavePrivada || 'private.pem');
    console.log(`  Chave privada: ${caminhoChavePrivada}`);
    log('INFO', `Chave privada utilizada: ${path.resolve(caminhoChavePrivada)}`);

    const clientIdCifrado = (await pergunta('Client ID cifrado: ')).trim();
    const tokenCifrado = (await pergunta('Token temporario cifrado: ')).trim();
    const chaveSessaoCifrada = (await pergunta('Chave de sessao cifrada: ')).trim();
    log('INFO', `Client ID cifrado: ${mascarar(clientIdCifrado)}`);
    log('INFO', `Token cifrado: ${mascarar(tokenCifrado)}`);
    log('INFO', `Chave de sessao cifrada: ${mascarar(chaveSessaoCifrada)}`);

    const { clientId, token: tokenDecifrado } = CertificadoDinamico.descriptografarCredenciais(
        clientIdCifrado, tokenCifrado, chaveSessaoCifrada, caminhoChavePrivada
    );

    const caminhoClientId = path.join(PASTA_ETAPA2, 'client_id.txt');
    fs.writeFileSync(caminhoClientId, clientId, 'utf8');
    console.log(`  Client ID salvo em ${path.resolve(caminhoClientId)}`);
    log('INFO', `Client ID decifrado: ${mascarar(clientId)}`);

    const caminhoToken = path.join(PASTA_ETAPA2, 'token_temporario.txt');
    fs.writeFileSync(caminhoToken, tokenDecifrado, 'utf8');
    console.log(`  Token temporario salvo em ${path.resolve(caminhoToken)}`);
    log('INFO', `Token decifrado: ${mascarar(tokenDecifrado)}`);

    console.log('\n[PASSO 2.2] Gerando CSR (Certificate Sign Request)...');
    console.log(`  Client ID: ${mascarar(clientId)}`);

    let organizacao = '';
    while (!organizacao) {
        organizacao = (await pergunta('Nome da Empresa: ')).trim();
        if (!organizacao) console.log('  Campo obrigatorio. Informe o nome da empresa.');
    }
    let cidade = '';
    while (!cidade) {
        cidade = (await pergunta('Cidade: ')).trim();
        if (!cidade) console.log('  Campo obrigatorio. Informe a cidade.');
    }
    let estadoUF = '';
    while (!estadoUF) {
        estadoUF = (await pergunta('Estado - UF: ')).trim();
        if (!estadoUF) console.log('  Campo obrigatorio. Informe o estado (UF).');
    }
    let pais = '';
    while (!pais) {
        pais = (await pergunta('Pais: ')).trim();
        if (!pais) console.log('  Campo obrigatorio. Informe o pais.');
    }
    log('INFO', `Client ID: ${mascarar(clientId)}`);
    log('INFO', `Organizacao: ${organizacao}, Cidade: ${cidade}, Estado: ${estadoUF}, Pais: ${pais}`);

    const caminhoCSR = path.join(PASTA_ETAPA2, 'ARQUIVO_REQUEST_CERTIFICADO.csr');
    const caminhoChaveCSR = path.join(PASTA_ETAPA2, 'ARQUIVO_CHAVE_PRIVADA.key');
    CertificadoDinamico.gerarCSR(clientId, organizacao, cidade, estadoUF, pais, caminhoCSR, caminhoChaveCSR);

    console.log('\n[PASSO 2.3] Enviando CSR ao STS Itau...');
    log('INFO', `Token: ${mascarar(tokenDecifrado)}`);
    log('INFO', `CSR: ${path.resolve(caminhoCSR)}`);
    log('INFO', 'Enviando requisicao para https://sts.itau.com.br/seguranca/v1/certificado/solicitacao');

    const caminhoCRT = path.join(PASTA_ETAPA2, 'certificado.crt');
    const secret = await CertificadoDinamico.enviarCertificado(tokenDecifrado, caminhoCSR, caminhoCRT);

    console.log('\n=============================================');
    console.log('  Certificado gerado com sucesso!            ');
    console.log('=============================================');
    console.log('Arquivos gerados:');
    console.log(`  - ${path.resolve(PASTA_ETAPA1, 'private.pem')} (chave privada para descriptografia)`);
    console.log(`  - ${path.resolve(PASTA_ETAPA1, 'public.pem')} (chave publica)`);
    console.log(`  - ${path.resolve(PASTA_ETAPA2, 'client_id.txt')} (Client ID)`);
    console.log(`  - ${path.resolve(PASTA_ETAPA2, 'ARQUIVO_CHAVE_PRIVADA.key')} (chave privada do certificado)`);
    console.log(`  - ${path.resolve(PASTA_ETAPA2, 'ARQUIVO_REQUEST_CERTIFICADO.csr')} (CSR)`);
    console.log(`  - ${path.resolve(PASTA_ETAPA2, 'certificado.crt')} (certificado assinado)`);
    console.log('\n==============================================');
    console.log('  ATENCAO - CLIENT SECRET                     ');
    console.log('==============================================');
    console.log('\nO Client Secret e equivalente a uma SENHA. Trate-o com o mesmo cuidado.');
    console.log('O valor do Client Secret sera exibido APENAS NESTE MOMENTO.');
    console.log('Copie e salve em um local seguro. Este valor NAO sera armazenado pelo programa.');
    console.log(`\n  Client Secret: ${secret}`);
    console.log('\n==============================================');
    console.log('Guarde essas informacoes em local seguro. O Client ID e o Client Secret sao equivalentes a senhas');
    console.log('e serao necessarios para gerar tokens OAuth e consumir as APIs do Itau.');
    console.log('NAO compartilhe essas credenciais. Quem tiver acesso a elas podera acessar as APIs em seu nome.');
    console.log('\nO Itau nao se responsabiliza pelo armazenamento local das credenciais.');
    console.log('A guarda correta e de responsabilidade exclusiva do cliente.');

    const expiracao = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000);
    const inicioRenovacao = new Date(expiracao.getTime() - 30 * 24 * 60 * 60 * 1000);
    const fimRenovacao = new Date(expiracao.getTime() - 1 * 24 * 60 * 60 * 1000);
    const fmtData = (d) => `${String(d.getDate()).padStart(2,'0')}/${String(d.getMonth()+1).padStart(2,'0')}/${d.getFullYear()}`;
    console.log('\n===============================================');
    console.log('  VALIDADE DO CERTIFICADO                      ');
    console.log('===============================================');
    console.log('  Validade: 365 dias');
    console.log(`  Data de expiracao: ${fmtData(expiracao)}`);
    console.log(`  Periodo de renovacao: de ${fmtData(inicioRenovacao)} ate ${fmtData(fimRenovacao)}`);
    console.log('\n  O processo de renovacao (etapa 4) pode ser realizado a partir de');
    console.log('  30 dias antes da expiracao ate um dia antes da data de expiracao do certificado.');
    console.log('\n  ATENCAO: A responsabilidade de renovar o certificado dentro do prazo');
    console.log('  e exclusivamente sua. Certificados expirados impossibilitam o acesso');
    console.log('  as APIs do Itau e exigem a geracao de um novo certificado do zero.');

    estado.etapaConcluida = Math.max(estado.etapaConcluida || 0, 2);
    estado.caminhoClientId = path.resolve(caminhoClientId);
    estado.caminhoChavePrivada = caminhoChavePrivada;
    estado.caminhoCSR = path.resolve(caminhoCSR);
    estado.caminhoCRT = path.resolve(caminhoCRT);
    salvarEstado(estado);
    log('INFO', `Certificado salvo: ${path.resolve(caminhoCRT)}`);
    log('INFO', `Client Secret: ${mascarar(secret)}`);
    log('INFO', 'Etapa 2 concluida com sucesso');
    console.log('\n>> Etapa 2 concluida. Progresso salvo.');
    console.log('\n  O que fazer agora:');
    console.log('  1. Guarde o Client ID e o Client Secret em local seguro - eles sao equivalentes a SENHAS');
    console.log(`  2. O certificado (.crt) e a chave privada (.key) estao salvos na pasta ${path.resolve(PASTA_ETAPA2)}`);
    console.log('  3. Execute a etapa 3 para validar que tudo esta funcionando corretamente');
    console.log('  4. Apos validar, utilize o client_id, client_secret, certificado e chave privada na sua aplicacao');
    console.log('\n  LEMBRE-SE: Client ID, Client Secret, certificado e chave privada sao equivalentes a senhas.');
    console.log('  NAO compartilhe, NAO envie por e-mail e NAO deixe exposto em codigo-fonte.');
    console.log('  O Itau NAO se responsabiliza pelo armazenamento local. A guarda e responsabilidade exclusiva do cliente.');

    const respPfx2 = (await pergunta('\nDeseja gerar o arquivo PFX (PKCS#12)? O PFX combina o certificado e a chave privada em um unico arquivo protegido por senha. (s/N): ')).trim();
    if (respPfx2.toLowerCase() === 's') {
        const senhaPfx2 = (await pergunta('  Informe a senha para proteger o PFX: ')).trim();
        if (!senhaPfx2) {
            console.log('  Senha nao informada. PFX nao gerado.');
            log('WARN', 'PFX nao gerado - senha nao informada');
        } else {
            const caminhoPfx2 = path.join(PASTA_ETAPA2, 'certificado.pfx');
            const caminhoKeyPfx2 = path.resolve(PASTA_ETAPA2, 'ARQUIVO_CHAVE_PRIVADA.key');
            execSync(`openssl pkcs12 -export -out "${caminhoPfx2}" -inkey "${caminhoKeyPfx2}" -in "${caminhoCRT}" -passout pass:"${senhaPfx2}"`);
            console.log(`  PFX gerado: ${path.resolve(caminhoPfx2)}`);
            log('INFO', `PFX gerado: ${path.resolve(caminhoPfx2)}`);
        }
    }

    return secret;
}

async function executarEtapa3(estado, pergunta, clientSecretMemoria) {
    if ((estado.etapaConcluida || 0) < 2) {
        console.log('\n[AVISO] A etapa 2 (gerar e enviar certificado) precisa ser concluida antes.');
        log('WARN', 'Tentativa de executar Etapa 3 sem concluir Etapa 2');
        return;
    }

        log('INFO', 'Inicio da Etapa 3: Testar geracao de token');
        console.log('\n[PASSO 3] Testando geracao de token...');
    const caminhoClientId = estado.caminhoClientId || path.join(PASTA_ETAPA2, 'client_id.txt');
    if (!fs.existsSync(caminhoClientId)) {
        throw new Error(`Arquivo de client_id nao encontrado: ${caminhoClientId}. Execute as etapas anteriores.`);
    }
    const clientId = fs.readFileSync(caminhoClientId, 'utf8').trim();
    let clientSecret;
    if (clientSecretMemoria) {
        clientSecret = clientSecretMemoria;
        console.log(`  Client Secret: ${mascarar(clientSecret)} (obtido da sessao atual)`);
    } else {
        clientSecret = (await pergunta('  Informe o Client Secret: ')).trim();
    }
    const caminhoCRT = estado.caminhoCRT || path.join(PASTA_ETAPA2, 'certificado.crt');
    const caminhoKey = path.resolve(PASTA_ETAPA2, 'ARQUIVO_CHAVE_PRIVADA.key');

    if (!clientId || !clientSecret) {
        throw new Error('client_id ou client_secret nao informados. Verifique e tente novamente.');
    }

    console.log('\n  Arquivos utilizados na requisicao:');
    console.log(`  - ${path.resolve(caminhoClientId)} (Client ID: ${mascarar(clientId)})`);
    console.log(`  - ${path.resolve(caminhoCRT)} (certificado assinado)`);
    console.log(`  - ${path.resolve(caminhoKey)} (chave privada do certificado)`);

    const resposta = await CertificadoDinamico.obterTokenOAuth(clientId, clientSecret, caminhoCRT, caminhoKey);
    log('INFO', 'Token OAuth obtido com sucesso');
    log('INFO', 'Etapa 3 concluida com sucesso');

    let accessToken = '';
    try {
        const dados = JSON.parse(resposta);
        accessToken = dados.access_token || '';
    } catch (e) {}

    console.log('\n==============================================');
    console.log('  Credenciais e certificado VALIDADOS!        ');
    console.log('==============================================');
    console.log('\nAccess Token:');
    console.log(accessToken || resposta);
    console.log('\n  Validade: 300 segundos (5 minutos)');
    console.log('  Uso: Informe este token no header \'Authorization: Bearer <access_token>\' das requisicoes as APIs do Itau.');
    console.log('\nAs credenciais (client_id e client_secret) e o certificado digital estao validados e funcionais.');
    console.log('Voce ja pode utiliza-los em suas integracoes.');
    console.log('\n>> Etapa 3 concluida.');
    console.log('\n  O que fazer agora:');
    console.log('  1. Suas credenciais e certificado estao validados e prontos para uso');
    console.log('  2. Na sua aplicacao, implemente o fluxo OAuth usando:');
    console.log('     - Client ID e Client Secret (da etapa 2)');
    console.log(`     - Certificado .crt e chave privada .key (da pasta ${path.resolve(PASTA_ETAPA2)})`);
    console.log('  3. O token gerado tem validade de 5 minutos - sua aplicacao deve renova-lo periodicamente');
    console.log('  4. Lembre-se de renovar o certificado antes da data de expiracao (etapa 4)');
}

function exibirValidadeRenovacao(caminhoCRT) {
    const expiracaoRenov = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000);
    const inicioRenov = new Date(expiracaoRenov.getTime() - 30 * 24 * 60 * 60 * 1000);
    const fimRenov = new Date(expiracaoRenov.getTime() - 1 * 24 * 60 * 60 * 1000);
    const fmtDataRenov = (d) => `${String(d.getDate()).padStart(2,'0')}/${String(d.getMonth()+1).padStart(2,'0')}/${d.getFullYear()}`;
    console.log('\n===============================================');
    console.log('  VALIDADE DO CERTIFICADO RENOVADO             ');
    console.log('===============================================');
    console.log('  Validade: 365 dias');
    console.log(`  Data de expiracao: ${fmtDataRenov(expiracaoRenov)}`);
    console.log(`  Periodo de renovacao: de ${fmtDataRenov(inicioRenov)} ate ${fmtDataRenov(fimRenov)}`);
    console.log('\n  O processo de renovacao (etapa 4) pode ser realizado a partir de');
    console.log('  30 dias antes da expiracao ate um dia antes da data de expiracao do certificado.');
    console.log('\n  ATENCAO: A responsabilidade de renovar o certificado dentro do prazo');
    console.log('  e exclusivamente sua. Certificados expirados impossibilitam o acesso');
    console.log('  as APIs do Itau e exigem a geracao de um novo certificado do zero.');

    log('INFO', `Certificado renovado: ${path.resolve(caminhoCRT)}`);
    log('INFO', 'Etapa 4 concluida com sucesso');
    console.log('\n>> Etapa 4 concluida. Certificado renovado.');
}

async function oferecerPfxRenovacao(caminhoCRT, caminhoKey, pergunta) {
    const respPfx = (await pergunta('\nDeseja gerar o arquivo PFX (PKCS#12)? O PFX combina o certificado e a chave privada em um unico arquivo protegido por senha. (s/N): ')).trim();
    if (respPfx.toLowerCase() === 's') {
        const senhaPfx = (await pergunta('  Informe a senha para proteger o PFX: ')).trim();
        if (!senhaPfx) {
            console.log('  Senha nao informada. PFX nao gerado.');
            log('WARN', 'PFX nao gerado - senha nao informada');
        } else {
            const caminhoPfx = path.join(PASTA_ETAPA4, 'certificado.pfx');
            execSync(`openssl pkcs12 -export -out "${caminhoPfx}" -inkey "${caminhoKey}" -in "${caminhoCRT}" -passout pass:"${senhaPfx}"`);
            console.log(`  PFX gerado: ${path.resolve(caminhoPfx)}`);
            log('INFO', `PFX gerado: ${path.resolve(caminhoPfx)}`);
        }
    }
}

async function obterTokenRenovacao(estado, pergunta, clientSecretMemoria) {
    console.log('\n=============================================');
    console.log('  Como deseja obter o token para renovacao?  ');
    console.log('=============================================');
    console.log('  1. Automatico (via mTLS com certificado atual)');
    console.log('  2. Manual (informar token manualmente)');
    const opcaoToken = (await pergunta('\nEscolha (1 ou 2): ')).trim();

    if (opcaoToken === '1') {
        log('INFO', 'Obtencao de token automatica via mTLS');
        console.log('\n[PASSO 2.1] Obtendo token automaticamente via mTLS...');
        const caminhoClientIdPadrao = estado.caminhoClientId || path.join(PASTA_ETAPA2, 'client_id.txt');
        let clientId;
        if (fs.existsSync(caminhoClientIdPadrao)) {
            clientId = fs.readFileSync(caminhoClientIdPadrao, 'utf-8').trim();
            console.log(`  Client ID encontrado: ${mascarar(clientId)}`);
        } else if (estado.clientId) {
            clientId = estado.clientId;
            console.log(`  Client ID recuperado do estado salvo: ${mascarar(clientId)}`);
        } else {
            console.log(`  [AVISO] Arquivo de Client ID nao encontrado em: ${path.resolve(caminhoClientIdPadrao)}`);
            clientId = '';
            while (!clientId) {
                clientId = (await pergunta('  Informe o Client ID: ')).trim();
                if (!clientId) console.log('  Campo obrigatorio. Informe o Client ID.');
            }
        }
        let clientSecret;
        if (clientSecretMemoria) {
            clientSecret = clientSecretMemoria;
            console.log(`  Client Secret: ${mascarar(clientSecret)} (obtido da sessao atual)`);
        } else {
            clientSecret = '';
            while (!clientSecret) {
                clientSecret = (await pergunta('  Informe o Client Secret: ')).trim();
                if (!clientSecret) console.log('  Campo obrigatorio. Informe o Client Secret.');
            }
        }
        let caminhoCRT = estado.caminhoCRT || path.join(PASTA_ETAPA2, 'certificado.crt');
        if (!fs.existsSync(caminhoCRT)) {
            console.log(`  [AVISO] Certificado nao encontrado em: ${path.resolve(caminhoCRT)}`);
            caminhoCRT = '';
            while (!caminhoCRT || !fs.existsSync(caminhoCRT)) {
                caminhoCRT = (await pergunta('  Informe o caminho do certificado (.crt): ')).trim();
                if (!caminhoCRT || !fs.existsSync(caminhoCRT)) console.log(`  Arquivo nao encontrado: ${caminhoCRT}. Informe um caminho valido.`);
            }
        }
        console.log(`  Certificado: ${path.resolve(caminhoCRT)}`);
        let caminhoKey = path.resolve(PASTA_ETAPA2, 'ARQUIVO_CHAVE_PRIVADA.key');
        if (!fs.existsSync(caminhoKey)) {
            console.log(`  [AVISO] Chave privada nao encontrada em: ${caminhoKey}`);
            caminhoKey = '';
            while (!caminhoKey || !fs.existsSync(caminhoKey)) {
                caminhoKey = (await pergunta('  Informe o caminho da chave privada (.key): ')).trim();
                if (!caminhoKey || !fs.existsSync(caminhoKey)) console.log(`  Arquivo nao encontrado: ${caminhoKey}. Informe um caminho valido.`);
            }
        }
        console.log(`  Chave privada: ${path.resolve(caminhoKey)}`);
        const resposta = await CertificadoDinamico.obterTokenOAuth(clientId, clientSecret, caminhoCRT, caminhoKey);
        let tokenSTS = '';
        try {
            const dados = JSON.parse(resposta);
            tokenSTS = dados.access_token || '';
        } catch (e) {
            tokenSTS = resposta;
        }
        if (!tokenSTS) {
            throw new Error('Nao foi possivel obter o token automaticamente. Tente a opcao manual.');
        }
        console.log(`  Token obtido com sucesso: ${mascarar(tokenSTS)}`);
        log('INFO', `Token obtido automaticamente via mTLS: ${mascarar(tokenSTS)}`);
        return { token: tokenSTS, caminhoCRT, caminhoKey };
    } else {
        log('INFO', 'Obtencao de token manual');
        const tokenSTS = (await pergunta('Token STS para renovacao: ')).trim();
        if (!tokenSTS) throw new Error('Token STS e obrigatorio para renovacao do certificado.');
        console.log('\n  Para a requisicao de renovacao, e necessario o certificado e a chave privada (mTLS).');
        let caminhoCRT = estado.caminhoCRT || path.join(PASTA_ETAPA2, 'certificado.crt');
        if (!fs.existsSync(caminhoCRT)) {
            console.log(`  [AVISO] Certificado nao encontrado em: ${path.resolve(caminhoCRT)}`);
            caminhoCRT = '';
            while (!caminhoCRT || !fs.existsSync(caminhoCRT)) {
                caminhoCRT = (await pergunta('  Informe o caminho do certificado (.crt): ')).trim();
                if (!caminhoCRT || !fs.existsSync(caminhoCRT)) console.log(`  Arquivo nao encontrado: ${caminhoCRT}. Informe um caminho valido.`);
            }
        }
        console.log(`  Certificado: ${path.resolve(caminhoCRT)}`);
        let caminhoKey = path.resolve(PASTA_ETAPA2, 'ARQUIVO_CHAVE_PRIVADA.key');
        if (!fs.existsSync(caminhoKey)) {
            console.log(`  [AVISO] Chave privada nao encontrada em: ${caminhoKey}`);
            caminhoKey = '';
            while (!caminhoKey || !fs.existsSync(caminhoKey)) {
                caminhoKey = (await pergunta('  Informe o caminho da chave privada (.key): ')).trim();
                if (!caminhoKey || !fs.existsSync(caminhoKey)) console.log(`  Arquivo nao encontrado: ${caminhoKey}. Informe um caminho valido.`);
            }
        }
        console.log(`  Chave privada: ${path.resolve(caminhoKey)}`);
        return { token: tokenSTS, caminhoCRT, caminhoKey };
    }
}

async function executarEtapa4(estado, pergunta, clientSecretMemoria) {
    log('INFO', 'Inicio da Etapa 4: Renovar certificado');
    if (!fs.existsSync(PASTA_ETAPA4)) fs.mkdirSync(PASTA_ETAPA4, { recursive: true });

    const caminhoCRTValidacao = estado.caminhoCRT || path.join(PASTA_ETAPA2, 'certificado.crt');
    if (fs.existsSync(caminhoCRTValidacao)) {
        try {
            const certPem = fs.readFileSync(caminhoCRTValidacao, 'utf-8');
            const certDer = certPem.replace(/-----BEGIN CERTIFICATE-----/, '').replace(/-----END CERTIFICATE-----/, '').replace(/\s/g, '');
            const certBuffer = Buffer.from(certDer, 'base64');
            const x509Cert = new crypto.X509Certificate(certBuffer);
            const dataExpiracao = new Date(x509Cert.validTo);
            const agora = new Date();
            const diasRestantes = Math.floor((dataExpiracao.getTime() - agora.getTime()) / (1000 * 60 * 60 * 24));
            const fmtData = (d) => `${String(d.getDate()).padStart(2,'0')}/${String(d.getMonth()+1).padStart(2,'0')}/${d.getFullYear()}`;
            log('INFO', `Certificado encontrado: ${path.resolve(caminhoCRTValidacao)}`);
            log('INFO', `Data de expiracao: ${fmtData(dataExpiracao)} | Dias restantes: ${diasRestantes}`);
            console.log(`\n  Certificado encontrado: ${path.resolve(caminhoCRTValidacao)}`);
            console.log(`  Data de expiracao: ${fmtData(dataExpiracao)} (${diasRestantes} dias restantes)`);
            if (diasRestantes > 30) {
                console.log(`\n  [AVISO] O certificado ainda nao esta no periodo de renovacao.`);
                console.log(`  A renovacao e permitida nos ultimos 30 dias antes da expiracao.`);
                console.log(`  Faltam ${diasRestantes - 30} dias para o inicio do periodo de renovacao.`);
                log('WARN', `Certificado fora do periodo de renovacao. Dias restantes: ${diasRestantes}`);
                const prosseguir = (await pergunta('\n  Deseja prosseguir mesmo assim? (s/N): ')).trim().toLowerCase();
                if (prosseguir !== 's') {
                    console.log('  Renovacao cancelada pelo usuario.');
                    log('INFO', 'Renovacao cancelada pelo usuario - fora do periodo');
                    return;
                }
                log('INFO', 'Usuario optou por prosseguir com a renovacao fora do periodo');
            } else if (diasRestantes < 0) {
                console.log(`\n  [AVISO] O certificado ja esta EXPIRADO ha ${Math.abs(diasRestantes)} dias.`);
                log('WARN', `Certificado expirado ha ${Math.abs(diasRestantes)} dias`);
            } else {
                console.log('  Certificado dentro do periodo de renovacao.');
            }
        } catch (e) {
            log('WARN', `Nao foi possivel ler o certificado para validacao: ${e.message}`);
            console.log('\n  [AVISO] Nao foi possivel ler o certificado para validar a data de expiracao.');
            console.log('  Prosseguindo com a renovacao...');
        }
    } else {
        log('INFO', 'Certificado nao encontrado para validacao de periodo. Prosseguindo com a renovacao.');
        console.log(`\n  [INFO] Certificado nao encontrado em: ${path.resolve(caminhoCRTValidacao)}`);
        console.log('  Nao foi possivel validar o periodo de renovacao (30 dias antes da expiracao).');
        console.log('  Prosseguindo com a renovacao...');
    }

    console.log('\n=============================================');
    console.log('  Renovacao de Certificado - Escolha a opcao ');
    console.log('=============================================');
    console.log('\n  A. Renovacao completa (gera novo CSR e nova chave privada) - recomendada');
    console.log('     - Gera nova chave privada e novo CSR antes de renovar');
    console.log('     - Recomendado para maior seguranca ou quando a chave pode ter sido comprometida');
    console.log('\n  B. Renovacao simples (reutiliza o CSR existente)');
    console.log('     - Mais rapido, mantem a mesma chave privada do certificado');
    console.log('     - Recomendado quando nao ha necessidade de trocar a chave');

    const opcao = (await pergunta('\nEscolha a opcao (A ou B): ')).trim().toUpperCase();
    if (opcao !== 'A' && opcao !== 'B') {
        console.log('Opcao invalida. Escolha A ou B.');
        return;
    }

    if (opcao === 'A') {
        log('INFO', 'Opcao A: Renovacao completa (novo CSR e nova chave privada)');
        console.log('\n[PASSO 1] Gerando nova chave privada e novo CSR...');
        const caminhoClientId = estado.caminhoClientId || path.join(PASTA_ETAPA2, 'client_id.txt');
        let clientId;
        if (fs.existsSync(caminhoClientId)) {
            clientId = fs.readFileSync(caminhoClientId, 'utf-8').trim();
        } else if (estado.clientId) {
            clientId = estado.clientId;
            console.log('  Client ID recuperado do estado salvo.');
        } else {
            console.log('  Nao foi possivel recuperar o Client ID automaticamente.');
            console.log('  Informe o Client ID manualmente para continuar.');
            clientId = '';
            while (!clientId) {
                clientId = (await pergunta('Client ID: ')).trim();
                if (!clientId) console.log('  Campo obrigatorio. Informe o Client ID.');
            }
        }
        console.log(`  Client ID: ${mascarar(clientId)}`);

        let organizacao = '';
        while (!organizacao) {
            organizacao = (await pergunta('Nome da Empresa: ')).trim();
            if (!organizacao) console.log('  Campo obrigatorio. Informe o nome da empresa.');
        }
        let cidade = '';
        while (!cidade) {
            cidade = (await pergunta('Cidade: ')).trim();
            if (!cidade) console.log('  Campo obrigatorio. Informe a cidade.');
        }
        let estadoUF = '';
        while (!estadoUF) {
            estadoUF = (await pergunta('Estado - UF: ')).trim();
            if (!estadoUF) console.log('  Campo obrigatorio. Informe o estado (UF).');
        }
        let pais = '';
        while (!pais) {
            pais = (await pergunta('Pais: ')).trim();
            if (!pais) console.log('  Campo obrigatorio. Informe o pais.');
        }

        const caminhoCSR = path.join(PASTA_ETAPA4, 'ARQUIVO_REQUEST_CERTIFICADO.csr');
        const caminhoChaveCSR = path.join(PASTA_ETAPA4, 'ARQUIVO_CHAVE_PRIVADA.key');
        CertificadoDinamico.gerarCSR(clientId, organizacao, cidade, estadoUF, pais, caminhoCSR, caminhoChaveCSR);
        log('INFO', `Novo CSR gerado: ${path.resolve(caminhoCSR)}`);
        log('INFO', `Nova chave privada gerada: ${path.resolve(caminhoChaveCSR)}`);

        console.log('\n[PASSO 2] Obtendo token para renovacao...');
        const resultadoToken = await obterTokenRenovacao(estado, pergunta, clientSecretMemoria);
        const tokenSTS = resultadoToken.token;
        const caminhoCertMtls = resultadoToken.caminhoCRT;
        const caminhoKeyMtls = resultadoToken.caminhoKey;

        console.log('\n[PASSO 3] Enviando novo CSR para renovacao...');
        const caminhoCRT = path.join(PASTA_ETAPA4, 'certificado.crt');
        log('INFO', `Token STS: ${mascarar(tokenSTS)}`);
        log('INFO', `CSR: ${path.resolve(caminhoCSR)}`);
        log('INFO', 'Enviando requisicao para https://sts.itau.com.br/seguranca/v2/certificado/renovacao');
        await CertificadoDinamico.renovarCertificado(tokenSTS, caminhoCSR, caminhoCRT, caminhoCertMtls, caminhoKeyMtls);

        exibirValidadeRenovacao(caminhoCRT);
        console.log('\n  O que fazer agora:');
        console.log(`  1. O novo certificado esta salvo na pasta ${path.resolve(PASTA_ETAPA4)}`);
        console.log('  2. Substitua o certificado antigo E a chave privada pelos novos na sua aplicacao');
        console.log(`  3. A nova chave privada (.key) esta em ${path.resolve(caminhoChaveCSR)}`);
        console.log('  4. Teste a geracao de token (etapa 3) para confirmar que o novo certificado esta funcionando');

        await oferecerPfxRenovacao(caminhoCRT, path.resolve(caminhoChaveCSR), pergunta);

    } else {
        log('INFO', 'Opcao B: Renovacao simples (reutiliza CSR existente)');
        console.log('\n[PASSO 1] Localizando CSR existente...');
        let caminhoCSR = estado.caminhoCSR || '';
        if (!caminhoCSR || !fs.existsSync(caminhoCSR)) {
            const caminhoCSRPadrao = path.resolve(PASTA_ETAPA2, 'ARQUIVO_REQUEST_CERTIFICADO.csr');
            if (fs.existsSync(caminhoCSRPadrao)) {
                caminhoCSR = caminhoCSRPadrao;
            } else {
                caminhoCSR = (await pergunta('Caminho do arquivo CSR: ')).trim();
                if (!caminhoCSR || !fs.existsSync(caminhoCSR)) throw new Error(`Arquivo CSR nao encontrado: ${caminhoCSR}`);
            }
        }
        console.log(`  CSR encontrado: ${path.resolve(caminhoCSR)}`);

        console.log('\n[PASSO 2] Obtendo token para renovacao...');
        const resultadoTokenB = await obterTokenRenovacao(estado, pergunta, clientSecretMemoria);
        const tokenSTS = resultadoTokenB.token;
        const caminhoCertMtlsB = resultadoTokenB.caminhoCRT;
        const caminhoKeyMtlsB = resultadoTokenB.caminhoKey;

        console.log('\n[PASSO 3] Enviando CSR para renovacao...');
        const caminhoCRT = path.join(PASTA_ETAPA4, 'certificado.crt');
        log('INFO', `Token STS: ${mascarar(tokenSTS)}`);
        log('INFO', `CSR: ${path.resolve(caminhoCSR)}`);
        log('INFO', 'Enviando requisicao para https://sts.itau.com.br/seguranca/v2/certificado/renovacao');
        await CertificadoDinamico.renovarCertificado(tokenSTS, caminhoCSR, caminhoCRT, caminhoCertMtlsB, caminhoKeyMtlsB);

        exibirValidadeRenovacao(caminhoCRT);
        console.log('\n  O que fazer agora:');
        console.log(`  1. O novo certificado esta salvo na pasta ${path.resolve(PASTA_ETAPA4)}`);
        console.log('  2. Substitua o certificado antigo pelo novo na sua aplicacao');
        console.log('  3. A chave privada (.key) da etapa 2 continua sendo a mesma - nao muda na renovacao');
        console.log('  4. Teste a geracao de token (etapa 3) para confirmar que o novo certificado esta funcionando');

        const caminhoKeyPfx = path.resolve(PASTA_ETAPA2, 'ARQUIVO_CHAVE_PRIVADA.key');
        await oferecerPfxRenovacao(caminhoCRT, caminhoKeyPfx, pergunta);
    }
}

function statusEtapa(n, etapaConcluida) {
    if (n <= etapaConcluida) return '[CONCLUIDA]';
    if (n === etapaConcluida + 1) return '[PROXIMA >>] <------ voce esta aqui';
    return '[PENDENTE]';
}

function exibirMenu(estado) {
    const etapaConcluida = estado.etapaConcluida || 0;

    console.log('\n=============================================');
    console.log('  Certificado Dinamico Itau - Menu Principal ');
    console.log('=============================================');
    console.log(`\n  Progresso atual: Etapa ${etapaConcluida} de 3 concluida(s)\n`);

    console.log(`  1. ${ETAPAS[1]} ${statusEtapa(1, etapaConcluida)}`);

    console.log(`  2. ${ETAPAS[2]} ${statusEtapa(2, etapaConcluida)}`);

    console.log(`  3. ${ETAPAS[3]} ${statusEtapa(3, etapaConcluida)}`);

    console.log('  4. Renovar certificado');

    console.log('\n  0. Sair');
    console.log('  9. Recomecar do zero (limpa progresso)');

    return etapaConcluida + 1 <= 3 ? etapaConcluida + 1 : 3;
}

async function main() {
    const arquivoLog = configurarLog();
    log('INFO', 'Programa iniciado');
    console.log(`  Log desta execucao: ${arquivoLog}`);

    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    });

    const pergunta = (texto) => new Promise((resolve) => rl.question(texto, resolve));

    try {
        let estado = carregarEstado();
        let clientSecretMemoria = '';
        while (true) {
            const proxima = exibirMenu(estado);

            const escolhaStr = (await pergunta(`\nEscolha a etapa (padrao: ${proxima}): `)).trim();
            const escolha = escolhaStr === '' ? proxima : parseInt(escolhaStr);
            log('INFO', `Opcao escolhida pelo usuario: ${escolha}`);

            if (escolha === 0) {
                log('INFO', 'Programa encerrado pelo usuario');
                console.log('\nSaindo. Seu progresso foi salvo.');
                break;
            } else if (escolha === 9) {
                log('INFO', 'Limpeza de progresso e arquivos gerados iniciada');
                for (const pasta of [PASTA_ETAPA1, PASTA_ETAPA2, PASTA_ETAPA4]) {
                    if (fs.existsSync(pasta)) {
                        for (const arquivo of fs.readdirSync(pasta)) {
                            const caminhoArquivo = path.join(pasta, arquivo);
                            if (fs.statSync(caminhoArquivo).isFile()) {
                                fs.unlinkSync(caminhoArquivo);
                            }
                        }
                        console.log(`  Arquivos removidos da pasta: ${path.resolve(pasta)}`);
                        log('INFO', `Arquivos removidos da pasta: ${path.resolve(pasta)}`);
                    }
                }
                if (fs.existsSync(ARQUIVO_ESTADO)) {
                    fs.unlinkSync(ARQUIVO_ESTADO);
                    console.log(`  Removido: ${path.resolve(ARQUIVO_ESTADO)}`);
                    log('INFO', `Removido: ${path.resolve(ARQUIVO_ESTADO)}`);
                }
                console.log('\nProgresso e arquivos gerados removidos.');
                log('INFO', 'Limpeza concluida');
                estado = {};
                clientSecretMemoria = '';
                continue;
            } else if (isNaN(escolha)) {
                console.log('Opcao invalida.');
                continue;
            }

            try {
                if (escolha === 1) {
                    executarEtapa1(estado);
                } else if (escolha === 2) {
                    const secret = await executarEtapa2(estado, pergunta);
                    if (secret) clientSecretMemoria = secret;
                } else if (escolha === 3) {
                    await executarEtapa3(estado, pergunta, clientSecretMemoria);
                } else if (escolha === 4) {
                    await executarEtapa4(estado, pergunta, clientSecretMemoria);
                } else {
                    console.log('Opcao invalida.');
                    continue;
                }
            } catch (error) {
                log('ERROR', `${error.name}: ${error.message}`);
                log('WARN', 'Etapa NAO foi marcada como concluida devido ao erro');
                console.log(`\n[ERRO] ${error.name}: ${error.message}`);
                console.log('A etapa NAO foi marcada como concluida. Verifique o erro acima e tente novamente. Caso persista entre em contato com o suporte.');
            }

            await pergunta('\nPressione ENTER para voltar ao menu...');
        }
    } finally {
        if (logStream) logStream.end();
        rl.close();
    }
}

main();

import base64
import json
import logging
import os
import shutil
import sys

from datetime import datetime

from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.padding import PKCS7
from cryptography.x509.oid import NameOID

import requests

class CertificadoDinamico:

    @staticmethod
    def gerar_par_de_chaves(
        caminho_privada: str = "private.pem",
        caminho_publica: str = "public.pem",
    ) -> rsa.RSAPrivateKey:
        private_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=2048,
        )

        private_pem = private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption(),
        )
        with open(caminho_privada, "wb") as f:
            f.write(private_pem)
        print(f"Chave privada salva em {os.path.abspath(caminho_privada)}")

        public_key = private_key.public_key()
        public_pem = public_key.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo,
        )
        with open(caminho_publica, "wb") as f:
            f.write(public_pem)
        print(f"Chave publica salva em {os.path.abspath(caminho_publica)}")

        return private_key

    @staticmethod
    def _carregar_chave_privada(caminho_chave_privada: str) -> rsa.RSAPrivateKey:
        with open(caminho_chave_privada, "rb") as f:
            return serialization.load_pem_private_key(f.read(), password=None)

    @staticmethod
    def _descriptografar_rsa(
        caminho_chave_privada: str, dados_cifrados_base64: str
    ) -> bytes:
        private_key = CertificadoDinamico._carregar_chave_privada(caminho_chave_privada)
        dados_cifrados = base64.b64decode(dados_cifrados_base64)
        return private_key.decrypt(dados_cifrados, padding.PKCS1v15())

    @staticmethod
    def _descriptografar_aes(chave_aes: bytes, texto_cifrado_base64: str) -> str:
        texto_cifrado = base64.b64decode(texto_cifrado_base64)
        iv = bytes(16)

        cipher = Cipher(algorithms.AES(chave_aes), modes.CBC(iv))
        decryptor = cipher.decryptor()
        texto_decifrado = decryptor.update(texto_cifrado) + decryptor.finalize()

        unpadder = PKCS7(128).unpadder()
        texto_decifrado = unpadder.update(texto_decifrado) + unpadder.finalize()

        return texto_decifrado.decode("utf-8")

    @staticmethod
    def descriptografar_credenciais(
        client_id_cifrado: str,
        token_cifrado: str,
        chave_sessao_cifrada: str,
        caminho_chave_privada: str,
    ) -> tuple:
        print("\n=========================================")
        print("    Processo de Descriptografia           ")
        print("=========================================")

        chave_sessao_decifrada = CertificadoDinamico._descriptografar_rsa(
            caminho_chave_privada, chave_sessao_cifrada
        )

        client_id_decifrado = CertificadoDinamico._descriptografar_aes(
            chave_sessao_decifrada, client_id_cifrado
        )
        print(
            f"\nClient ID decifrado com a chave de sessao AES:\n[ {mascarar(client_id_decifrado)} ]"
        )

        token_decifrado = CertificadoDinamico._descriptografar_aes(
            chave_sessao_decifrada, token_cifrado
        )
        print(
            f"\nToken decifrado com a chave de sessao AES:\n[ {mascarar(token_decifrado)} ]"
        )

        return client_id_decifrado, token_decifrado

    @staticmethod
    def gerar_csr(
        client_id: str,
        organizacao: str,
        cidade: str,
        estado: str,
        pais: str,
        caminho_csr: str = "ARQUIVO_REQUEST_CERTIFICADO.csr",
        caminho_chave_privada_csr: str = "ARQUIVO_CHAVE_PRIVADA.key",
    ) -> None:
        chave = rsa.generate_private_key(public_exponent=65537, key_size=2048)

        with open(caminho_chave_privada_csr, "wb") as f:
            f.write(chave.private_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PrivateFormat.PKCS8,
                encryption_algorithm=serialization.NoEncryption()
            ))
        print(f"\nChave privada do CSR salva em {os.path.abspath(caminho_chave_privada_csr)}")

        csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
            x509.NameAttribute(NameOID.COMMON_NAME, client_id),
            x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, organizacao),
            x509.NameAttribute(NameOID.LOCALITY_NAME, cidade),
            x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, estado),
            x509.NameAttribute(NameOID.COUNTRY_NAME, pais),
        ])).sign(chave, hashes.SHA512())

        with open(caminho_csr, "wb") as f:
            f.write(csr.public_bytes(serialization.Encoding.PEM))
        print(f"CSR salvo em {os.path.abspath(caminho_csr)}")

    @staticmethod
    def enviar_certificado(
        token: str,
        caminho_csr: str = "ARQUIVO_REQUEST_CERTIFICADO.csr",
        caminho_crt: str = "certificado.crt",
    ) -> str:
        with open(caminho_csr, "r") as f:
            conteudo_csr = f.read()

        url = "https://sts.itau.com.br/seguranca/v1/certificado/solicitacao"
        headers = {
            "Authorization": f"Bearer {token}",
            "Content-Type": "text/plain",
        }

        logger = logging.getLogger("certificado_dinamico")
        logger.info("=== DETALHES DA REQUISICAO HTTP (Enviar CSR) ===")
        logger.info(f"URL: {url}")
        logger.info("Metodo: POST")
        logger.info(f"Headers: Content-Type=text/plain, Authorization=Bearer {mascarar(token)}")
        logger.info(f"Body (CSR): {conteudo_csr[:200]}..." if len(conteudo_csr) > 200 else f"Body (CSR): {conteudo_csr}")
        logger.info(f"Tamanho do body: {len(conteudo_csr)} bytes")
        logger.info("verify_ssl: False")
        response = requests.post(url, data=conteudo_csr, headers=headers, verify=False)

        logger.info("=== DETALHES DA RESPOSTA HTTP (Enviar CSR) ===")
        logger.info(f"Status code: {response.status_code}")
        logger.info(f"Response headers: {dict(response.headers)}")
        corpo_resp = response.text[:500] if len(response.text) > 500 else response.text
        logger.info(f"Response body (primeiros 500 chars): {corpo_resp}")

        if response.status_code != 200:
            detalhes = _detalhar_erro_http(response.status_code)
            raise Exception(
                f"Erro na geracao do certificado. Status: {response.status_code}\n"
                f"  Possivel causa: {detalhes}\n"
                f"  Resposta do servidor: {response.text[:500]}"
            )

        linhas = response.text.split("\n")
        secret = ""
        crt_content = []

        import re
        uuid_pattern = re.compile(r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}')
        for linha in linhas:
            if "Secret:" in linha:
                match = uuid_pattern.search(linha)
                secret = match.group() if match else linha
            else:
                crt_content.append(linha)

        with open(caminho_crt, "w") as f:
            f.write("\n".join(crt_content))

        print(f"\nCertificado salvo em {os.path.abspath(caminho_crt)}")
        print(f"Client Secret: {mascarar(secret)}")

        return secret

    @staticmethod
    def obter_token_oauth(
        client_id: str,
        client_secret: str,
        caminho_crt: str,
        caminho_key: str,
    ) -> str:
        url = "https://sts.itau.com.br/api/oauth/token"
        headers = {
            "Content-Type": "application/x-www-form-urlencoded",
        }
        body = f"grant_type=client_credentials&client_id={client_id}&client_secret={client_secret}"

        logger = logging.getLogger("certificado_dinamico")
        logger.info("=== DETALHES DA REQUISICAO HTTP (Token OAuth) ===")
        logger.info(f"URL: {url}")
        logger.info("Metodo: POST")
        logger.info("Headers: Content-Type=application/x-www-form-urlencoded")
        logger.info(f"Certificado mTLS: {caminho_crt}")
        logger.info(f"Chave mTLS: {caminho_key}")
        logger.info(f"Body: grant_type=client_credentials&client_id={mascarar(client_id)}&client_secret={mascarar(client_secret)}")
        logger.info("verify_ssl: False")

        response = requests.post(
            url, data=body, headers=headers,
            cert=(caminho_crt, caminho_key), verify=False
        )

        logger.info("=== DETALHES DA RESPOSTA HTTP (Token OAuth) ===")
        logger.info(f"Status code: {response.status_code}")
        logger.info(f"Response headers: {dict(response.headers)}")
        corpo_resp = response.text[:500] if len(response.text) > 500 else response.text
        logger.info(f"Response body (primeiros 500 chars): {corpo_resp}")

        if response.status_code != 200:
            detalhes = _detalhar_erro_http(response.status_code)
            raise Exception(
                f"Erro ao obter token OAuth. Status: {response.status_code}\n"
                f"  Possivel causa: {detalhes}\n"
                f"  Resposta do servidor: {response.text[:500]}"
            )

        return response.text

    @staticmethod
    def renovar_certificado(
        token_sts: str,
        caminho_csr: str = "ARQUIVO_REQUEST_CERTIFICADO.csr",
        caminho_crt: str = "certificado.crt",
        caminho_cert_mtls: str = "",
        caminho_key_mtls: str = "",
    ) -> str:
        with open(caminho_csr, "r") as f:
            conteudo_csr = f.read()

        import uuid
        url = "https://sts.itau.com.br/seguranca/v2/certificado/renovacao"
        correlation_id = str(uuid.uuid4())
        headers = {
            "Authorization": f"Bearer {token_sts}",
            "Content-Type": "text/plain",
            "x-itau-correlationID": correlation_id,
        }

        logger = logging.getLogger("certificado_dinamico")
        logger.info("=== DETALHES DA REQUISICAO HTTP (Renovacao) ===")
        logger.info(f"URL: {url}")
        logger.info("Metodo: POST")
        logger.info(f"Headers: Content-Type=text/plain, Authorization=Bearer {mascarar(token_sts)}, x-itau-correlationID={correlation_id}")
        logger.info(f"Body (CSR): {conteudo_csr[:200]}..." if len(conteudo_csr) > 200 else f"Body (CSR): {conteudo_csr}")
        logger.info(f"Tamanho do body: {len(conteudo_csr)} bytes")
        logger.info("verify_ssl: False")
        cert_param = None
        if caminho_cert_mtls and caminho_key_mtls:
            cert_param = (caminho_cert_mtls, caminho_key_mtls)
            logger.info(f"mTLS cert: {caminho_cert_mtls}")
            logger.info(f"mTLS key: {caminho_key_mtls}")
        response = requests.post(url, data=conteudo_csr, headers=headers, cert=cert_param, verify=False)

        logger.info("=== DETALHES DA RESPOSTA HTTP (Renovacao) ===")
        logger.info(f"Status code: {response.status_code}")
        logger.info(f"Response headers: {dict(response.headers)}")
        corpo_resp = response.text[:500] if len(response.text) > 500 else response.text
        logger.info(f"Response body (primeiros 500 chars): {corpo_resp}")

        if response.status_code != 200:
            detalhes = _detalhar_erro_http(response.status_code)
            raise Exception(
                f"Erro na renovacao do certificado. Status: {response.status_code}\n"
                f"  Possivel causa: {detalhes}\n"
                f"  Resposta do servidor: {response.text[:500]}"
            )

        with open(caminho_crt, "w") as f:
            f.write(response.text)

        print(f"\nCertificado renovado salvo em {os.path.abspath(caminho_crt)}")
        return response.text

_DIR_SCRIPT = os.path.dirname(os.path.abspath(__file__))
_DIR_RAIZ = os.path.dirname(_DIR_SCRIPT)  # sobe de python/ para certificado-dinamico/

PASTA_SAIDA = os.path.join(_DIR_RAIZ, "output")
PASTA_ETAPA1 = os.path.join(PASTA_SAIDA, "etapa1")
PASTA_ETAPA2 = os.path.join(PASTA_SAIDA, "etapa2")
PASTA_ETAPA4 = os.path.join(PASTA_SAIDA, "etapa4")
ARQUIVO_ESTADO = os.path.join(PASTA_SAIDA, "estado_certificado.json")
PASTA_LOGS = os.path.join(PASTA_SAIDA, "logs")

def gerar_pfx(caminho_crt, caminho_key, caminho_pfx, senha):
    from cryptography.hazmat.primitives.serialization import pkcs12, BestAvailableEncryption
    with open(caminho_crt, "rb") as f:
        cert = x509.load_pem_x509_certificate(f.read())
    with open(caminho_key, "rb") as f:
        chave_privada = serialization.load_pem_private_key(f.read(), password=None)
    pfx_data = pkcs12.serialize_key_and_certificates(
        name=b"certificado",
        key=chave_privada,
        cert=cert,
        cas=None,
        encryption_algorithm=BestAvailableEncryption(senha.encode("utf-8")),
    )
    os.makedirs(os.path.dirname(caminho_pfx), exist_ok=True)
    with open(caminho_pfx, "wb") as f:
        f.write(pfx_data)

def _detalhar_erro_http(status_code):
    descricoes = {
        400: "Requisicao invalida. O CSR pode estar malformado ou os dados enviados estao incorretos.",
        401: "Nao autorizado. O token pode estar invalido, expirado ou nao foi informado corretamente. Tente executar novamente a partir da etapa 2.",
        403: "Acesso negado. O token nao tem permissao para esta operacao ou esta expirado. Tente executar novamente a partir da etapa 2 para obter um novo token.",
        404: "Endpoint nao encontrado. Verifique se a URL do STS Itau esta correta.",
        500: "Erro interno do servidor STS Itau. Tente novamente mais tarde.",
        502: "Bad Gateway. O servidor STS Itau esta temporariamente indisponivel. Tente novamente mais tarde.",
        503: "Servico indisponivel. O servidor STS Itau esta em manutencao. Tente novamente mais tarde.",
    }
    return descricoes.get(status_code, f"Erro HTTP {status_code}. Verifique a documentacao do STS Itau.")

def mascarar(valor):
    if not valor or not isinstance(valor, str):
        return "******"
    valor = valor.strip()
    if len(valor) <= 6:
        return "******"
    return valor[:3] + "******" + valor[-3:]

def configurar_log():
    os.makedirs(PASTA_LOGS, exist_ok=True)
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    arquivo_log = os.path.join(PASTA_LOGS, f"certificado_dinamico_{timestamp}.log")

    logger = logging.getLogger("certificado_dinamico")
    logger.setLevel(logging.INFO)
    logger.handlers.clear()

    fh = logging.FileHandler(arquivo_log, encoding="utf-8")
    fh.setLevel(logging.INFO)
    formatter = logging.Formatter(
        "%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
    )
    fh.setFormatter(formatter)
    logger.addHandler(fh)

    return logger, arquivo_log

ETAPAS = {
    1: "Gerar par de chaves e exibir chave publica",
    2: "Descriptografar credenciais, gerar e enviar certificado",
    3: "Testar geracao de token",
}

def carregar_estado():
    for pasta in [PASTA_SAIDA, PASTA_ETAPA1, PASTA_ETAPA2, PASTA_ETAPA4, PASTA_LOGS]:
        os.makedirs(pasta, exist_ok=True)
    if os.path.exists(ARQUIVO_ESTADO):
        with open(ARQUIVO_ESTADO, "r", encoding="utf-8") as f:
            estado = json.load(f)
        return _migrar_estado_legado(estado)
    return {"etapaConcluida": 0}

# Aliases legados (snake_case) suportados apenas para leitura, mantendo retrocompatibilidade
# com arquivos estado_certificado.json gerados por versoes antigas em Python.
_ALIASES_LEGADOS = {
    "etapa_concluida": "etapaConcluida",
    "caminho_chave_privada": "caminhoChavePrivada",
    "caminho_chave_publica": "caminhoChavePublica",
    "caminho_client_id": "caminhoClientId",
    "caminho_csr": "caminhoCSR",
    "caminho_crt": "caminhoCRT",
}

def _migrar_estado_legado(estado):
    if not isinstance(estado, dict):
        return {"etapaConcluida": 0}
    for chave_legada, chave_nova in _ALIASES_LEGADOS.items():
        if chave_legada in estado and chave_nova not in estado:
            estado[chave_nova] = estado.pop(chave_legada)
    try:
        estado["etapaConcluida"] = int(estado.get("etapaConcluida", 0))
    except (TypeError, ValueError):
        estado["etapaConcluida"] = 0
    return estado

def _estado_get(estado, chave, padrao=None):
    if chave in estado:
        return estado[chave]
    for legada, nova in _ALIASES_LEGADOS.items():
        if nova == chave and legada in estado:
            return estado[legada]
    return padrao

def salvar_estado(estado):
    with open(ARQUIVO_ESTADO, "w", encoding="utf-8") as f:
        json.dump(estado, f, indent=2, ensure_ascii=False)

def _status_etapa(num, etapa_concluida):
    if num <= etapa_concluida:
        return "[CONCLUIDA]"
    elif num == etapa_concluida + 1:
        return "[PROXIMA >>] <------ voce esta aqui"
    return "[PENDENTE]"

def exibir_menu(estado):
    etapa_concluida = int(_estado_get(estado, "etapaConcluida", 0) or 0)

    print("\n=============================================")
    print("  Certificado Dinamico Itau - Menu Principal ")
    print("=============================================")
    print(f"\n  Progresso atual: Etapa {etapa_concluida} de 3 concluida(s)\n")

    print(f"  1. {ETAPAS[1]} {_status_etapa(1, etapa_concluida)}")

    print(f"  2. {ETAPAS[2]} {_status_etapa(2, etapa_concluida)}")

    print(f"  3. {ETAPAS[3]} {_status_etapa(3, etapa_concluida)}")

    print("  4. Renovar certificado")

    print("\n  0. Sair")
    print("  9. Recomecar do zero (limpa progresso)")

    proxima = etapa_concluida + 1 if etapa_concluida < 3 else 3
    escolha = input(f"\nEscolha a etapa (padrao: {proxima}): ").strip()

    if escolha == "":
        return proxima
    if escolha == "9":
        return 9
    if escolha == "0":
        return 0
    try:
        return int(escolha)
    except ValueError:
        print("Opcao invalida.")
        return -1

def executar_etapa_1(estado):
    logger = logging.getLogger("certificado_dinamico")
    logger.info("Inicio da Etapa 1: Gerar par de chaves e exibir chave publica")
    os.makedirs(PASTA_ETAPA1, exist_ok=True)
    print("\n[PASSO 1.1] Gerando par de chaves RSA 2048...")
    caminho_privada = os.path.join(PASTA_ETAPA1, "private.pem")
    caminho_publica = os.path.join(PASTA_ETAPA1, "public.pem")
    CertificadoDinamico.gerar_par_de_chaves(caminho_privada, caminho_publica)

    logger.info(f"Chave privada gerada: {os.path.abspath(caminho_privada)}")
    logger.info(f"Chave publica gerada: {os.path.abspath(caminho_publica)}")

    print("\n[PASSO 1.2] Exibindo chave publica...")
    with open(caminho_publica, "r") as f:
        conteudo_chave_publica = f.read()
    print("\n=============================================")
    print("  Chave Publica Gerada                       ")
    print("=============================================")
    print(conteudo_chave_publica)

    estado["etapaConcluida"] = max(int(_estado_get(estado, "etapaConcluida", 0) or 0), 1)
    estado["caminhoChavePrivada"] = os.path.abspath(caminho_privada)
    estado["caminhoChavePublica"] = os.path.abspath(caminho_publica)
    salvar_estado(estado)
    logger.info("Etapa 1 concluida com sucesso")
    print("\n>> Etapa 1 concluida. Progresso salvo.")
    print("\n  O que fazer agora:")
    print("  1. Copie o conteudo da chave publica exibido acima")
    print("  2. Envie a chave publica para o Analista de operacoes Itau")
    print("  3. Aguarde o e-mail do Itau com as credenciais cifradas")
    print("  4. Quando receber o e-mail, execute a etapa 2")

def executar_etapa_2(estado, client_secret_memoria=""):
    logger = logging.getLogger("certificado_dinamico")
    if int(_estado_get(estado, "etapaConcluida", 0) or 0) < 1:
        print("\n[AVISO] A etapa 1 (gerar chaves) precisa ser concluida antes.")
        logger.warning("Tentativa de executar Etapa 2 sem concluir Etapa 1")
        return

    logger.info("Inicio da Etapa 2: Descriptografar credenciais, gerar e enviar certificado")
    os.makedirs(PASTA_ETAPA2, exist_ok=True)

    print("\n[PASSO 2.1] Descriptografar credenciais recebidas por e-mail...")
    caminho_chave_privada = os.path.abspath(_estado_get(estado, "caminhoChavePrivada", "private.pem"))
    print(f"  Chave privada: {os.path.abspath(caminho_chave_privada)}")
    logger.info(f"Chave privada utilizada: {os.path.abspath(caminho_chave_privada)}")

    client_id_cifrado = input("Client ID cifrado: ").strip()
    token_cifrado = input("Token temporario cifrado: ").strip()
    chave_sessao_cifrada = input("Chave de sessao cifrada: ").strip()
    logger.info(f"Client ID cifrado: {mascarar(client_id_cifrado)}")
    logger.info(f"Token cifrado: {mascarar(token_cifrado)}")
    logger.info(f"Chave de sessao cifrada: {mascarar(chave_sessao_cifrada)}")

    client_id, token_decifrado = CertificadoDinamico.descriptografar_credenciais(
        client_id_cifrado, token_cifrado, chave_sessao_cifrada, caminho_chave_privada
    )

    caminho_client_id = os.path.join(PASTA_ETAPA2, "client_id.txt")
    with open(caminho_client_id, "w", encoding="utf-8") as f:
        f.write(client_id)
    print(f"  Client ID salvo em {os.path.abspath(caminho_client_id)}")
    logger.info(f"Client ID decifrado: {mascarar(client_id)}")

    caminho_token = os.path.join(PASTA_ETAPA2, "token_temporario.txt")
    with open(caminho_token, "w", encoding="utf-8") as f:
        f.write(token_decifrado)
    print(f"  Token temporario salvo em {os.path.abspath(caminho_token)}")
    logger.info(f"Token decifrado: {mascarar(token_decifrado)}")

    print("\n[PASSO 2.2] Gerando CSR (Certificate Sign Request)...")
    print(f"  Client ID: {mascarar(client_id)}")

    organizacao = ""
    while not organizacao:
        organizacao = input("Nome da Empresa: ").strip()
        if not organizacao:
            print("  Campo obrigatorio. Informe o nome da empresa.")
    cidade = ""
    while not cidade:
        cidade = input("Cidade: ").strip()
        if not cidade:
            print("  Campo obrigatorio. Informe a cidade.")
    estado_uf = ""
    while not estado_uf:
        estado_uf = input("Estado - UF: ").strip()
        if not estado_uf:
            print("  Campo obrigatorio. Informe o estado (UF).")
    pais = ""
    while not pais:
        pais = input("Pais: ").strip()
        if not pais:
            print("  Campo obrigatorio. Informe o pais.")
    logger.info(f"Client ID: {mascarar(client_id)}")
    logger.info(f"Organizacao: {organizacao}, Cidade: {cidade}, Estado: {estado_uf}, Pais: {pais}")

    caminho_csr = os.path.join(PASTA_ETAPA2, "ARQUIVO_REQUEST_CERTIFICADO.csr")
    caminho_chave_csr = os.path.join(PASTA_ETAPA2, "ARQUIVO_CHAVE_PRIVADA.key")
    CertificadoDinamico.gerar_csr(client_id, organizacao, cidade, estado_uf, pais, caminho_csr, caminho_chave_csr)

    print("\n[PASSO 2.3] Enviando CSR ao STS Itau...")
    logger.info(f"Token: {mascarar(token_decifrado)}")
    logger.info(f"CSR: {os.path.abspath(caminho_csr)}")
    logger.info("Enviando requisicao para https://sts.itau.com.br/seguranca/v1/certificado/solicitacao")

    caminho_crt = os.path.join(PASTA_ETAPA2, "certificado.crt")
    secret = CertificadoDinamico.enviar_certificado(token_decifrado, caminho_csr, caminho_crt)

    print("\n=============================================")
    print("  Certificado gerado com sucesso!            ")
    print("=============================================")
    print("Arquivos gerados:")
    print(f"  - {os.path.abspath(os.path.join(PASTA_ETAPA1, 'private.pem'))} (chave privada para descriptografia)")
    print(f"  - {os.path.abspath(os.path.join(PASTA_ETAPA1, 'public.pem'))} (chave publica)")
    print(f"  - {os.path.abspath(os.path.join(PASTA_ETAPA2, 'client_id.txt'))} (Client ID)")
    print(f"  - {os.path.abspath(os.path.join(PASTA_ETAPA2, 'ARQUIVO_CHAVE_PRIVADA.key'))} (chave privada do certificado)")
    print(f"  - {os.path.abspath(os.path.join(PASTA_ETAPA2, 'ARQUIVO_REQUEST_CERTIFICADO.csr'))} (CSR)")
    print(f"  - {os.path.abspath(os.path.join(PASTA_ETAPA2, 'certificado.crt'))} (certificado assinado)")
    print("\n==============================================")
    print("  ATENCAO - CLIENT SECRET                     ")
    print("==============================================")
    print("\nO Client Secret e equivalente a uma SENHA. Trate-o com o mesmo cuidado.")
    print("O valor do Client Secret sera exibido APENAS NESTE MOMENTO.")
    print("Copie e salve em um local seguro. Este valor NAO sera armazenado pelo programa.")
    print(f"\n  Client Secret: {secret}")
    print("\n==============================================")
    print("Guarde essas informacoes em local seguro. O Client ID e o Client Secret sao equivalentes a senhas")
    print("e serao necessarios para gerar tokens OAuth e consumir as APIs do Itau.")
    print("NAO compartilhe essas credenciais. Quem tiver acesso a elas podera acessar as APIs em seu nome.")
    print("\nO Itau nao se responsabiliza pelo armazenamento local das credenciais.")
    print("A guarda correta e de responsabilidade exclusiva do cliente.")

    from datetime import datetime, timedelta
    expiracao = datetime.now() + timedelta(days=365)
    inicio_renovacao = expiracao - timedelta(days=30)
    fim_renovacao = expiracao - timedelta(days=1)
    print("\n===============================================")
    print("  VALIDADE DO CERTIFICADO                      ")
    print("===============================================")
    print("  Validade: 365 dias")
    print(f"  Data de expiracao: {expiracao.strftime('%d/%m/%Y')}")
    print(f"  Periodo de renovacao: de {inicio_renovacao.strftime('%d/%m/%Y')} ate {fim_renovacao.strftime('%d/%m/%Y')}")
    print("\n  O processo de renovacao (etapa 4) pode ser realizado a partir de")
    print("  30 dias antes da expiracao ate um dia antes da data de expiracao do certificado.")
    print("\n  ATENCAO: A responsabilidade de renovar o certificado dentro do prazo")
    print("  e exclusivamente sua. Certificados expirados impossibilitam o acesso")
    print("  as APIs do Itau e exigem a geracao de um novo certificado do zero.")

    estado["etapaConcluida"] = max(int(_estado_get(estado, "etapaConcluida", 0) or 0), 2)
    estado["caminhoClientId"] = os.path.abspath(caminho_client_id)
    estado["caminhoChavePrivada"] = caminho_chave_privada
    estado["caminhoCSR"] = os.path.abspath(caminho_csr)
    estado["caminhoCRT"] = os.path.abspath(caminho_crt)
    salvar_estado(estado)
    logger.info(f"Certificado salvo: {os.path.abspath(caminho_crt)}")
    logger.info(f"Client Secret: {mascarar(secret)}")
    logger.info("Etapa 2 concluida com sucesso")
    print("\n>> Etapa 2 concluida. Progresso salvo.")
    print("\n  O que fazer agora:")
    print("  1. Guarde o Client ID e o Client Secret em local seguro - eles sao equivalentes a SENHAS")
    print(f"  2. O certificado (.crt) e a chave privada (.key) estao salvos na pasta {os.path.abspath(PASTA_ETAPA2)}")
    print("  3. Execute a etapa 3 para validar que tudo esta funcionando corretamente")
    print("  4. Apos validar, utilize o client_id, client_secret, certificado e chave privada na sua aplicacao")
    print("\n  LEMBRE-SE: Client ID, Client Secret, certificado e chave privada sao equivalentes a senhas.")
    print("  NAO compartilhe, NAO envie por e-mail e NAO deixe exposto em codigo-fonte.")
    print("  O Itau NAO se responsabiliza pelo armazenamento local. A guarda e responsabilidade exclusiva do cliente.")

    resp_pfx = input("\nDeseja gerar o arquivo PFX (PKCS#12)? O PFX combina o certificado e a chave privada em um unico arquivo protegido por senha. (s/N): ").strip()
    if resp_pfx.lower() == "s":
        senha_pfx = input("  Informe a senha para proteger o PFX: ").strip()
        if not senha_pfx:
            print("  Senha nao informada. PFX nao gerado.")
            logger.warning("PFX nao gerado - senha nao informada")
        else:
            caminho_pfx = os.path.join(PASTA_ETAPA2, "certificado.pfx")
            caminho_key_pfx = os.path.abspath(os.path.join(PASTA_ETAPA2, "ARQUIVO_CHAVE_PRIVADA.key"))
            gerar_pfx(caminho_crt, caminho_key_pfx, caminho_pfx, senha_pfx)
            print(f"  PFX gerado: {os.path.abspath(caminho_pfx)}")
            logger.info(f"PFX gerado: {os.path.abspath(caminho_pfx)}")

    return secret

def executar_etapa_3(estado, client_secret_memoria=""):
    logger = logging.getLogger("certificado_dinamico")
    if int(_estado_get(estado, "etapaConcluida", 0) or 0) < 2:
        print("\n[AVISO] A etapa 2 (gerar e enviar certificado) precisa ser concluida antes.")
        logger.warning("Tentativa de executar Etapa 3 sem concluir Etapa 2")
        return

        logger.info("Inicio da Etapa 3: Testar geracao de token")
        print("\n[PASSO 3] Testando geracao de token...")
    caminho_client_id = _estado_get(estado, "caminhoClientId", os.path.join(PASTA_ETAPA2, "client_id.txt"))
    if not os.path.exists(caminho_client_id):
        raise Exception(f"Arquivo de client_id nao encontrado: {caminho_client_id}. Execute as etapas anteriores.")
    client_id = open(caminho_client_id, "r", encoding="utf-8").read().strip()
    if client_secret_memoria:
        client_secret = client_secret_memoria
        print(f"  Client Secret: {mascarar(client_secret)} (obtido da sessao atual)")
    else:
        client_secret = input("  Informe o Client Secret: ").strip()
    caminho_crt = _estado_get(estado, "caminhoCRT", os.path.join(PASTA_ETAPA2, "certificado.crt"))
    caminho_key = os.path.abspath(os.path.join(PASTA_ETAPA2, "ARQUIVO_CHAVE_PRIVADA.key"))

    if not client_id or not client_secret:
        raise Exception("client_id ou client_secret nao informados. Verifique e tente novamente.")

    print(f"\n  Arquivos utilizados na requisicao:")
    print(f"  - {os.path.abspath(caminho_client_id)} (Client ID: {mascarar(client_id)})")
    print(f"  - {os.path.abspath(caminho_crt)} (certificado assinado)")
    print(f"  - {os.path.abspath(caminho_key)} (chave privada do certificado)")

    resposta = CertificadoDinamico.obter_token_oauth(client_id, client_secret, caminho_crt, caminho_key)
    logger.info("Token OAuth obtido com sucesso")
    logger.info("Etapa 3 concluida com sucesso")

    access_token = ""
    try:
        dados = json.loads(resposta)
        access_token = dados.get("access_token", "")
    except Exception:
        pass

    print("\n==============================================")
    print("  Credenciais e certificado VALIDADOS!        ")
    print("==============================================")
    print("\nAccess Token:")
    print(access_token if access_token else resposta)
    print("\n  Validade: 300 segundos (5 minutos)")
    print("  Uso: Informe este token no header 'Authorization: Bearer <access_token>' das requisicoes as APIs do Itau.")
    print("\nAs credenciais (client_id e client_secret) e o certificado digital estao validados e funcionais.")
    print("Voce ja pode utiliza-los em suas integracoes.")
    print("\n>> Etapa 3 concluida.")
    print("\n  O que fazer agora:")
    print("  1. Suas credenciais e certificado estao validados e prontos para uso")
    print("  2. Na sua aplicacao, implemente o fluxo OAuth usando:")
    print("     - Client ID e Client Secret (da etapa 2)")
    print(f"     - Certificado .crt e chave privada .key (da pasta {os.path.abspath(PASTA_ETAPA2)})")
    print("  3. O token gerado tem validade de 5 minutos - sua aplicacao deve renova-lo periodicamente")
    print("  4. Lembre-se de renovar o certificado antes da data de expiracao (etapa 4)")

def _exibir_validade_renovacao(caminho_crt, logger):
    from datetime import datetime, timedelta
    expiracao = datetime.now() + timedelta(days=365)
    inicio_renovacao = expiracao - timedelta(days=30)
    fim_renovacao = expiracao - timedelta(days=1)
    print("\n===============================================")
    print("  VALIDADE DO CERTIFICADO RENOVADO             ")
    print("===============================================")
    print("  Validade: 365 dias")
    print(f"  Data de expiracao: {expiracao.strftime('%d/%m/%Y')}")
    print(f"  Periodo de renovacao: de {inicio_renovacao.strftime('%d/%m/%Y')} ate {fim_renovacao.strftime('%d/%m/%Y')}")
    print("\n  O processo de renovacao (etapa 4) pode ser realizado a partir de")
    print("  30 dias antes da expiracao ate um dia antes da data de expiracao do certificado.")
    print("\n  ATENCAO: A responsabilidade de renovar o certificado dentro do prazo")
    print("  e exclusivamente sua. Certificados expirados impossibilitam o acesso")
    print("  as APIs do Itau e exigem a geracao de um novo certificado do zero.")

    logger.info(f"Certificado renovado: {os.path.abspath(caminho_crt)}")
    logger.info("Etapa 4 concluida com sucesso")
    print("\n>> Etapa 4 concluida. Certificado renovado.")

def _oferecer_pfx_renovacao(caminho_crt, caminho_key, logger):
    resp_pfx = input("\nDeseja gerar o arquivo PFX (PKCS#12)? O PFX combina o certificado e a chave privada em um unico arquivo protegido por senha. (s/N): ").strip()
    if resp_pfx.lower() == "s":
        senha_pfx = input("  Informe a senha para proteger o PFX: ").strip()
        if not senha_pfx:
            print("  Senha nao informada. PFX nao gerado.")
            logger.warning("PFX nao gerado - senha nao informada")
        else:
            caminho_pfx = os.path.join(PASTA_ETAPA4, "certificado.pfx")
            gerar_pfx(caminho_crt, caminho_key, caminho_pfx, senha_pfx)
            print(f"  PFX gerado: {os.path.abspath(caminho_pfx)}")
            logger.info(f"PFX gerado: {os.path.abspath(caminho_pfx)}")

def _obter_token_renovacao(estado, client_secret_memoria=""):
    logger = logging.getLogger("certificado_dinamico")
    print("\n=============================================")
    print("  Como deseja obter o token para renovacao?  ")
    print("=============================================")
    print("  1. Automatico (via mTLS com certificado atual)")
    print("  2. Manual (informar token manualmente)")
    opcao_token = input("\nEscolha (1 ou 2): ").strip()

    if opcao_token == "1":
        logger.info("Obtencao de token automatica via mTLS")
        print("\n[PASSO 2.1] Obtendo token automaticamente via mTLS...")
        caminho_client_id = _estado_get(estado, "caminhoClientId", os.path.join(PASTA_ETAPA2, "client_id.txt"))
        if os.path.exists(caminho_client_id):
            client_id = open(caminho_client_id, "r", encoding="utf-8").read().strip()
            print(f"  Client ID encontrado: {mascarar(client_id)}")
        elif estado.get("clientId"):
            client_id = estado["clientId"]
            print(f"  Client ID recuperado do estado salvo: {mascarar(client_id)}")
        else:
            print(f"  [AVISO] Arquivo de Client ID nao encontrado em: {os.path.abspath(caminho_client_id)}")
            client_id = ""
            while not client_id:
                client_id = input("  Informe o Client ID: ").strip()
                if not client_id:
                    print("  Campo obrigatorio. Informe o Client ID.")
        if client_secret_memoria:
            client_secret = client_secret_memoria
            print(f"  Client Secret: {mascarar(client_secret)} (obtido da sessao atual)")
        else:
            client_secret = ""
            while not client_secret:
                client_secret = input("  Informe o Client Secret: ").strip()
                if not client_secret:
                    print("  Campo obrigatorio. Informe o Client Secret.")
        caminho_crt = _estado_get(estado, "caminhoCRT", os.path.join(PASTA_ETAPA2, "certificado.crt"))
        if not os.path.isfile(caminho_crt):
            print(f"  [AVISO] Certificado nao encontrado em: {os.path.abspath(caminho_crt)}")
            caminho_crt = ""
            while not caminho_crt or not os.path.isfile(caminho_crt):
                caminho_crt = input("  Informe o caminho do certificado (.crt): ").strip()
                if not caminho_crt or not os.path.isfile(caminho_crt):
                    print(f"  Arquivo nao encontrado: {caminho_crt}. Informe um caminho valido.")
        print(f"  Certificado: {os.path.abspath(caminho_crt)}")
        caminho_key = os.path.abspath(os.path.join(PASTA_ETAPA2, "ARQUIVO_CHAVE_PRIVADA.key"))
        if not os.path.isfile(caminho_key):
            print(f"  [AVISO] Chave privada nao encontrada em: {caminho_key}")
            caminho_key = ""
            while not caminho_key or not os.path.isfile(caminho_key):
                caminho_key = input("  Informe o caminho da chave privada (.key): ").strip()
                if not caminho_key or not os.path.isfile(caminho_key):
                    print(f"  Arquivo nao encontrado: {caminho_key}. Informe um caminho valido.")
        print(f"  Chave privada: {os.path.abspath(caminho_key)}")
        resposta = CertificadoDinamico.obter_token_oauth(client_id, client_secret, caminho_crt, caminho_key)
        import json as _json
        try:
            dados = _json.loads(resposta)
            token_sts = dados.get("access_token", "")
        except Exception:
            token_sts = resposta
        if not token_sts:
            raise Exception("Nao foi possivel obter o token automaticamente. Tente a opcao manual.")
        print(f"  Token obtido com sucesso: {mascarar(token_sts)}")
        logger.info(f"Token obtido automaticamente via mTLS: {mascarar(token_sts)}")
        return token_sts, caminho_crt, caminho_key
    else:
        logger.info("Obtencao de token manual")
        token_sts = input("Token STS para renovacao: ").strip()
        if not token_sts:
            raise Exception("Token STS e obrigatorio para renovacao do certificado.")
        print("\n  Para a requisicao de renovacao, e necessario o certificado e a chave privada (mTLS).")
        caminho_crt = _estado_get(estado, "caminhoCRT", os.path.join(PASTA_ETAPA2, "certificado.crt"))
        if not os.path.isfile(caminho_crt):
            print(f"  [AVISO] Certificado nao encontrado em: {os.path.abspath(caminho_crt)}")
            caminho_crt = ""
            while not caminho_crt or not os.path.isfile(caminho_crt):
                caminho_crt = input("  Informe o caminho do certificado (.crt): ").strip()
                if not caminho_crt or not os.path.isfile(caminho_crt):
                    print(f"  Arquivo nao encontrado: {caminho_crt}. Informe um caminho valido.")
        print(f"  Certificado: {os.path.abspath(caminho_crt)}")
        caminho_key = os.path.abspath(os.path.join(PASTA_ETAPA2, "ARQUIVO_CHAVE_PRIVADA.key"))
        if not os.path.isfile(caminho_key):
            print(f"  [AVISO] Chave privada nao encontrada em: {caminho_key}")
            caminho_key = ""
            while not caminho_key or not os.path.isfile(caminho_key):
                caminho_key = input("  Informe o caminho da chave privada (.key): ").strip()
                if not caminho_key or not os.path.isfile(caminho_key):
                    print(f"  Arquivo nao encontrado: {caminho_key}. Informe um caminho valido.")
        print(f"  Chave privada: {os.path.abspath(caminho_key)}")
        return token_sts, caminho_crt, caminho_key

def executar_etapa_4(estado, client_secret_memoria=""):
    logger = logging.getLogger("certificado_dinamico")
    logger.info("Inicio da Etapa 4: Renovar certificado")
    os.makedirs(PASTA_ETAPA4, exist_ok=True)

    from datetime import timedelta
    caminho_crt_validacao = _estado_get(estado, "caminhoCRT", os.path.join(PASTA_ETAPA2, "certificado.crt"))
    if os.path.isfile(caminho_crt_validacao):
        try:
            with open(caminho_crt_validacao, "rb") as f:
                cert_data = f.read()
            cert_obj = x509.load_pem_x509_certificate(cert_data)
            data_expiracao = cert_obj.not_valid_after_utc
            agora = datetime.now(data_expiracao.tzinfo) if data_expiracao.tzinfo else datetime.utcnow()
            dias_restantes = (data_expiracao - agora).days
            logger.info(f"Certificado encontrado: {os.path.abspath(caminho_crt_validacao)}")
            logger.info(f"Data de expiracao: {data_expiracao.strftime('%d/%m/%Y')} | Dias restantes: {dias_restantes}")
            print(f"\n  Certificado encontrado: {os.path.abspath(caminho_crt_validacao)}")
            print(f"  Data de expiracao: {data_expiracao.strftime('%d/%m/%Y')} ({dias_restantes} dias restantes)")
            if dias_restantes > 30:
                print(f"\n  [AVISO] O certificado ainda nao esta no periodo de renovacao.")
                print(f"  A renovacao e permitida nos ultimos 30 dias antes da expiracao.")
                print(f"  Faltam {dias_restantes - 30} dias para o inicio do periodo de renovacao.")
                logger.warning(f"Certificado fora do periodo de renovacao. Dias restantes: {dias_restantes}")
                prosseguir = input("\n  Deseja prosseguir mesmo assim? (s/N): ").strip().lower()
                if prosseguir != "s":
                    print("  Renovacao cancelada pelo usuario.")
                    logger.info("Renovacao cancelada pelo usuario - fora do periodo")
                    return
                logger.info("Usuario optou por prosseguir com a renovacao fora do periodo")
            elif dias_restantes < 0:
                print(f"\n  [AVISO] O certificado ja esta EXPIRADO ha {abs(dias_restantes)} dias.")
                logger.warning(f"Certificado expirado ha {abs(dias_restantes)} dias")
            else:
                print(f"  Certificado dentro do periodo de renovacao.")
        except Exception as e:
            logger.warning(f"Nao foi possivel ler o certificado para validacao: {e}")
            print(f"\n  [AVISO] Nao foi possivel ler o certificado para validar a data de expiracao.")
            print("  Prosseguindo com a renovacao...")
    else:
        logger.info("Certificado nao encontrado para validacao de periodo. Prosseguindo com a renovacao.")
        print(f"\n  [INFO] Certificado nao encontrado em: {os.path.abspath(caminho_crt_validacao)}")
        print("  Nao foi possivel validar o periodo de renovacao (30 dias antes da expiracao).")
        print("  Prosseguindo com a renovacao...")

    print("\n=============================================")
    print("  Renovacao de Certificado - Escolha a opcao ")
    print("=============================================")
    print("\n  A. Renovacao completa (gera novo CSR e nova chave privada) - recomendada")
    print("     - Gera nova chave privada e novo CSR antes de renovar")
    print("     - Recomendado para maior seguranca ou quando a chave pode ter sido comprometida")
    print("\n  B. Renovacao simples (reutiliza o CSR existente)")
    print("     - Mais rapido, mantem a mesma chave privada do certificado")
    print("     - Recomendado quando nao ha necessidade de trocar a chave")

    opcao = input("\nEscolha a opcao (A ou B): ").strip().upper()
    if opcao not in ("A", "B"):
        print("Opcao invalida. Escolha A ou B.")
        return

    if opcao == "A":
        logger.info("Opcao A: Renovacao completa (novo CSR e nova chave privada)")
        print("\n[PASSO 1] Gerando nova chave privada e novo CSR...")
        caminho_client_id = _estado_get(estado, "caminhoClientId", os.path.join(PASTA_ETAPA2, "client_id.txt"))
        if os.path.exists(caminho_client_id):
            client_id = open(caminho_client_id, "r", encoding="utf-8").read().strip()
        elif estado.get("clientId"):
            client_id = estado["clientId"]
            print(f"  Client ID recuperado do estado salvo.")
        else:
            print(f"  Nao foi possivel recuperar o Client ID automaticamente.")
            print("  Informe o Client ID manualmente para continuar.")
            client_id = ""
            while not client_id:
                client_id = input("Client ID: ").strip()
                if not client_id:
                    print("  Campo obrigatorio. Informe o Client ID.")
        print(f"  Client ID: {mascarar(client_id)}")

        organizacao = ""
        while not organizacao:
            organizacao = input("Nome da Empresa: ").strip()
            if not organizacao:
                print("  Campo obrigatorio. Informe o nome da empresa.")
        cidade = ""
        while not cidade:
            cidade = input("Cidade: ").strip()
            if not cidade:
                print("  Campo obrigatorio. Informe a cidade.")
        estado_uf = ""
        while not estado_uf:
            estado_uf = input("Estado - UF: ").strip()
            if not estado_uf:
                print("  Campo obrigatorio. Informe o estado (UF).")
        pais = ""
        while not pais:
            pais = input("Pais: ").strip()
            if not pais:
                print("  Campo obrigatorio. Informe o pais.")

        caminho_csr = os.path.join(PASTA_ETAPA4, "ARQUIVO_REQUEST_CERTIFICADO.csr")
        caminho_chave_csr = os.path.join(PASTA_ETAPA4, "ARQUIVO_CHAVE_PRIVADA.key")
        CertificadoDinamico.gerar_csr(client_id, organizacao, cidade, estado_uf, pais, caminho_csr, caminho_chave_csr)
        logger.info(f"Novo CSR gerado: {os.path.abspath(caminho_csr)}")
        logger.info(f"Nova chave privada gerada: {os.path.abspath(caminho_chave_csr)}")

        print("\n[PASSO 2] Obtendo token para renovacao...")
        token_sts, caminho_cert_mtls, caminho_key_mtls = _obter_token_renovacao(estado, client_secret_memoria)

        print("\n[PASSO 3] Enviando novo CSR para renovacao...")
        caminho_crt = os.path.join(PASTA_ETAPA4, "certificado.crt")
        logger.info(f"Token STS: {mascarar(token_sts)}")
        logger.info(f"CSR: {os.path.abspath(caminho_csr)}")
        logger.info("Enviando requisicao para https://sts.itau.com.br/seguranca/v2/certificado/renovacao")
        CertificadoDinamico.renovar_certificado(token_sts, caminho_csr, caminho_crt, caminho_cert_mtls, caminho_key_mtls)

        _exibir_validade_renovacao(caminho_crt, logger)
        print("\n  O que fazer agora:")
        print(f"  1. O novo certificado esta salvo na pasta {os.path.abspath(PASTA_ETAPA4)}")
        print("  2. Substitua o certificado antigo E a chave privada pelos novos na sua aplicacao")
        print(f"  3. A nova chave privada (.key) esta em {os.path.abspath(caminho_chave_csr)}")
        print("  4. Teste a geracao de token (etapa 3) para confirmar que o novo certificado esta funcionando")

        _oferecer_pfx_renovacao(caminho_crt, os.path.abspath(caminho_chave_csr), logger)

    else:
        logger.info("Opcao B: Renovacao simples (reutiliza CSR existente)")
        print("\n[PASSO 1] Localizando CSR existente...")
        caminho_csr = _estado_get(estado, "caminhoCSR", "")
        if not caminho_csr or not os.path.isfile(caminho_csr):
            caminho_csr_padrao = os.path.abspath(os.path.join(PASTA_ETAPA2, "ARQUIVO_REQUEST_CERTIFICADO.csr"))
            if os.path.isfile(caminho_csr_padrao):
                caminho_csr = caminho_csr_padrao
            else:
                caminho_csr = input("Caminho do arquivo CSR: ").strip()
                if not caminho_csr or not os.path.isfile(caminho_csr):
                    raise Exception(f"Arquivo CSR nao encontrado: {caminho_csr}")
        print(f"  CSR encontrado: {os.path.abspath(caminho_csr)}")

        print("\n[PASSO 2] Obtendo token para renovacao...")
        token_sts, caminho_cert_mtls, caminho_key_mtls = _obter_token_renovacao(estado, client_secret_memoria)

        print("\n[PASSO 3] Enviando CSR para renovacao...")
        caminho_crt = os.path.join(PASTA_ETAPA4, "certificado.crt")
        logger.info(f"Token STS: {mascarar(token_sts)}")
        logger.info(f"CSR: {os.path.abspath(caminho_csr)}")
        logger.info("Enviando requisicao para https://sts.itau.com.br/seguranca/v2/certificado/renovacao")
        CertificadoDinamico.renovar_certificado(token_sts, caminho_csr, caminho_crt, caminho_cert_mtls, caminho_key_mtls)

        _exibir_validade_renovacao(caminho_crt, logger)
        print("\n  O que fazer agora:")
        print(f"  1. O novo certificado esta salvo na pasta {os.path.abspath(PASTA_ETAPA4)}")
        print("  2. Substitua o certificado antigo pelo novo na sua aplicacao")
        print("  3. A chave privada (.key) da etapa 2 continua sendo a mesma - nao muda na renovacao")
        print("  4. Teste a geracao de token (etapa 3) para confirmar que o novo certificado esta funcionando")

        caminho_key_pfx = os.path.abspath(os.path.join(PASTA_ETAPA2, "ARQUIVO_CHAVE_PRIVADA.key"))
        _oferecer_pfx_renovacao(caminho_crt, caminho_key_pfx, logger)

if __name__ == "__main__":
    logger, arquivo_log = configurar_log()
    logger.info("Programa iniciado")
    print(f"  Log desta execucao: {arquivo_log}")

    estado = carregar_estado()
    client_secret_memoria = ""
    while True:
        escolha = exibir_menu(estado)
        logger.info(f"Opcao escolhida pelo usuario: {escolha}")

        if escolha == 0:
            logger.info("Programa encerrado pelo usuario")
            print("\nSaindo. Seu progresso foi salvo.")
            break
        elif escolha == 9:
            logger.info("Limpeza de progresso e arquivos gerados iniciada")
            for pasta in [PASTA_ETAPA1, PASTA_ETAPA2, PASTA_ETAPA4]:
                if os.path.isdir(pasta):
                    for arquivo in os.listdir(pasta):
                        caminho_arquivo = os.path.join(pasta, arquivo)
                        if os.path.isfile(caminho_arquivo):
                            os.remove(caminho_arquivo)
                    print(f"  Arquivos removidos da pasta: {os.path.abspath(pasta)}")
                    logger.info(f"Arquivos removidos da pasta: {os.path.abspath(pasta)}")
            if os.path.exists(ARQUIVO_ESTADO):
                os.remove(ARQUIVO_ESTADO)
                print(f"  Removido: {os.path.abspath(ARQUIVO_ESTADO)}")
                logger.info(f"Removido: {os.path.abspath(ARQUIVO_ESTADO)}")
            print("\nProgresso e arquivos gerados removidos.")
            logger.info("Limpeza concluida")
            estado = {}
            client_secret_memoria = ""
            continue
        elif escolha == -1:
            continue

        try:
            if escolha == 1:
                executar_etapa_1(estado)
            elif escolha == 2:
                resultado = executar_etapa_2(estado, client_secret_memoria)
                if resultado:
                    client_secret_memoria = resultado
            elif escolha == 3:
                executar_etapa_3(estado, client_secret_memoria)
            elif escolha == 4:
                executar_etapa_4(estado, client_secret_memoria)
            else:
                print("Opcao invalida.")
                continue
        except Exception as e:
            logger.error(f"{type(e).__name__}: {e}")
            logger.warning("Etapa NAO foi marcada como concluida devido ao erro")
            print(f"\n[ERRO] {type(e).__name__}: {e}")
            print("A etapa NAO foi marcada como concluida. Verifique o erro acima e tente novamente. Caso persista entre em contato com o suporte.")

        input("\nPressione ENTER para voltar ao menu...")