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...")