213 lines
10 KiB
C#
213 lines
10 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Security.Cryptography;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using MLL;
|
|
|
|
namespace TLL
|
|
{
|
|
/// <summary>
|
|
/// Gera um certificado autoassinado com os dados da empresa.
|
|
/// Salva .pfx (com chave privada) e .cer (chave pública) em C:\LevelCode\LevelOS\Cert
|
|
/// Validade: 1 ano a partir da geração.
|
|
/// </summary>
|
|
public static class CertificadoHelper
|
|
{
|
|
private static readonly string PastaDestino = @"C:\LevelCode\LevelOS\Cert";
|
|
|
|
// ══════════════════════════════════════════════════════════════════════
|
|
// GERAR CERTIFICADO
|
|
// ══════════════════════════════════════════════════════════════════════
|
|
public static CertificadoResultado Gerar(ModeloEmpresa empresa, string senhaPfx)
|
|
{
|
|
try
|
|
{
|
|
// Garante que a pasta existe
|
|
Directory.CreateDirectory(PastaDestino);
|
|
|
|
// Limpa campos nulos antes de usar
|
|
string nome = empresa.Nome?.Trim() ?? "Empresa";
|
|
string uf = empresa.UF?.Trim() ?? "BR";
|
|
string cidade = empresa.Cidade?.Trim() ?? "";
|
|
string email = empresa.Email?.Trim() ?? "";
|
|
string cnpjLimpo = empresa.CNPJ?
|
|
.Replace(".", "").Replace("/", "").Replace("-", "").Trim() ?? "empresa";
|
|
|
|
// ── Monta o Distinguished Name (DN) ────────────────────────────
|
|
var dn = new X500DistinguishedName(
|
|
$"CN={nome}, " +
|
|
$"O={nome}, " +
|
|
$"OU=LevelOS, " +
|
|
$"C=BR, " +
|
|
$"ST={uf}, " +
|
|
$"L={cidade}, " +
|
|
$"SERIALNUMBER={cnpjLimpo}");
|
|
|
|
// ── Gera chave RSA 2048 bits ───────────────────────────────────
|
|
using var rsa = RSA.Create(2048);
|
|
|
|
// ── Configura o certificado ────────────────────────────────────
|
|
var request = new CertificateRequest(dn, rsa,
|
|
HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
|
|
|
// Extensões
|
|
request.CertificateExtensions.Add(
|
|
new X509BasicConstraintsExtension(false, false, 0, true));
|
|
|
|
request.CertificateExtensions.Add(
|
|
new X509KeyUsageExtension(
|
|
X509KeyUsageFlags.DigitalSignature |
|
|
X509KeyUsageFlags.NonRepudiation |
|
|
X509KeyUsageFlags.KeyEncipherment,
|
|
true));
|
|
|
|
request.CertificateExtensions.Add(
|
|
new X509EnhancedKeyUsageExtension(new OidCollection
|
|
{
|
|
new Oid("1.3.6.1.5.5.7.3.2"), // Client Authentication
|
|
new Oid("1.3.6.1.5.5.7.3.4") // Email Protection
|
|
}, false));
|
|
|
|
// Compatível com .NET 8
|
|
request.CertificateExtensions.Add(
|
|
new X509SubjectKeyIdentifierExtension(request.PublicKey, false));
|
|
|
|
// ── Validade: hoje até 1 ano ───────────────────────────────────
|
|
var agora = DateTimeOffset.UtcNow;
|
|
var expira = agora.AddYears(1);
|
|
|
|
// ── Gera o certificado autoassinado ────────────────────────────
|
|
var cert = request.CreateSelfSigned(agora, expira);
|
|
|
|
// ── Caminhos dos arquivos ──────────────────────────────────────
|
|
string nomePfx = Path.Combine(PastaDestino, $"{cnpjLimpo}.pfx");
|
|
string nomeCer = Path.Combine(PastaDestino, $"{cnpjLimpo}.cer");
|
|
|
|
// ── Exporta .pfx (chave pública + privada, protegido por senha)
|
|
byte[] pfxBytes = cert.Export(X509ContentType.Pfx, senhaPfx);
|
|
File.WriteAllBytes(nomePfx, pfxBytes);
|
|
|
|
// ── Exporta .cer (somente chave pública) ──────────────────────
|
|
byte[] cerBytes = cert.Export(X509ContentType.Cert);
|
|
File.WriteAllBytes(nomeCer, cerBytes);
|
|
|
|
return new CertificadoResultado
|
|
{
|
|
Sucesso = true,
|
|
CaminhoPfx = nomePfx,
|
|
CaminhoCer = nomeCer,
|
|
Thumbprint = cert.Thumbprint,
|
|
ValidoAte = expira.LocalDateTime,
|
|
Mensagem = "Certificado gerado com sucesso!"
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new CertificadoResultado
|
|
{
|
|
Sucesso = false,
|
|
Mensagem = $"Erro ao gerar certificado: {ex.Message}"
|
|
};
|
|
}
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════════
|
|
// CARREGAR CERTIFICADO EXISTENTE
|
|
// ══════════════════════════════════════════════════════════════════════
|
|
public static X509Certificate2? Carregar(string cnpj, string senhaPfx)
|
|
{
|
|
try
|
|
{
|
|
string cnpjLimpo = cnpj?.Replace(".", "").Replace("/", "").Replace("-", "") ?? "";
|
|
string caminho = Path.Combine(PastaDestino, $"{cnpjLimpo}.pfx");
|
|
|
|
if (!File.Exists(caminho)) return null;
|
|
|
|
var collection = new X509Certificate2Collection();
|
|
collection.Import(caminho, senhaPfx, X509KeyStorageFlags.DefaultKeySet);
|
|
return collection.Count > 0 ? collection[0] : null;
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════════
|
|
// VERIFICAR SE CERTIFICADO EXISTE E ESTÁ VÁLIDO
|
|
// ══════════════════════════════════════════════════════════════════════
|
|
public static StatusCertificado VerificarStatus(string cnpj, string senhaPfx)
|
|
{
|
|
try
|
|
{
|
|
string cnpjLimpo = cnpj?.Replace(".", "").Replace("/", "").Replace("-", "") ?? "";
|
|
string caminho = Path.Combine(PastaDestino, $"{cnpjLimpo}.pfx");
|
|
|
|
if (!File.Exists(caminho))
|
|
return new StatusCertificado
|
|
{
|
|
Existe = false,
|
|
Mensagem = "Certificado não encontrado."
|
|
};
|
|
|
|
var collection = new X509Certificate2Collection();
|
|
collection.Import(caminho, senhaPfx, X509KeyStorageFlags.DefaultKeySet);
|
|
|
|
if (collection.Count == 0)
|
|
return new StatusCertificado
|
|
{
|
|
Existe = false,
|
|
Mensagem = "Certificado inválido ou vazio."
|
|
};
|
|
|
|
var cert = collection[0];
|
|
var expira = cert.NotAfter;
|
|
var diasRestantes = (expira - DateTime.Now).Days;
|
|
|
|
return new StatusCertificado
|
|
{
|
|
Existe = true,
|
|
Valido = DateTime.Now <= expira,
|
|
Thumbprint = cert.Thumbprint,
|
|
ValidoAte = expira,
|
|
DiasRestantes = diasRestantes,
|
|
Mensagem = diasRestantes > 0
|
|
? $"Certificado válido por mais {diasRestantes} dias."
|
|
: "Certificado expirado!"
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return new StatusCertificado
|
|
{
|
|
Existe = true,
|
|
Valido = false,
|
|
Mensagem = $"Erro ao ler o certificado: {ex.Message}"
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
// MODELOS DE RETORNO
|
|
// ══════════════════════════════════════════════════════════════════════════
|
|
public class CertificadoResultado
|
|
{
|
|
public bool Sucesso { get; set; }
|
|
public string Mensagem { get; set; } = string.Empty;
|
|
public string? CaminhoPfx { get; set; }
|
|
public string? CaminhoCer { get; set; }
|
|
public string? Thumbprint { get; set; }
|
|
public DateTime ValidoAte { get; set; }
|
|
}
|
|
|
|
public class StatusCertificado
|
|
{
|
|
public bool Existe { get; set; }
|
|
public bool Valido { get; set; }
|
|
public string Mensagem { get; set; } = string.Empty;
|
|
public string? Thumbprint { get; set; }
|
|
public DateTime ValidoAte { get; set; }
|
|
public int DiasRestantes { get; set; }
|
|
}
|
|
} |