98 lines
3.7 KiB
C#
98 lines
3.7 KiB
C#
using System.Security.Cryptography;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
namespace Csob.Credentials;
|
|
|
|
/// <summary>
|
|
/// Resolves the ČSOB credentials and PSU context for the current request, exclusively from HTTP
|
|
/// headers (this service stores no secrets). Missing required headers produce a 401; a malformed
|
|
/// certificate or wrong passphrase produces a 400 (via <see cref="CryptographicException"/>).
|
|
/// </summary>
|
|
public sealed class RequestCredentialsProvider
|
|
{
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
|
|
public RequestCredentialsProvider(IHttpContextAccessor httpContextAccessor)
|
|
{
|
|
_httpContextAccessor = httpContextAccessor;
|
|
}
|
|
|
|
/// <summary>Reads a single request header, returning null when absent or blank.</summary>
|
|
public string? Header(string name)
|
|
{
|
|
var headers = _httpContextAccessor.HttpContext?.Request.Headers;
|
|
if (headers is not null && headers.TryGetValue(name, out var value))
|
|
{
|
|
var raw = value.ToString();
|
|
if (!string.IsNullOrWhiteSpace(raw))
|
|
{
|
|
return raw;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds the eIDAS client certificate from the Base64 PFX header (+ optional passphrase).
|
|
/// Returns null when no certificate header is present.
|
|
/// </summary>
|
|
/// <exception cref="CryptographicException">The header is not valid Base64 or the PFX/passphrase is invalid.</exception>
|
|
public X509Certificate2? TryBuildCertificate()
|
|
{
|
|
var base64 = Header(CredentialConstants.CertificateHeader);
|
|
if (base64 is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
byte[] raw;
|
|
try
|
|
{
|
|
raw = Convert.FromBase64String(base64.Trim());
|
|
}
|
|
catch (FormatException ex)
|
|
{
|
|
throw new CryptographicException($"{CredentialConstants.CertificateHeader} is not valid Base64.", ex);
|
|
}
|
|
|
|
var password = Header(CredentialConstants.CertificatePasswordHeader);
|
|
|
|
// EphemeralKeySet keeps the private key in memory only — never written to the machine key store / disk.
|
|
return new X509Certificate2(raw, password, X509KeyStorageFlags.EphemeralKeySet);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves the full credential set required for an AISP/PISP/consent call. Throws
|
|
/// <see cref="MissingCredentialsException"/> if any required header is absent.
|
|
/// </summary>
|
|
public CsobCredentials Resolve()
|
|
{
|
|
var accessToken = Header(CredentialConstants.AccessTokenHeader);
|
|
var apiKey = Header(CredentialConstants.ApiKeyHeader);
|
|
var tppName = Header(CredentialConstants.TppNameHeader);
|
|
var certificate = TryBuildCertificate();
|
|
|
|
var missing = new List<string>();
|
|
if (string.IsNullOrWhiteSpace(accessToken)) missing.Add(CredentialConstants.AccessTokenHeader);
|
|
if (string.IsNullOrWhiteSpace(apiKey)) missing.Add(CredentialConstants.ApiKeyHeader);
|
|
if (string.IsNullOrWhiteSpace(tppName)) missing.Add(CredentialConstants.TppNameHeader);
|
|
if (certificate is null) missing.Add(CredentialConstants.CertificateHeader);
|
|
if (missing.Count > 0)
|
|
{
|
|
certificate?.Dispose();
|
|
throw new MissingCredentialsException(missing);
|
|
}
|
|
|
|
return new CsobCredentials
|
|
{
|
|
AccessToken = accessToken!,
|
|
ApiKey = apiKey!,
|
|
TppName = tppName!,
|
|
Certificate = certificate,
|
|
UserInvolved = string.Equals(Header(CredentialConstants.UserInvolvedHeader), "true", StringComparison.OrdinalIgnoreCase),
|
|
UserIpAddress = Header(CredentialConstants.UserIpAddressHeader),
|
|
};
|
|
}
|
|
}
|