From e5e1802da996433b4761fdc58c623cf13d787f93 Mon Sep 17 00:00:00 2001 From: Neil Dorin Date: Sun, 10 May 2026 20:48:05 -0600 Subject: [PATCH] feat: enhance LoginRequestHandler to include detailed LoginResponse structure Co-authored-by: Copilot --- .../Logging/DebugWebsocketSink.cs | 35 ++++--------- .../RequestHandlers/LoginRequestHandler.cs | 52 ++++++++++++++++++- 2 files changed, 60 insertions(+), 27 deletions(-) diff --git a/src/PepperDash.Core/Logging/DebugWebsocketSink.cs b/src/PepperDash.Core/Logging/DebugWebsocketSink.cs index 8986fcb4..99910ec9 100644 --- a/src/PepperDash.Core/Logging/DebugWebsocketSink.cs +++ b/src/PepperDash.Core/Logging/DebugWebsocketSink.cs @@ -15,12 +15,10 @@ using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Operators; -using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.Security; using Org.BouncyCastle.X509; -using System.Security.Cryptography; using Serilog.Formatting; using Serilog.Formatting.Json; @@ -244,9 +242,10 @@ namespace PepperDash.Core /// /// Loads a PKCS#12 file written by BouncyCastle and returns an with - /// private key attached via . - /// Using BouncyCastle's own reader avoids the .NET/Mono PFX parser, which can reject - /// BouncyCastle-generated archives on the Crestron runtime. + /// private key attached. + /// The PFX is parsed and re-encoded by BouncyCastle (ensuring format compatibility), then passed as + /// raw bytes to so neither RSACryptoServiceProvider nor the + /// EphemeralKeySet flag (unsupported on the Crestron/Mono runtime) is needed. /// private static X509Certificate2 LoadCertFromBouncyCastle(string certPath, string certPassword) { @@ -258,26 +257,12 @@ namespace PepperDash.Core var store = new Pkcs12StoreBuilder().Build(); store.Load(stream, passwordChars); - foreach (string alias in store.Aliases) + // Re-encode through BouncyCastle to guarantee PKCS#12 format compatibility, + // then hand raw bytes to X509Certificate2 — no RSACryptoServiceProvider needed. + using (var ms = new MemoryStream()) { - if (!store.IsKeyEntry(alias)) continue; - - var keyEntry = store.GetKey(alias); - var certChain = store.GetCertificateChain(alias); - if (certChain == null || certChain.Length == 0) continue; - - // Build X509Certificate2 from raw DER — no PFX parsing by .NET needed. - var cert = new X509Certificate2(certChain[0].Certificate.GetEncoded()); - - // Attach the private key via RSACryptoServiceProvider (available on all target runtimes). - var rsaParams = DotNetUtilities.ToRSAParameters( - (RsaPrivateCrtKeyParameters)keyEntry.Key); - var rsa = new RSACryptoServiceProvider(); - rsa.PersistKeyInCsp = false; - rsa.ImportParameters(rsaParams); - cert.PrivateKey = rsa; - - return cert; + store.Save(ms, passwordChars, new SecureRandom()); + return new X509Certificate2(ms.ToArray(), certPassword); } } } @@ -285,8 +270,6 @@ namespace PepperDash.Core { Array.Clear(passwordChars, 0, passwordChars.Length); } - - throw new InvalidOperationException("No key entry found in PKCS#12 store: " + certPath); } private void Start(int port, string certPath = "", string certPassword = "") diff --git a/src/PepperDash.Essentials.Core/Web/RequestHandlers/LoginRequestHandler.cs b/src/PepperDash.Essentials.Core/Web/RequestHandlers/LoginRequestHandler.cs index 5a4b7df7..c05e0cb6 100644 --- a/src/PepperDash.Essentials.Core/Web/RequestHandlers/LoginRequestHandler.cs +++ b/src/PepperDash.Essentials.Core/Web/RequestHandlers/LoginRequestHandler.cs @@ -1,5 +1,6 @@ using System; +using System.Collections.Generic; using Crestron.SimplSharp.CrestronAuthentication; using Crestron.SimplSharp.WebScripting; using Newtonsoft.Json; @@ -91,7 +92,15 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers context.Response.StatusDescription = "OK"; context.Response.ContentType = "application/json"; context.Response.ContentEncoding = System.Text.Encoding.UTF8; - context.Response.Write(JsonConvert.SerializeObject(new { Token = token }, Formatting.Indented), false); + context.Response.Write(JsonConvert.SerializeObject( + new LoginResponse + { UserName = token.UserName, + Password = token.Password, + Access = token.Access, + State = token.State, + Groups = token.Groups, + ADConnect = token.ADConnect, + Valid = token.Valid }, Formatting.Indented), false); context.Response.End(); } catch (System.Exception ex) @@ -121,4 +130,45 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers /// public string Password { get; set; } } + + /// + /// Represents a LoginResponse + /// + internal class LoginResponse + { + /// + /// Gets or sets the username. + /// + public string UserName { get; set; } + + /// + /// Gets or sets the password. + /// + public string Password { get; set; } + + /// + /// Gets or sets the access level. + /// + public Authentication.UserAuthenticationLevelEnum Access { get; set; } + + /// + /// Gets or sets the token authenticated state. + /// + public Authentication.eTokenAuthenticatedState State { get; set; } + + /// + /// Gets or sets the list of groups. + /// + public List Groups { get; set; } + + /// + /// Gets or sets the active directory connection flag. + /// + public int ADConnect { get; set; } + + /// + /// Gets or sets the valid flag indicating whether the token is valid. + /// + public bool Valid { get; set; } + } } \ No newline at end of file