feat: refactor certificate generation to use BouncyCastle for improved security and flexibility

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Neil Dorin 2026-05-05 13:07:52 -06:00
parent 1ecfca7e5a
commit 507130c1ae

View file

@ -8,10 +8,17 @@ using Crestron.SimplSharp;
using WebSocketSharp; using WebSocketSharp;
using System.Security.Authentication; using System.Security.Authentication;
using WebSocketSharp.Net; using WebSocketSharp.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.IO; using System.IO;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
using Serilog.Formatting; using Serilog.Formatting;
using Serilog.Formatting.Json; using Serilog.Formatting.Json;
@ -114,52 +121,63 @@ namespace PepperDash.Core
var subjectName = string.Format("CN={0}.{1}", hostName, domainName); var subjectName = string.Format("CN={0}.{1}", hostName, domainName);
var fqdn = string.Format("{0}.{1}", hostName, domainName); var fqdn = string.Format("{0}.{1}", hostName, domainName);
using (var rsa = RSA.Create(2048)) var random = new SecureRandom();
{
var request = new CertificateRequest( // Generate RSA 2048 key pair
subjectName, var keyPairGenerator = new RsaKeyPairGenerator();
rsa, keyPairGenerator.Init(new KeyGenerationParameters(random, 2048));
HashAlgorithmName.SHA256, var keyPair = keyPairGenerator.GenerateKeyPair();
RSASignaturePadding.Pkcs1);
// Subject Key Identifier // Build certificate
request.CertificateExtensions.Add( var certGenerator = new X509V3CertificateGenerator();
new X509SubjectKeyIdentifierExtension(request.PublicKey, false)); certGenerator.SetSerialNumber(BigInteger.ValueOf(Math.Abs(DateTime.UtcNow.Ticks)));
certGenerator.SetIssuerDN(new X509Name(subjectName));
certGenerator.SetSubjectDN(new X509Name(subjectName));
certGenerator.SetNotBefore(DateTime.UtcNow);
certGenerator.SetNotAfter(DateTime.UtcNow.AddYears(2));
certGenerator.SetPublicKey(keyPair.Public);
// Extended Key Usage: server + client auth // Extended Key Usage: server + client auth
request.CertificateExtensions.Add( certGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, false,
new X509EnhancedKeyUsageExtension( new ExtendedKeyUsage(new[] { KeyPurposeID.id_kp_serverAuth, KeyPurposeID.id_kp_clientAuth }));
new OidCollection
{
new Oid("1.3.6.1.5.5.7.3.1"), // id-kp-serverAuth
new Oid("1.3.6.1.5.5.7.3.2") // id-kp-clientAuth
},
false));
// Subject Alternative Names: DNS + IP // Subject Alternative Names: DNS + IP
var sanBuilder = new SubjectAlternativeNameBuilder(); System.Net.IPAddress parsedIp;
sanBuilder.AddDnsName(fqdn); if (System.Net.IPAddress.TryParse(ipAddress, out parsedIp))
if (System.Net.IPAddress.TryParse(ipAddress, out var ip))
sanBuilder.AddIpAddress(ip);
request.CertificateExtensions.Add(sanBuilder.Build());
var notBefore = DateTimeOffset.UtcNow;
var notAfter = notBefore.AddYears(2);
using (var cert = request.CreateSelfSigned(notBefore, notAfter))
{ {
certGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false,
new GeneralNames(new GeneralName[] {
new GeneralName(GeneralName.DnsName, fqdn),
new GeneralName(GeneralName.IPAddress, ipAddress)
}));
}
else
{
certGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false,
new GeneralNames(new GeneralName(GeneralName.DnsName, fqdn)));
}
// Sign with SHA256withRSA
var signatureFactory = new Asn1SignatureFactory("SHA256WITHRSA", keyPair.Private, random);
var certificate = certGenerator.Generate(signatureFactory);
// Export as PKCS12/PFX
var pkcs12Store = new Pkcs12StoreBuilder().Build();
var certEntry = new X509CertificateEntry(certificate);
pkcs12Store.SetCertificateEntry(_certificateName, certEntry);
pkcs12Store.SetKeyEntry(_certificateName, new AsymmetricKeyEntry(keyPair.Private), new[] { certEntry });
var separator = Path.DirectorySeparatorChar; var separator = Path.DirectorySeparatorChar;
var outputPath = string.Format("{0}user{1}{2}.pfx", separator, separator, _certificateName); var outputPath = string.Format("{0}user{1}{2}.pfx", separator, separator, _certificateName);
var pfxBytes = cert.Export(X509ContentType.Pfx, _certificatePassword); using (var ms = new MemoryStream())
File.WriteAllBytes(outputPath, pfxBytes); {
pkcs12Store.Save(ms, _certificatePassword.ToCharArray(), random);
File.WriteAllBytes(outputPath, ms.ToArray());
}
CrestronConsole.PrintLine(string.Format("CreateCert: Certificate written to {0}", outputPath)); CrestronConsole.PrintLine(string.Format("CreateCert: Certificate written to {0}", outputPath));
} }
}
}
catch (Exception ex) catch (Exception ex)
{ {
CrestronConsole.PrintLine(string.Format("WSS CreateCert Failed: {0}\r\n{1}", ex.Message, ex.StackTrace)); CrestronConsole.PrintLine(string.Format("WSS CreateCert Failed: {0}\r\n{1}", ex.Message, ex.StackTrace));