Add SGU credential provider and authentication broker

This commit is contained in:
2026-08-31 17:48:18 -06:00
parent 5e216f42a4
commit 1f43f200b4
50 changed files with 3226 additions and 236 deletions
@@ -0,0 +1,71 @@
using System.Text.Json;
namespace SGU.CredentialProvider;
internal sealed class ProviderSettings
{
private const int MaximumSettingsBytes = 16 * 1024;
public Uri BrokerEndpoint { get; init; } = new("https://sgu-auth.lci.lasalle.mx:8443/v1/authenticate");
public string DomainNetbios { get; init; } = "LCI";
public int TimeoutSeconds { get; init; } = 6;
public string ClientCertificateThumbprint { get; init; } = string.Empty;
public string ServerCertificateThumbprint { get; init; } = string.Empty;
public static string DefaultPath => Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
"SGU",
"CredentialProvider",
"settings.json");
public static ProviderSettings Load(string? path = null)
{
path ??= Environment.GetEnvironmentVariable("SGU_CREDENTIAL_PROVIDER_CONFIG") ?? DefaultPath;
FileInfo file = new(path);
if (!file.Exists || file.Length is <= 0 or > MaximumSettingsBytes)
{
throw new InvalidOperationException("Credential Provider settings are missing or invalid.");
}
using FileStream stream = new(path, FileMode.Open, FileAccess.Read, FileShare.Read);
ProviderSettings settings = JsonSerializer.Deserialize<ProviderSettings>(stream, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
}) ?? throw new InvalidOperationException("Credential Provider settings could not be read.");
settings.Validate();
return settings;
}
public void Validate()
{
if (!BrokerEndpoint.IsAbsoluteUri ||
BrokerEndpoint.Scheme != Uri.UriSchemeHttps ||
!string.IsNullOrEmpty(BrokerEndpoint.UserInfo))
{
throw new InvalidOperationException("BrokerEndpoint must be an absolute HTTPS URL without user information.");
}
if (!string.Equals(BrokerEndpoint.AbsolutePath.TrimEnd('/'), "/v1/authenticate", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("BrokerEndpoint must target /v1/authenticate.");
}
if (string.IsNullOrWhiteSpace(DomainNetbios) || TimeoutSeconds is < 2 or > 30)
{
throw new InvalidOperationException("DomainNetbios or TimeoutSeconds is invalid.");
}
if (NormalizeThumbprint(ClientCertificateThumbprint).Length != 40 ||
NormalizeThumbprint(ServerCertificateThumbprint).Length != 40)
{
throw new InvalidOperationException("Client and server SHA-1 certificate thumbprints are required.");
}
}
public static string NormalizeThumbprint(string value) =>
value.Replace(" ", string.Empty, StringComparison.Ordinal).ToUpperInvariant();
}