diff --git a/src/TBC.OpenBanking.Jws.Http/src/GlobalSuppressions.cs b/src/TBC.OpenBanking.Jws.Http/src/GlobalSuppressions.cs new file mode 100644 index 0000000..139d6be --- /dev/null +++ b/src/TBC.OpenBanking.Jws.Http/src/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Design", "MA0026:Fix TODO comment", Justification = "Reviewed", Scope = "module")] +[assembly: SuppressMessage("Design", "MA0051:Method is too long", Justification = "Reviewed", Scope = "module")] diff --git a/src/TBC.OpenBanking.Jws.Http/src/JwsMessageHandler.cs b/src/TBC.OpenBanking.Jws.Http/src/JwsMessageHandler.cs index 3c7a010..82a322d 100644 --- a/src/TBC.OpenBanking.Jws.Http/src/JwsMessageHandler.cs +++ b/src/TBC.OpenBanking.Jws.Http/src/JwsMessageHandler.cs @@ -48,6 +48,7 @@ namespace TBC.OpenBanking.Jws.Http; public sealed class JwsMessageHandler : DelegatingHandler { private readonly IOptions jwsOptions; + [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "This class does not own this certificate")] private readonly X509Certificate2? signerCertificate; private readonly HttpSigner? reqSign; @@ -113,7 +114,7 @@ loggerFactory is null RevocationMode = this.jwsOptions.Value.CheckCertificateRevocationList ? X509RevocationMode.Online : X509RevocationMode.NoCheck, - } + }, }; } } @@ -151,7 +152,7 @@ private async Task ProcessRequestAsync( httpData.AddHeader("Host", httpData.Uri!.DnsSafeHost); } - httpData.AppendHeaders(request.Headers, true); + httpData.AppendHeaders(request.Headers, acceptMultivalue: true); if (request.Content is not null) { @@ -163,7 +164,7 @@ private async Task ProcessRequestAsync( httpData.Body = await request.Content!.ReadAsByteArrayAsync().ConfigureAwait(false); #endif - httpData.AppendHeaders(request.Content!.Headers, true); + httpData.AppendHeaders(request.Content!.Headers, acceptMultivalue: true); } if (this.reqSign!.CreateSignature(httpData)) @@ -196,14 +197,14 @@ private async Task ProcessResponseAsync( { var httpData = new HttpResponseData { - StatusCode = ((uint)response.StatusCode).ToString(CultureInfo.InvariantCulture) + StatusCode = ((uint)response.StatusCode).ToString(CultureInfo.InvariantCulture), }; - httpData.AppendHeaders(response.Headers, true); + httpData.AppendHeaders(response.Headers, acceptMultivalue: true); if (response.Content != null) { - httpData.AppendHeaders(response.Content!.Headers, true); + httpData.AppendHeaders(response.Content!.Headers, acceptMultivalue: true); // This is ugly, but there's no better way (so far): diff --git a/src/TBC.OpenBanking.Jws.Http/src/TBC.OpenBanking.Jws.Http.csproj b/src/TBC.OpenBanking.Jws.Http/src/TBC.OpenBanking.Jws.Http.csproj index 9659867..ec66693 100644 --- a/src/TBC.OpenBanking.Jws.Http/src/TBC.OpenBanking.Jws.Http.csproj +++ b/src/TBC.OpenBanking.Jws.Http/src/TBC.OpenBanking.Jws.Http.csproj @@ -45,9 +45,21 @@ + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + all diff --git a/src/TBC.OpenBanking.Jws.Http/src/X509CertificateLocator.cs b/src/TBC.OpenBanking.Jws.Http/src/X509CertificateLocator.cs index 50289f9..debb498 100644 --- a/src/TBC.OpenBanking.Jws.Http/src/X509CertificateLocator.cs +++ b/src/TBC.OpenBanking.Jws.Http/src/X509CertificateLocator.cs @@ -199,7 +199,7 @@ public X509CertificateLocator(Uri? uri) var storeLocation = Enum.Parse(WebUtility.UrlDecode(_uri.Segments[1].Trim(TrimSlashes).Trim()), true); using var store = new X509Store(storeName, storeLocation, OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); #else - var storeLocation = (StoreLocation)Enum.Parse(typeof(StoreLocation), WebUtility.UrlDecode(_uri.Segments[1].Trim(TrimSlashes).Trim()), true); + var storeLocation = (StoreLocation)Enum.Parse(typeof(StoreLocation), WebUtility.UrlDecode(_uri.Segments[1].Trim(TrimSlashes).Trim()), ignoreCase: true); using var store = new X509Store(storeName, storeLocation); store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); #endif diff --git a/src/TBC.OpenBanking.Jws/src/Exceptions/CertificateValidationException.cs b/src/TBC.OpenBanking.Jws/src/Exceptions/CertificateValidationException.cs index 4c4f1d5..026883e 100644 --- a/src/TBC.OpenBanking.Jws/src/Exceptions/CertificateValidationException.cs +++ b/src/TBC.OpenBanking.Jws/src/Exceptions/CertificateValidationException.cs @@ -23,6 +23,7 @@ namespace TBC.OpenBanking.Jws.Exceptions; using System; +using System.Runtime.Serialization; using System.Security.Cryptography.X509Certificates; [Serializable] @@ -35,6 +36,7 @@ public CertificateValidationException(string message) : base(message) { this.SetHResult(ErrorCode); + this.message = message; } public CertificateValidationException(X509ChainStatus[] statuses, string message) @@ -70,10 +72,34 @@ public CertificateValidationException(string message, Exception innerException) : base(message, innerException) { this.SetHResult(ErrorCode); + this.message = message; } public CertificateValidationException() { this.SetHResult(ErrorCode); } + + protected CertificateValidationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + if (info == null) + { + throw new ArgumentNullException(nameof(info)); + } + + this.message = info.GetString("message2"); + this.SetHResult(ErrorCode); + } + + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException(nameof(info)); + } + + info.AddValue("message2", this.message, typeof(string)); + base.GetObjectData(info, context); + } } diff --git a/src/TBC.OpenBanking.Jws/src/GlobalSuppressions.cs b/src/TBC.OpenBanking.Jws/src/GlobalSuppressions.cs index 12960ae..f864e61 100644 --- a/src/TBC.OpenBanking.Jws/src/GlobalSuppressions.cs +++ b/src/TBC.OpenBanking.Jws/src/GlobalSuppressions.cs @@ -10,3 +10,5 @@ [assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Reviewed", Scope = "module")] [assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Reviewed", Scope = "module")] [assembly: SuppressMessage("Performance", "CA1848:Use the LoggerMessage delegates", Justification = "Reviewed", Scope = "module")] +[assembly: SuppressMessage("Design", "MA0026:Fix TODO comment", Justification = "Reviewed", Scope = "module")] +[assembly: SuppressMessage("Design", "MA0051:Method is too long", Justification = "Reviewed", Scope = "module")] diff --git a/src/TBC.OpenBanking.Jws/src/HttpDigest.cs b/src/TBC.OpenBanking.Jws/src/HttpDigest.cs index 35f7152..f1cdb1c 100644 --- a/src/TBC.OpenBanking.Jws/src/HttpDigest.cs +++ b/src/TBC.OpenBanking.Jws/src/HttpDigest.cs @@ -73,7 +73,7 @@ internal static HttpDigest CreateDigest(string digestString) throw new ArgumentOutOfRangeException(nameof(digestString), "Bad format of digest string. Can't find algorithm prefix"); var algName = digestString.Substring(0, dividerIndex).Trim(); - var element = supportedAlgorithms.Find(x => x.Prefix == algName); + var element = supportedAlgorithms.Find(x => string.Equals(x.Prefix, algName, StringComparison.Ordinal)); if (element == default) throw new ArgumentOutOfRangeException(nameof(digestString), "Unsupported hash algorithm"); diff --git a/src/TBC.OpenBanking.Jws/src/HttpRequestData.cs b/src/TBC.OpenBanking.Jws/src/HttpRequestData.cs index eb13fcb..c841e25 100644 --- a/src/TBC.OpenBanking.Jws/src/HttpRequestData.cs +++ b/src/TBC.OpenBanking.Jws/src/HttpRequestData.cs @@ -64,8 +64,11 @@ public class HttpRequestData : HttpMessageData /// Special header name "(request-target)" is also acceptable and will be processed accordingly. /// Additional header name-values /// + [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Intentional")] public override string ComposeHeadersForSignature(IList headers, IDictionary additionalHeaders = null) { + _ = headers ?? throw new ArgumentNullException(nameof(headers)); + using var sb = new ValueStringBuilder(initialCapacity: 512); foreach (var hn in headers) { @@ -165,6 +168,7 @@ public override void CheckMandatoryHeaders() } } + [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Intentional")] public override List GetHeaderNamesForSignature() { var headersList = new List(NecessaryHeaders.Count); diff --git a/src/TBC.OpenBanking.Jws/src/HttpResponseData.cs b/src/TBC.OpenBanking.Jws/src/HttpResponseData.cs index fbc966e..0131b41 100644 --- a/src/TBC.OpenBanking.Jws/src/HttpResponseData.cs +++ b/src/TBC.OpenBanking.Jws/src/HttpResponseData.cs @@ -93,6 +93,7 @@ public override string ComposeHeadersForSignature(IList headers, IDictio return sb.ToString(); } + [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Intentional")] public override List GetHeaderNamesForSignature() { var headersList = new List(NecessaryHeaders.Count); diff --git a/src/TBC.OpenBanking.Jws/src/HttpSignatureVerifier.cs b/src/TBC.OpenBanking.Jws/src/HttpSignatureVerifier.cs index 18e582e..e59105e 100644 --- a/src/TBC.OpenBanking.Jws/src/HttpSignatureVerifier.cs +++ b/src/TBC.OpenBanking.Jws/src/HttpSignatureVerifier.cs @@ -63,6 +63,8 @@ private void InitData() /// public bool VerifySignature(T httpData, DateTime checkTime) { + _ = httpData ?? throw new ArgumentNullException(nameof(httpData)); + IsSignatureVerified = false; // Check headers. If any is missing throws exception diff --git a/src/TBC.OpenBanking.Jws/src/Internals/Helper.cs b/src/TBC.OpenBanking.Jws/src/Internals/Helper.cs index d3593a8..cc61476 100644 --- a/src/TBC.OpenBanking.Jws/src/Internals/Helper.cs +++ b/src/TBC.OpenBanking.Jws/src/Internals/Helper.cs @@ -29,36 +29,28 @@ namespace TBC.OpenBanking.Jws; static internal class Helper { - private static readonly JsonSerializerOptions Options; - - static Helper() + // These options match Newtonsoft.Json's defaults, more or less + private static readonly JsonSerializerOptions s_options = new(JsonSerializerDefaults.Web) { - // These options match Newtonsoft.Json defaults, more or less. - - var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) - { - AllowTrailingCommas = true, - DefaultBufferSize = 81920, // Keeping under large object heap treshold (85K) - MaxDepth = 128, // Newtonsoft has no limit on this - DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - IncludeFields = false, - NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals, - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - ReadCommentHandling = JsonCommentHandling.Skip, - WriteIndented = false, - }; - - Options = options; - } + AllowTrailingCommas = true, + DefaultBufferSize = 81920, // Keeping under large object heap treshold (85K) + MaxDepth = 128, // Newtonsoft has no limit on this + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + IncludeFields = false, + NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals, + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + ReadCommentHandling = JsonCommentHandling.Skip, + WriteIndented = false, + }; [MethodImpl(MethodImplOptions.AggressiveInlining)] - static internal string SerializeToJson(object obj) => JsonSerializer.Serialize(obj, options: Options); + static internal string SerializeToJson(object obj) => JsonSerializer.Serialize(obj, s_options); [MethodImpl(MethodImplOptions.AggressiveInlining)] - static internal string SerializeToJson(T obj) => JsonSerializer.Serialize(obj, options: Options); + static internal string SerializeToJson(T obj) => JsonSerializer.Serialize(obj, s_options); [MethodImpl(MethodImplOptions.AggressiveInlining)] - static internal T DeserializeFromJson(string jsonString) => JsonSerializer.Deserialize(jsonString, options: Options); + static internal T DeserializeFromJson(string jsonString) => JsonSerializer.Deserialize(jsonString, s_options); } diff --git a/src/TBC.OpenBanking.Jws/src/ProtectedHeader.cs b/src/TBC.OpenBanking.Jws/src/ProtectedHeader.cs index e2be1ec..e4b255e 100644 --- a/src/TBC.OpenBanking.Jws/src/ProtectedHeader.cs +++ b/src/TBC.OpenBanking.Jws/src/ProtectedHeader.cs @@ -43,16 +43,16 @@ public class DataToBeSignedDescription [JsonPropertyName("mId")] public string IdentificationMechanism { get; set; } = "http://uri.etsi.org/19182/HttpHeaders"; + [SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Intentional")] public void AddParameter(string value) { if (value == null) throw new ArgumentNullException(nameof(value)); if (value.Length != 0) { - value = value.ToLowerInvariant(); // Preventing duplication - if (!Parameters.Contains(value)) - Parameters.Add(value); + if (!Parameters.Contains(value, StringComparer.OrdinalIgnoreCase)) + Parameters.Add(value.ToLowerInvariant()); } } } diff --git a/src/TBC.OpenBanking.Jws/src/TBC.OpenBanking.Jws.csproj b/src/TBC.OpenBanking.Jws/src/TBC.OpenBanking.Jws.csproj index e9f11f6..ca96e35 100644 --- a/src/TBC.OpenBanking.Jws/src/TBC.OpenBanking.Jws.csproj +++ b/src/TBC.OpenBanking.Jws/src/TBC.OpenBanking.Jws.csproj @@ -46,9 +46,21 @@ + + + + all + runtime; build; native; contentfiles; analyzers + + + all + runtime; build; native; contentfiles; analyzers + + + all