From 0622a1995691e22f6b4c761dcca95d9ef4508688 Mon Sep 17 00:00:00 2001 From: angusmillar Date: Fri, 28 May 2021 18:46:47 +1000 Subject: [PATCH 1/6] Small clean up --- SmartHealthCard.QRCode/SmartHealthCard.QRCode.csproj | 2 +- SmartHealthCard.QRCode/SmartHealthCardQRCodeDecoder.cs | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/SmartHealthCard.QRCode/SmartHealthCard.QRCode.csproj b/SmartHealthCard.QRCode/SmartHealthCard.QRCode.csproj index 737681a..6db2a80 100644 --- a/SmartHealthCard.QRCode/SmartHealthCard.QRCode.csproj +++ b/SmartHealthCard.QRCode/SmartHealthCard.QRCode.csproj @@ -1,4 +1,4 @@ - + net5.0 diff --git a/SmartHealthCard.QRCode/SmartHealthCardQRCodeDecoder.cs b/SmartHealthCard.QRCode/SmartHealthCardQRCodeDecoder.cs index 48534d3..f3fc3c3 100644 --- a/SmartHealthCard.QRCode/SmartHealthCardQRCodeDecoder.cs +++ b/SmartHealthCard.QRCode/SmartHealthCardQRCodeDecoder.cs @@ -42,12 +42,9 @@ public SmartHealthCardQRCodeDecoder( /// /// public string GetToken(List QRCodeRawDataList) - { - IQRCodeDecoder QRCodeDecoder = new QRCodeDecoder(); + { IEnumerable ChunkList = QRCodeDecoder.GetQRCodeChunkList(QRCodeRawDataList); - INumericalModeDecoder NumericalModeDecoder = new NumericalModeDecoder(); - string test = NumericalModeDecoder.Decode(ChunkList); - return test; + return NumericalModeDecoder.Decode(ChunkList); } } } From 0e74b445043c4ba5826a44c946c3c9c1a92e1fed Mon Sep 17 00:00:00 2001 From: angusmillar Date: Sun, 30 May 2021 02:56:21 +1000 Subject: [PATCH 2/6] Improved the way JwksProvider makes its HttpClient call Introduced Result Support Added null check to Jwks model Keys element --- SmartHealthCard.DecoderDemo/Program.cs | 12 +- .../SmartHealthCardQRCodeEncoderTest.cs | 28 +++-- SmartHealthCard.Test/Support/JwksSupport.cs | 4 +- .../Algorithms/ES256Algorithm.cs | 2 +- SmartHealthCard.Token/JwsToken/JwsDecoder.cs | 12 +- .../JwsToken/SmartHealthCardJwsDecoder.cs | 14 ++- .../Model/Jwks/JsonWebKeySet.cs | 3 +- .../Providers/IJwksProvider.cs | 3 +- .../Providers/JwksProvider.cs | 10 +- .../Providers/JwksProviderHttpClient.cs | 94 ++++++++++++++++ .../Serializers/Json/IJsonSerializer.cs | 8 +- .../Serializers/Json/JsonSerializer.cs | 29 ++++- .../Shc/SmartHealthCardJwsHeaderSerializer.cs | 7 ++ .../SmartHealthCardJwsPayloadSerializer.cs | 6 + SmartHealthCard.Token/Support/Result.cs | 104 ++++++++++++++++++ 15 files changed, 309 insertions(+), 27 deletions(-) create mode 100644 SmartHealthCard.Token/Providers/JwksProviderHttpClient.cs create mode 100644 SmartHealthCard.Token/Support/Result.cs diff --git a/SmartHealthCard.DecoderDemo/Program.cs b/SmartHealthCard.DecoderDemo/Program.cs index 62b2fce..41d2ad4 100644 --- a/SmartHealthCard.DecoderDemo/Program.cs +++ b/SmartHealthCard.DecoderDemo/Program.cs @@ -2,9 +2,12 @@ using SmartHealthCard.Token.Certificates; using SmartHealthCard.Token.Model.Jwks; using SmartHealthCard.Token.Model.Shc; +using SmartHealthCard.Token.Providers; +using SmartHealthCard.Token.Support; using System; using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; +using System.Threading; using System.Threading.Tasks; namespace SHC.DecoderDemo @@ -78,7 +81,7 @@ public MyJwksProvider(X509Certificate2 Certificate) this.Certificate = Certificate; } - public Task GetJwksAsync(Uri WellKnownJwksUri) + public Task> GetJwksAsync(Uri WellKnownJwksUri, CancellationToken? CancellationToken = null) { //In production the default implementation of this IJwksProvider interface would //retrieve the JWKS file from the provided 'WellKnownJwksUri' URL that is found in @@ -87,8 +90,11 @@ public Task GetJwksAsync(Uri WellKnownJwksUri) //own JWKS which we have generated from our certificate as seen below. //This allows you to test before you have a publicly exposed endpoint for you JWKS. SmartHealthCardJwks SmartHealthCardJwks = new SmartHealthCardJwks(); - SmartHealthCard.Token.Model.Jwks.JsonWebKeySet Jwks = SmartHealthCardJwks.GetJsonWebKeySet(new List() { Certificate }); - return Task.FromResult(Jwks); + JsonWebKeySet Jwks = SmartHealthCardJwks.GetJsonWebKeySet(new List() { Certificate }); + + return Task.FromResult(Result.Ok(Jwks)); } + + } } diff --git a/SmartHealthCard.Test/SmartHealthCardQRCodeEncoderTest.cs b/SmartHealthCard.Test/SmartHealthCardQRCodeEncoderTest.cs index d6ce72d..110f757 100644 --- a/SmartHealthCard.Test/SmartHealthCardQRCodeEncoderTest.cs +++ b/SmartHealthCard.Test/SmartHealthCardQRCodeEncoderTest.cs @@ -14,6 +14,7 @@ using Xunit; using System.Drawing; using System.Drawing.Imaging; +using System.IO; namespace SmartHealthCard.Test { @@ -35,7 +36,7 @@ public async void Encode_QRCode() string FhirBundleJson = FhirSerializer.SerializeToJson(FhirBundleResource); //The base of the URL where a validator will retie the public keys from (e.g : [Issuer]/.well-known/jwks.json) - Uri Issuer = new Uri("https://sonichealthcare.com/something"); + Uri Issuer = new Uri("https://localhost:44306/Smart-health-card"); //When the Smart Health Card became valid, the from date. DateTimeOffset IssuanceDateTimeOffset = DateTimeOffset.Now.AddMinutes(-1); @@ -62,13 +63,13 @@ public async void Encode_QRCode() SmartHealthCardQRCodeEncoder SmartHealthCardQRCodeFactory = new SmartHealthCardQRCodeEncoder(); List QRCodeImageList = SmartHealthCardQRCodeFactory.GetQRCodeList(SmartHealthCardJwsToken); - + //Write out QR Code to file - //for (int i = 0; i < QRCodeImageList.Length; i++) - //{ - // QRCodeImageList[i].Save(@$"C:\Temp\SMARTHealthCard\QRCode-{i}.png", ImageFormat.Png); - //} - + for (int i = 0; i < QRCodeImageList.Count; i++) + { + QRCodeImageList[i].Save(@$"C:\Temp\SMARTHealthCard\QRCode-{i}.png", ImageFormat.Png); + } + //### Assert ####################################################### Assert.True(!string.IsNullOrWhiteSpace(SmartHealthCardJwsToken)); @@ -93,7 +94,7 @@ public async void Decode_QRCodeRawData() string FhirBundleJson = FhirSerializer.SerializeToJson(FhirBundleResource); //The base of the URL where a validator will retie the public keys from (e.g : [Issuer]/.well-known/jwks.json) - Uri Issuer = new Uri("https://sonichealthcare.com/something"); + Uri Issuer = new Uri("https://localhost:44306/Smart-health-card"); //When the Smart Health Card became valid, the from date. DateTimeOffset IssuanceDateTimeOffset = DateTimeOffset.Now.AddMinutes(-1); @@ -103,7 +104,7 @@ public async void Decode_QRCodeRawData() List VerifiableCredentialTypeList = new List() { VerifiableCredentialType.Covid19 }; //Create the SmartHealthCardModel - SmartHealthCardModel SmartHealthCardToEncode = new SmartHealthCardModel(Issuer, IssuanceDateTimeOffset, + SmartHealthCardModel SmartHealthCardModel = new SmartHealthCardModel(Issuer, IssuanceDateTimeOffset, new VerifiableCredential(VerifiableCredentialTypeList, new CredentialSubject(FhirVersion, FhirBundleJson))); @@ -114,16 +115,23 @@ public async void Decode_QRCodeRawData() //### Act ########################################################## //Get the Smart Health Card retrieve Token - string SmartHealthCardJwsToken = await SmartHealthCardEncoder.GetTokenAsync(Certificate, SmartHealthCardToEncode); + string SmartHealthCardJwsToken = await SmartHealthCardEncoder.GetTokenAsync(Certificate, SmartHealthCardModel); //Create list of QR Codes SmartHealthCardQRCodeEncoder SmartHealthCardQRCodeEncoder = new SmartHealthCardQRCodeEncoder(); List QRCodeRawDataList = SmartHealthCardQRCodeEncoder.GetQRCodeRawDataList(SmartHealthCardJwsToken); + //Write out Raw QR Code data to file + for (int i = 0; i < QRCodeRawDataList.Count; i++) + { + File.WriteAllText(@$"C:\Temp\SMARTHealthCard\RawQRCodeData-{i}.txt", QRCodeRawDataList[i]); + } SmartHealthCardQRCodeDecoder SmartHealthCardQRCodeDecoder = new SmartHealthCardQRCodeDecoder(); string JWS = SmartHealthCardQRCodeDecoder.GetToken(QRCodeRawDataList); + SmartHealthCardDecoder SmartHealthCardDecoder = new SmartHealthCardDecoder(); + SmartHealthCardModel = await SmartHealthCardDecoder.DecodeAsync(JWS, true); //### Assert ####################################################### Assert.True(!string.IsNullOrWhiteSpace(JWS)); diff --git a/SmartHealthCard.Test/Support/JwksSupport.cs b/SmartHealthCard.Test/Support/JwksSupport.cs index 5566356..8c0f0d7 100644 --- a/SmartHealthCard.Test/Support/JwksSupport.cs +++ b/SmartHealthCard.Test/Support/JwksSupport.cs @@ -8,6 +8,7 @@ using SmartHealthCard.Token; using SmartHealthCard.Token.Model.Jwks; using SmartHealthCard.Token.Providers; +using SmartHealthCard.Token.Support; namespace SmartHealthCard.Test.Support { @@ -20,7 +21,8 @@ public static IJwksProvider GetMockedIJwksProvider(X509Certificate2 Certificate, JsonWebKeySet JsonWebKeySet = SmartHealthCardJwks.GetJsonWebKeySet(CertificateList); Uri WellKnownJwksUri = new Uri($"{Issuer.OriginalString}/.well-known/jwks.json"); var JWKSProviderMock = new Mock(); - JWKSProviderMock.Setup(x => x.GetJwksAsync(WellKnownJwksUri)).ReturnsAsync(JsonWebKeySet); + + JWKSProviderMock.Setup(x => x.GetJwksAsync(WellKnownJwksUri, null)).ReturnsAsync(Result.Ok(JsonWebKeySet)); return JWKSProviderMock.Object; } } diff --git a/SmartHealthCard.Token/Algorithms/ES256Algorithm.cs b/SmartHealthCard.Token/Algorithms/ES256Algorithm.cs index 6319986..10cc716 100644 --- a/SmartHealthCard.Token/Algorithms/ES256Algorithm.cs +++ b/SmartHealthCard.Token/Algorithms/ES256Algorithm.cs @@ -110,7 +110,7 @@ public string GetPointCoordinateY() public static ES256Algorithm FromJWKS(string Kid, JsonWebKeySet JsonWebKeySet, IJsonSerializer JsonSerializer) { - JsonWebKey? Key = JsonWebKeySet.Keys.SingleOrDefault(x => x.Kid.Equals(Kid, StringComparison.CurrentCulture)); + JsonWebKey? Key = JsonWebKeySet.Keys.Find(x => x.Kid.Equals(Kid, StringComparison.CurrentCulture)); if (Key is null) throw new JsonWebKeySetException($"No key matching the token's header kid value of {Kid} found in the sourced JWKS file."); diff --git a/SmartHealthCard.Token/JwsToken/JwsDecoder.cs b/SmartHealthCard.Token/JwsToken/JwsDecoder.cs index af6545e..02aa580 100644 --- a/SmartHealthCard.Token/JwsToken/JwsDecoder.cs +++ b/SmartHealthCard.Token/JwsToken/JwsDecoder.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using SmartHealthCard.Token.Providers; using SmartHealthCard.Token.Serializers.Json; +using SmartHealthCard.Token.Support; namespace SmartHealthCard.Token.JwsToken { @@ -74,7 +75,16 @@ public async Task DecodePayloadAsync(strin if (JwksProvider is null) throw new SignatureVerificationException($"When Verify is true {nameof(this.JwksProvider)} must be not null."); - JsonWebKeySet JsonWebKeySet = await JwksProvider.GetJwksAsync(WellKnownJwksUri); + Result JsonWebKeySetResult = await JwksProvider.GetJwksAsync(WellKnownJwksUri); + JsonWebKeySet JsonWebKeySet; + if (JsonWebKeySetResult.Failure) + { + throw new SignatureVerificationException($"Unable to obtain the JsonWebKeySet (JWKS) from : {WellKnownJwksUri.OriginalString}. ErrorMessage: {JsonWebKeySetResult.ErrorMessage}"); + } + else + { + JsonWebKeySet = JsonWebKeySetResult.Value; + } string Header = new JwsParts(Token).Header; byte[] DecodedHeader = Base64UrlEncoder.Decode(Header); diff --git a/SmartHealthCard.Token/JwsToken/SmartHealthCardJwsDecoder.cs b/SmartHealthCard.Token/JwsToken/SmartHealthCardJwsDecoder.cs index 3a9f324..94cfed9 100644 --- a/SmartHealthCard.Token/JwsToken/SmartHealthCardJwsDecoder.cs +++ b/SmartHealthCard.Token/JwsToken/SmartHealthCardJwsDecoder.cs @@ -5,6 +5,7 @@ using SmartHealthCard.Token.Providers; using SmartHealthCard.Token.Serializers.Json; using SmartHealthCard.Token.Serializers.Jws; +using SmartHealthCard.Token.Support; using SmartHealthCard.Token.Validators; using System; using System.Threading.Tasks; @@ -73,8 +74,17 @@ public async Task DecodePayloadAsync(strin if (JwksProvider is null) throw new SignatureVerificationException($"When Verify is true {nameof(this.JwksProvider)} must be not null."); - - JsonWebKeySet JsonWebKeySet = await JwksProvider.GetJwksAsync(WellKnownJwksUri); + + Result JsonWebKeySetResult = await JwksProvider.GetJwksAsync(WellKnownJwksUri); + JsonWebKeySet JsonWebKeySet; + if (JsonWebKeySetResult.Success) + { + JsonWebKeySet = JsonWebKeySetResult.Value; + } + else + { + throw new SignatureVerificationException($"Unable to obtain the JsonWebKeySet (JWKS) from : {WellKnownJwksUri.OriginalString}. ErrorMessage: {JsonWebKeySetResult.ErrorMessage}"); + } string Header = new JwsParts(Token).Header; byte[] DecodedHeader = Base64UrlEncoder.Decode(Header); diff --git a/SmartHealthCard.Token/Model/Jwks/JsonWebKeySet.cs b/SmartHealthCard.Token/Model/Jwks/JsonWebKeySet.cs index 1f91563..e199195 100644 --- a/SmartHealthCard.Token/Model/Jwks/JsonWebKeySet.cs +++ b/SmartHealthCard.Token/Model/Jwks/JsonWebKeySet.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json; using System.Collections.Generic; +using System; namespace SmartHealthCard.Token.Model.Jwks { @@ -8,7 +9,7 @@ public class JsonWebKeySet [JsonConstructor] public JsonWebKeySet(List Keys) { - this.Keys = Keys; + this.Keys = Keys ?? throw new ArgumentNullException(nameof(Keys)); } public JsonWebKeySet() diff --git a/SmartHealthCard.Token/Providers/IJwksProvider.cs b/SmartHealthCard.Token/Providers/IJwksProvider.cs index 052f7fb..045f159 100644 --- a/SmartHealthCard.Token/Providers/IJwksProvider.cs +++ b/SmartHealthCard.Token/Providers/IJwksProvider.cs @@ -1,4 +1,5 @@ using SmartHealthCard.Token.Model.Jwks; +using SmartHealthCard.Token.Support; using System; using System.Threading.Tasks; @@ -6,6 +7,6 @@ namespace SmartHealthCard.Token.Providers { public interface IJwksProvider { - Task GetJwksAsync(Uri WellKnownJwksUri); + Task> GetJwksAsync(Uri WellKnownJwksUri, System.Threading.CancellationToken? CancellationToken = null); } } \ No newline at end of file diff --git a/SmartHealthCard.Token/Providers/JwksProvider.cs b/SmartHealthCard.Token/Providers/JwksProvider.cs index 38b4ff5..2b54d52 100644 --- a/SmartHealthCard.Token/Providers/JwksProvider.cs +++ b/SmartHealthCard.Token/Providers/JwksProvider.cs @@ -1,5 +1,6 @@ using SmartHealthCard.Token.Model.Jwks; using SmartHealthCard.Token.Serializers.Json; +using SmartHealthCard.Token.Support; using System; using System.Net.Http; using System.Threading.Tasks; @@ -9,16 +10,17 @@ namespace SmartHealthCard.Token.Providers public class JwksProvider : IJwksProvider { private readonly IJsonSerializer JsonSerializer; + private readonly HttpClient HttpClient; public JwksProvider(IJsonSerializer JsonSerializer) { this.JsonSerializer = JsonSerializer; + this.HttpClient = new HttpClient(); } - public async Task GetJwksAsync(Uri WellKnownJwksUri) + public async Task> GetJwksAsync(Uri WellKnownJwksUri, System.Threading.CancellationToken? CancellationToken) { - var HttpClient = new HttpClient(); - var JwksJson = await HttpClient.GetStringAsync(WellKnownJwksUri); - return JsonSerializer.FromJson(JwksJson); + JwksProviderHttpClient Client = new JwksProviderHttpClient(this.JsonSerializer, this.HttpClient); + return await Client.Get(WellKnownJwksUri, CancellationToken); } } } diff --git a/SmartHealthCard.Token/Providers/JwksProviderHttpClient.cs b/SmartHealthCard.Token/Providers/JwksProviderHttpClient.cs new file mode 100644 index 0000000..ee8866f --- /dev/null +++ b/SmartHealthCard.Token/Providers/JwksProviderHttpClient.cs @@ -0,0 +1,94 @@ +using SmartHealthCard.Token.Model.Jwks; +using SmartHealthCard.Token.Serializers.Json; +using SmartHealthCard.Token.Support; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace SmartHealthCard.Token.Providers +{ + public class JwksProviderHttpClient + { + private readonly HttpClient HttpClient; + private readonly IJsonSerializer JsonSerializer; + + public JwksProviderHttpClient(IJsonSerializer JsonSerializer, HttpClient HttpClient) + { + this.HttpClient = HttpClient; + this.JsonSerializer = JsonSerializer; + } + + + public async Task> Get(Uri WellKnownUrl, CancellationToken? CancellationToken = null) + { + var request = new HttpRequestMessage(HttpMethod.Get, WellKnownUrl); + try + { + using (var response = await HttpClient.SendAsync(request, CancellationToken ?? new CancellationToken())) + { + if (response.StatusCode == HttpStatusCode.OK) + { + if (response.Content == null) + { + return Result.Fail("Response content was null"); + } + + System.IO.Stream ResponseStream = await response.Content.ReadAsStreamAsync(); + //var responseJson = await response.Content.ReadAsStringAsync(); + Result JsonWebKeySetResult = JsonSerializer.FromJsonStream(ResponseStream); + + if (JsonWebKeySetResult.Success) + { + return JsonWebKeySetResult; + } + else + { + + + return Result.Fail($"Failed to deserialize response: {JsonWebKeySetResult.ErrorMessage}"); + } + } + else + { + string Message = string.Empty; + if (response.Content != null) + { + var ErrorResponseContent = await response.Content.ReadAsStringAsync(); + return Result.Fail($"Response status: {response.StatusCode}, Content: {ErrorResponseContent}"); + } + else + { + return Result.Fail($"Response status: {response.StatusCode}, Content: [None]"); + } + } + } + + } + catch(HttpRequestException HttpRequestException) + { + //_logger.LogError(Exec, "HttpRequestException when calling the API"); + return Result.Retry($"HttpRequestException when calling the API: {HttpRequestException.Message}"); + } + catch (TimeoutException) + { + //_logger.LogError(exception, "TimeoutException during call to API"); + return Result.Retry("TimeoutException during call to API"); + } + catch (OperationCanceledException) + { + //_logger.LogError(exception, "Task was canceled during call to API"); + return Result.Retry("Task was canceled during call to API"); + } + catch (Exception) + { + //_logger.LogError(exception, "Unhanded exception when calling the API"); + throw; + } + } + } +} diff --git a/SmartHealthCard.Token/Serializers/Json/IJsonSerializer.cs b/SmartHealthCard.Token/Serializers/Json/IJsonSerializer.cs index beb22d5..c05a204 100644 --- a/SmartHealthCard.Token/Serializers/Json/IJsonSerializer.cs +++ b/SmartHealthCard.Token/Serializers/Json/IJsonSerializer.cs @@ -1,4 +1,8 @@ -namespace SmartHealthCard.Token.Serializers.Json +using SmartHealthCard.Token.Providers; +using SmartHealthCard.Token.Support; +using System.IO; + +namespace SmartHealthCard.Token.Serializers.Json { public interface IJsonSerializer { @@ -11,5 +15,7 @@ public interface IJsonSerializer /// De-serialize a JSON string to typed object. /// T FromJson(string Json); + + public Result FromJsonStream(Stream JsonStream); } } diff --git a/SmartHealthCard.Token/Serializers/Json/JsonSerializer.cs b/SmartHealthCard.Token/Serializers/Json/JsonSerializer.cs index e3537c6..ffe2f95 100644 --- a/SmartHealthCard.Token/Serializers/Json/JsonSerializer.cs +++ b/SmartHealthCard.Token/Serializers/Json/JsonSerializer.cs @@ -1,7 +1,10 @@ using Newtonsoft.Json; using SmartHealthCard.Token.Encoders; using SmartHealthCard.Token.Exceptions; +using SmartHealthCard.Token.Providers; using SmartHealthCard.Token.Serializers.Jws; +using SmartHealthCard.Token.Support; +using System; using System.IO; using System.Text; using System.Threading.Tasks; @@ -53,6 +56,28 @@ public T FromJson(string Json) return Item; } - - } + public Result FromJsonStream(Stream JsonStream) + { + try + { + using (var streamReader = new StreamReader(JsonStream)) + { + using (var jsonReader = new JsonTextReader(streamReader)) + { + T? Item = Serializer.Deserialize(jsonReader); + if (Item is null) + throw new DeserializationException($"Unable to deserialize the JWS Header to type {typeof(T).Name}"); + + return Result.Ok(Item); + } + } + } + catch(Exception Exec) + { + return Result.Fail($"Unable to parser the returned content to JWKS. Message: {Exec.Message }"); + } + } + + + } } diff --git a/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsHeaderSerializer.cs b/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsHeaderSerializer.cs index 3572e36..b4b9323 100644 --- a/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsHeaderSerializer.cs +++ b/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsHeaderSerializer.cs @@ -1,7 +1,9 @@ using SmartHealthCard.Token.Model.Shc; using SmartHealthCard.Token.Serializers.Json; using SmartHealthCard.Token.Serializers.Jws; +using SmartHealthCard.Token.Support; using System; +using System.IO; using System.Threading.Tasks; namespace SmartHealthCard.Token.Serializers.Shc @@ -44,5 +46,10 @@ public async Task DeserializeAsync(byte[] bytes) public string ToJson(T Obj, bool Minified = true) => JsonSerializer.ToJson(Obj); public T FromJson(string Json) => JsonSerializer.FromJson(Json); + + public Result FromJsonStream(Stream JsonStream) + { + return JsonSerializer.FromJsonStream(JsonStream); + } } } diff --git a/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsPayloadSerializer.cs b/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsPayloadSerializer.cs index da69cff..1cecdda 100644 --- a/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsPayloadSerializer.cs +++ b/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsPayloadSerializer.cs @@ -3,7 +3,9 @@ using SmartHealthCard.Token.Model.Shc; using SmartHealthCard.Token.Serializers.Json; using SmartHealthCard.Token.Serializers.Jws; +using SmartHealthCard.Token.Support; using System; +using System.IO; using System.Threading.Tasks; namespace SmartHealthCard.Token.Serializers.Shc @@ -54,5 +56,9 @@ public async Task DeserializeAsync(byte[] bytes) public T FromJson(string Json) => JsonSerializer.FromJson(Json); + public Result FromJsonStream(Stream JsonStream) + { + return JsonSerializer.FromJsonStream(JsonStream); + } } } diff --git a/SmartHealthCard.Token/Support/Result.cs b/SmartHealthCard.Token/Support/Result.cs new file mode 100644 index 0000000..11e75d3 --- /dev/null +++ b/SmartHealthCard.Token/Support/Result.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartHealthCard.Token.Support +{ + public class Result + { + protected Result(bool Success, bool Retryable, string ErrorMessage) + { + this.Success = Success; + this.ErrorMessage = ErrorMessage; + this.Retryable = Retryable; + } + /// + /// The action was Successful and the result value property will be set. + /// + public bool Success { get; private set; } + /// + /// Indicates that the operation failed and no Result value can be returned. Refer the ErrorMessage for more detail. + /// + public bool Failure => !Success; + /// + /// Indicate that this result believes the operation could be retried again. + /// It is a decision of the caller to check Retryable or not, checking Success or Failure is usualy enough. + /// /// If Retryable is True then Success SHALL BE False and Failure SHALL BE True + /// If Success is True then Retryable will be False + /// + public bool Retryable { get; private set; } + /// + /// An error message that is populated when Success is False and Failure is true, is empty string of Success + /// + public string ErrorMessage { get; private set; } + + public static Result Ok() + { + return new Result(Success: true, Retryable: false, ErrorMessage: string.Empty); + } + + public static Result Retry(string message) + { + return new Result(Success: false, Retryable: true, ErrorMessage: message); + } + + public static Result Fail(string message) + { + return new Result(Success: false, Retryable: false, ErrorMessage: message); + } + + + } + + public class Result : Result + { + private T? ResultValue { get; set; } + public T Value + { + get + { + if (Success && ResultValue is object) + { + return ResultValue; + } + else + { + throw new ArgumentNullException(""); + } + } + } + + public static Result Ok(T value) + { + return new Result(Value: value, Success: true, ErrorMessage: string.Empty); + } + + public static new Result Retry(string message) + { + return new Result(Success: false, Retryable: true, ErrorMessage: message); + } + + public static new Result Fail(string message) + { + return new Result(Success: false, Retryable: false, ErrorMessage: message); + } + + + protected internal Result(T Value, bool Success, string ErrorMessage) + : base(Success: Success, Retryable: false, ErrorMessage: ErrorMessage) + { + this.ResultValue = Value; + } + + protected internal Result(bool Success, bool Retryable, string ErrorMessage) + : base(Success: Success, Retryable: false, ErrorMessage: ErrorMessage) + { + this.ResultValue = default; + } + + } + + +} From 1a50deb6d0015e441b86b0e3d143a465404656af Mon Sep 17 00:00:00 2001 From: angusmillar Date: Tue, 1 Jun 2021 00:24:15 +1000 Subject: [PATCH 3/6] Added some ReadMe files to explain how to create ECC 256 keys and Cert files Added ReadMe to explain how to upload the Nuget package. Added and moved all testcases to use PEM files to load the certificate, no no longer rely on having the cert installed in the windows certificate manager for development, use PEM file imbedded as resources in the Test project. Added a Covid result of Not Detected to test project Added support methods to load X509Certificate2 from PEM files Added more validation to the JWKS de-serializer --- ReadMeOpenSSLCreateECCkeys.txt | 93 +++++++++++++++++++ ...ublishReadme.txt => ReadmeNugetPublish.txt | 2 +- SmartHealthCard.Test/Model/FhirDataSupport.cs | 36 ++++++- SmartHealthCard.Test/ResourceData.Designer.cs | 30 ++++++ SmartHealthCard.Test/ResourceData.resx | 9 ++ .../Resources/InvalidJwks.json | 12 +++ .../Resources/TestECC256Cert.pem | 17 ++++ .../Resources/TestECC256Private-key.pem | 5 + .../SmartHealthCardDecoderTest.cs | 10 +- .../SmartHealthCardEncoderTest.cs | 10 +- .../SmartHealthCardJwksTest.cs | 45 ++++++++- .../SmartHealthCardQRCodeEncoderTest.cs | 37 +++++--- .../Support/CertificateSupport.cs | 11 +++ SmartHealthCard.Test/Support/JwksSupport.cs | 2 + .../Support/SmartHealthCardJwsSupport.cs | 2 +- .../Model/Jwks/JsonWebKey.cs | 16 ++-- .../Model/Jwks/JsonWebKeySet.cs | 4 +- SmartHealthCard.sln | 3 +- 18 files changed, 299 insertions(+), 45 deletions(-) create mode 100644 ReadMeOpenSSLCreateECCkeys.txt rename NugetPublishReadme.txt => ReadmeNugetPublish.txt (94%) create mode 100644 SmartHealthCard.Test/Resources/InvalidJwks.json create mode 100644 SmartHealthCard.Test/Resources/TestECC256Cert.pem create mode 100644 SmartHealthCard.Test/Resources/TestECC256Private-key.pem diff --git a/ReadMeOpenSSLCreateECCkeys.txt b/ReadMeOpenSSLCreateECCkeys.txt new file mode 100644 index 0000000..1d2d33f --- /dev/null +++ b/ReadMeOpenSSLCreateECCkeys.txt @@ -0,0 +1,93 @@ +Taken from : https://www.scottbrady91.com/OpenSSL/Creating-Elliptical-Curve-Keys-using-OpenSSL + +Recently, I have been using OpenSSL to generate private keys and X509 certificates +for Elliptical Curve Cryptography (ECC) and then using them in ASP.NET Core for token signing. + +In this article, I’m going to show you how to use OpenSSL to generate private and public +keys on the curve of your choice. Check out my other article for how to do the same for RSA +keys. + +OpenSSL ECDSA Cheat Sheet +------------------------------------------------------------------- +# find your curve +openssl ecparam -list_curves + +# generate a private key for a curve +openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem + +# generate corresponding public key +openssl ec -in private-key.pem -pubout -out public-key.pem + +# optional: create a self-signed certificate +openssl req -new -x509 -key private-key.pem -out cert.pem -days 360 + +# optional: convert pem to pfx +openssl pkcs12 -export -inkey private-key.pem -in cert.pem -out cert.pfx +Generating an Elliptical Curve Private Key Using OpenSSL +To start, you will need to choose the curve you will be working with. You can use the +following command to see a list of +supported curve names and descriptions. + +openssl ecparam -list_curves +In this example, I am using prime256v1 (secp256r1), which is suitable for JWT signing; +this is the curve used for JOSE’s ES256. +------------------------------------------------------------------- + +You can now generate a private key: + +openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem +This should give you a PEM file containing your EC private key, which looks something +like the following: + +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIKEubpBiHkZQYlORbCy8gGTz8tzrWsjBJA6GfFCrQ98coAoGCCqGSM49 +AwEHoUQDQgAEOr6rMmRRNKuZuwws/hWwFTM6ECEEaJGGARCJUO4UfoURl8b4JThG +t8VDFKeR2i+ZxE+xh/wTBaJ/zvtSqZiNnQ== +-----END EC PRIVATE KEY----- + +Creating an EC Public Key from a Private Key Using OpenSSL +Now that you have your private key, you can use it to generate another PEM, containing +only your public key. + +openssl ec -in private-key.pem -pubout -out public-key.pem +This should give you another PEM file, containing the public key: + +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOr6rMmRRNKuZuwws/hWwFTM6ECEE +aJGGARCJUO4UfoURl8b4JThGt8VDFKeR2i+ZxE+xh/wTBaJ/zvtSqZiNnQ== +-----END PUBLIC KEY----- + +Creating an EC Self-Signed Certificate Using OpenSSL +Now that you have a private key, you could use it to generate a self-signed certificate. +This is not required, but it allows you to use the key +for server/client authentication, or gain X509 specific functionality in technologies +such as JWT and SAML. + +openssl req -new -x509 -key private-key.pem -out cert.pem -days 360 +This will again generate another PEM file, this time containing the certificate created +by your private key: + +-----BEGIN CERTIFICATE----- +MIIB4DCCAYWgAwIBAgIUH53ssiPt4JEGx+VJyntCpHL+TdAwCgYIKoZIzj0EAwIw +RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu +dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDA3MTgxMTE4NDNaFw0yMTA3MTMx +MTE4NDNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD +VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwWTATBgcqhkjOPQIBBggqhkjO +PQMBBwNCAAQ6vqsyZFE0q5m7DCz+FbAVMzoQIQRokYYBEIlQ7hR+hRGXxvglOEa3 +xUMUp5HaL5nET7GH/BMFon/O+1KpmI2do1MwUTAdBgNVHQ4EFgQU9yjFBqAZOMv+ +cD6a3KHTWuYrcFEwHwYDVR0jBBgwFoAU9yjFBqAZOMv+cD6a3KHTWuYrcFEwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNJADBGAiEAwCpA5Nx083qqUqU6LUd0 +vzZLK4etuInxNvXohXH5LiACIQDSI63J4DiN3dq2sPPLw5iQi9MMefcV1iAySbKT +B9BaAw== +-----END CERTIFICATE----- + +You could leave things there, but if you are working on Windows, you may prefer a PFX +file that contains both the certificate and the private key for you to export and use. + +You can do this using OpenSSL’s pkcs12 command: + +openssl pkcs12 -export -inkey private-key.pem -in cert.pem -out cert.pfx +OpenSSL will ask you to create a password for the PFX file. Feel free to leave this blank. + +This should leave you with a certificate that Windows can both install and export the +EC private key from. \ No newline at end of file diff --git a/NugetPublishReadme.txt b/ReadmeNugetPublish.txt similarity index 94% rename from NugetPublishReadme.txt rename to ReadmeNugetPublish.txt index 127f06a..125d0ad 100644 --- a/NugetPublishReadme.txt +++ b/ReadmeNugetPublish.txt @@ -1,4 +1,4 @@ -How to pubish to Nuget: +How to publish to Nuget: 1. Rev the version in the project package dialog and save 2. Right click the project and select Pack diff --git a/SmartHealthCard.Test/Model/FhirDataSupport.cs b/SmartHealthCard.Test/Model/FhirDataSupport.cs index 2290e52..ad81c0f 100644 --- a/SmartHealthCard.Test/Model/FhirDataSupport.cs +++ b/SmartHealthCard.Test/Model/FhirDataSupport.cs @@ -10,10 +10,10 @@ namespace SmartHealthCard.Test.Model { static class FhirDataSupport { - public static Bundle GetCovid19FhirBundleExample1() + public static Bundle GetCovid19DetectedFhirBundleExample() { - Patient PatientResource = GetPatientResource("TestFamilyName", "TestGivenName", new DateTime(1973, 09, 30), "61481059995"); - + Patient PatientResource = GetPatientResource("Coyote", "Wile E", new DateTime(1973, 09, 30), "61481059995"); + Coding Code = new Coding(system: "http://loinc.org", code: "94558-4"); //SARS-CoV-2 (COVID-19) Ag [Presence] in Respiratory specimen by Rapid immunoassay Coding Value = new Coding(system: "http://snomed.info/sct", code: "260373001"); //Detected Observation CovidResultObservationResource = GetObservationResource( @@ -36,6 +36,32 @@ public static Bundle GetCovid19FhirBundleExample1() return Bundle; } + public static Bundle GetCovid19NotDetectedFhirBundleExample() + { + Patient PatientResource = GetPatientResource("Coyote", "Wile E", new DateTime(1973, 09, 30), "61481059995"); + + Coding Code = new Coding(system: "http://loinc.org", code: "94558-4"); //SARS-CoV-2 (COVID-19) Ag [Presence] in Respiratory specimen by Rapid immunoassay + Coding Value = new Coding(system: "http://snomed.info/sct", code: "260415000"); //Not Detected + Observation CovidResultObservationResource = GetObservationResource( + ObsCode: Code, + ObsValue: Value, + EffectiveDate: new DateTime(2021, 05, 24), + PerformerOrganisationName: "ACME Healthcare", + IdentityAssuranceLevelCode: "IAL1.4"); + + List BundleResourceList = new List() + { + PatientResource, + CovidResultObservationResource + }; + + Bundle Bundle = new Bundle(); + Bundle.Type = Bundle.BundleType.Collection; + Bundle.Entry = GetBundleResourceEntryList(BundleResourceList); + + return Bundle; + } + private static Patient GetPatientResource(string FamilyName, string GivenName, DateTime DateOfBirth, string PhoneNumber) { Patient Patient = new Patient(); @@ -77,7 +103,7 @@ private static Observation GetObservationResource(Coding ObsCode, Coding ObsValu { Coding = new List() { - new Coding(system: "http://loinc.org", code: "94558-4") + ObsCode } }; @@ -86,7 +112,7 @@ private static Observation GetObservationResource(Coding ObsCode, Coding ObsValu { Coding = new List() { - new Coding(system: "http://snomed.info/sct", code: "260373001") + ObsValue } }; Observation.Performer = new List() diff --git a/SmartHealthCard.Test/ResourceData.Designer.cs b/SmartHealthCard.Test/ResourceData.Designer.cs index bb365af..50a0db4 100644 --- a/SmartHealthCard.Test/ResourceData.Designer.cs +++ b/SmartHealthCard.Test/ResourceData.Designer.cs @@ -60,6 +60,16 @@ internal ResourceData() { } } + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] InvalidJwks { + get { + object obj = ResourceManager.GetObject("InvalidJwks", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// Looks up a localized resource of type System.Byte[]. /// @@ -69,5 +79,25 @@ internal static byte[] SmartHealthCardCovidExample { return ((byte[])(obj)); } } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] TestECC256Cert { + get { + object obj = ResourceManager.GetObject("TestECC256Cert", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] TestECC256Private_key { + get { + object obj = ResourceManager.GetObject("TestECC256Private_key", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/SmartHealthCard.Test/ResourceData.resx b/SmartHealthCard.Test/ResourceData.resx index f746aaf..efcf241 100644 --- a/SmartHealthCard.Test/ResourceData.resx +++ b/SmartHealthCard.Test/ResourceData.resx @@ -118,7 +118,16 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Resources\InvalidJwks.json;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + Resources\SmartHealthCardCovidExample.json;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Resources\TestECC256Cert.pem;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Resources\TestECC256Private-key.pem;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/SmartHealthCard.Test/Resources/InvalidJwks.json b/SmartHealthCard.Test/Resources/InvalidJwks.json new file mode 100644 index 0000000..65fa38c --- /dev/null +++ b/SmartHealthCard.Test/Resources/InvalidJwks.json @@ -0,0 +1,12 @@ +{ + "keys": [ + { + "kty": "EC", + "kid": "__q7Q4cf6G69ExQSrfW5A_2c3mJrtrRPBhtoRTyZlQM", + "use": "sig", + "alg": "ES256", + "crv": "P-256", + "y": "OPR0AnWPAmhFC6y1RAXFvsAGS0ptfUwuoTKkWXpP4bE" + } + ] +} \ No newline at end of file diff --git a/SmartHealthCard.Test/Resources/TestECC256Cert.pem b/SmartHealthCard.Test/Resources/TestECC256Cert.pem new file mode 100644 index 0000000..d999149 --- /dev/null +++ b/SmartHealthCard.Test/Resources/TestECC256Cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICnTCCAkOgAwIBAgIUVNNmHJZG+2m2bE2NjuYXs5UholswCgYIKoZIzj0EAwIw +gaMxCzAJBgNVBAYTAkFVMQwwCgYDVQQIDANRTEQxETAPBgNVBAcMCEJyaXNiYW5l +MRcwFQYDVQQKDA5Tb25pY0hlYWxoY2FyZTEQMA4GA1UECwwHU29uaWNJVDEUMBIG +A1UEAwwLQW5ndXNNSWxsYXIxMjAwBgkqhkiG9w0BCQEWI2FuZ3VzLm1pbGxhckBz +b25pY2hlYWx0aGNhcmUuY29tLmF1MB4XDTIxMDUxMDA0NTg0MFoXDTIyMDUwNTA0 +NTg0MFowgaMxCzAJBgNVBAYTAkFVMQwwCgYDVQQIDANRTEQxETAPBgNVBAcMCEJy +aXNiYW5lMRcwFQYDVQQKDA5Tb25pY0hlYWxoY2FyZTEQMA4GA1UECwwHU29uaWNJ +VDEUMBIGA1UEAwwLQW5ndXNNSWxsYXIxMjAwBgkqhkiG9w0BCQEWI2FuZ3VzLm1p +bGxhckBzb25pY2hlYWx0aGNhcmUuY29tLmF1MFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAEj2oyU1JyNT1x66i+PFsdsU1qL+y/Nxq7RjwKkd5kNyc49HQCdY8CaEUL +rLVEBcW+wAZLSm19TC6hMqRZek/hsaNTMFEwHQYDVR0OBBYEFFGC+JzgmozVIEv8 +59nMkGR/LIFYMB8GA1UdIwQYMBaAFFGC+JzgmozVIEv859nMkGR/LIFYMA8GA1Ud +EwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAKwGX0gIrQ1MmKKhf4cKUAI7 +PpSgKU3S+wiaNWuQ5aeRAiAF2lYQ+O2TEMVjT5+rm8gQEcmO2FEAvLs3N4hs1Guf +JQ== +-----END CERTIFICATE----- diff --git a/SmartHealthCard.Test/Resources/TestECC256Private-key.pem b/SmartHealthCard.Test/Resources/TestECC256Private-key.pem new file mode 100644 index 0000000..7edce13 --- /dev/null +++ b/SmartHealthCard.Test/Resources/TestECC256Private-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIHrua8isWUVjVBzQBt+s2QUzyGnh2uO20Siq+ha0pTZaoAoGCCqGSM49 +AwEHoUQDQgAEj2oyU1JyNT1x66i+PFsdsU1qL+y/Nxq7RjwKkd5kNyc49HQCdY8C +aEULrLVEBcW+wAZLSm19TC6hMqRZek/hsQ== +-----END EC PRIVATE KEY----- diff --git a/SmartHealthCard.Test/SmartHealthCardDecoderTest.cs b/SmartHealthCard.Test/SmartHealthCardDecoderTest.cs index c8622bc..0e6c23d 100644 --- a/SmartHealthCard.Test/SmartHealthCardDecoderTest.cs +++ b/SmartHealthCard.Test/SmartHealthCardDecoderTest.cs @@ -18,8 +18,10 @@ public class SmartHealthCardDecoderTest public async void Decode_Token_Verify_with_JWKS() { //### Prepare ###################################################### - //Get the ECC certificate from the Windows Certificate Store by Thumb-print - X509Certificate2 Certificate = CertificateSupport.GetCertificate(Thumbprint: CertificateSupport.TestingThumbprint); + + //Get the ECC certificate from the Cert and Private key PEM files + X509Certificate2 Certificate = CertificateSupport.GetCertificateFromPemFiles(); + List CertificateList = new List() { Certificate }; //The base of the URL where a validator will retrieve the public keys from (e.g : [Issuer]/.well-known/jwks.json) @@ -46,8 +48,8 @@ public async void Decode_Token_Verify_with_JWKS() public async void Decode_Token_Verify_with_Certificate() { //### Prepare ###################################################### - //Get the ECC certificate from the Windows Certificate Store by Thumb-print - X509Certificate2 Certificate = CertificateSupport.GetCertificate(Thumbprint: "72c78a3460fb27b9ef2ccfae2538675b75363fee"); + //Get the ECC certificate from the Cert and Private key PEM files + X509Certificate2 Certificate = CertificateSupport.GetCertificateFromPemFiles(); //The base of the URL where a validator will retrieve the public keys from (e.g : [Issuer]/.well-known/jwks.json) Uri Issuer = new Uri("https://sonichealthcare.com/something"); diff --git a/SmartHealthCard.Test/SmartHealthCardEncoderTest.cs b/SmartHealthCard.Test/SmartHealthCardEncoderTest.cs index f45bc0e..bf2600e 100644 --- a/SmartHealthCard.Test/SmartHealthCardEncoderTest.cs +++ b/SmartHealthCard.Test/SmartHealthCardEncoderTest.cs @@ -4,12 +4,14 @@ using SmartHealthCard.Test.Serializers; using SmartHealthCard.Test.Support; using SmartHealthCard.Token; +using SmartHealthCard.Token.Encoders; using SmartHealthCard.Token.Exceptions; using SmartHealthCard.Token.Model.Jwks; using SmartHealthCard.Token.Model.Shc; using SmartHealthCard.Token.Providers; using System; using System.Collections.Generic; +using System.IO; using System.Security.Cryptography.X509Certificates; using Xunit; @@ -22,14 +24,14 @@ public async void Create_Token_Decode_Token() { //### Prepare ###################################################### - //Get the ECC certificate from the Windows Certificate Store by Thumb-print - X509Certificate2 Certificate = CertificateSupport.GetCertificate(Thumbprint: CertificateSupport.TestingThumbprint); - + //Get the ECC certificate from the Cert and Private key PEM files + X509Certificate2 Certificate = CertificateSupport.GetCertificateFromPemFiles(); + //The Version of FHIR in use string FhirVersion = "4.0.1"; //Get FHIR bundle - Bundle FhirBundleResource = FhirDataSupport.GetCovid19FhirBundleExample1(); + Bundle FhirBundleResource = FhirDataSupport.GetCovid19DetectedFhirBundleExample(); string FhirBundleJson = FhirSerializer.SerializeToJson(FhirBundleResource); //The base of the URL where a validator will retrieve the public keys from (e.g : [Issuer]/.well-known/jwks.json) diff --git a/SmartHealthCard.Test/SmartHealthCardJwksTest.cs b/SmartHealthCard.Test/SmartHealthCardJwksTest.cs index eb8f7fa..0cd2fd4 100644 --- a/SmartHealthCard.Test/SmartHealthCardJwksTest.cs +++ b/SmartHealthCard.Test/SmartHealthCardJwksTest.cs @@ -9,6 +9,9 @@ using SmartHealthCard.Test.Support; using SmartHealthCard.Token.Serializers.Json; using SmartHealthCard.Token.Model.Jwks; +using SmartHealthCard.Token.Encoders; +using System.IO; +using System.Security.Cryptography; namespace SmartHealthCard.Test { @@ -19,14 +22,18 @@ public class SmartHealthCardJwksTest public void Jwks_Json_get() { //### Prepare ###################################################### + + //Get the ECC certificate from the Cert and Private key PEM files + X509Certificate2 Certificate = CertificateSupport.GetCertificateFromPemFiles(); + - X509Certificate2 Certificate = CertificateSupport.GetCertificate(Thumbprint: CertificateSupport.TestingThumbprint); + //X509Certificate2 Certificate = CertificateSupport.GetCertificate(Thumbprint: CertificateSupport.TestingThumbprint); List CertificateList = new List() { Certificate, Certificate, Certificate }; SmartHealthCardJwks SmartHealthCardJwks = new SmartHealthCardJwks(); JsonSerializer JsonSerializer = new JsonSerializer(); - - //### Act ########################################################## + //### Act ########################################################## + string JwksJson = SmartHealthCardJwks.Get(CertificateList, Minified: false); @@ -46,6 +53,36 @@ public void Jwks_Json_get() Assert.NotNull(JsonWebKeySet.Keys[0].Y); } - + //[Fact] + //public void invalid_Jwks_Json() + //{ + // //### Prepare ###################################################### + + + // JsonSerializer JsonSerializer = new JsonSerializer(); + + // string SmartHealthCardCovidJson = Utf8EncodingSupport.GetString(ResourceData.InvalidJwks); + + // //### Act ########################################################## + // string InvalidJwksJson = Utf8EncodingSupport.GetString(ResourceData.InvalidJwks); + + + + // //Parse the JSON back to the model + // JsonWebKeySet JsonWebKeySet = JsonSerializer.FromJson(InvalidJwksJson); + + + // //### Assert ####################################################### + // //Assert.NotNull(JwksJson); + // Assert.Equal(3, JsonWebKeySet.Keys.Count); + // Assert.Equal("EC", JsonWebKeySet.Keys[0].Kty); + // Assert.NotNull(JsonWebKeySet.Keys[0].Kid); + // Assert.Equal("sig", JsonWebKeySet.Keys[0].Use); + // Assert.Equal("ES256", JsonWebKeySet.Keys[0].Alg); + // Assert.Equal("P-256", JsonWebKeySet.Keys[0].Crv); + // Assert.NotNull(JsonWebKeySet.Keys[0].X); + // Assert.NotNull(JsonWebKeySet.Keys[0].Y); + //} + } } diff --git a/SmartHealthCard.Test/SmartHealthCardQRCodeEncoderTest.cs b/SmartHealthCard.Test/SmartHealthCardQRCodeEncoderTest.cs index 110f757..c45f27a 100644 --- a/SmartHealthCard.Test/SmartHealthCardQRCodeEncoderTest.cs +++ b/SmartHealthCard.Test/SmartHealthCardQRCodeEncoderTest.cs @@ -15,6 +15,7 @@ using System.Drawing; using System.Drawing.Imaging; using System.IO; +using SmartHealthCard.Token.Providers; namespace SmartHealthCard.Test { @@ -25,14 +26,15 @@ public async void Encode_QRCode() { //### Prepare ###################################################### - //Get the ECC certificate from the Windows Certificate Store by Thumb-print - X509Certificate2 Certificate = CertificateSupport.GetCertificate(Thumbprint: CertificateSupport.TestingThumbprint); + //Get the ECC certificate from the Cert and Private key PEM files + X509Certificate2 Certificate = CertificateSupport.GetCertificateFromPemFiles(); //The Version of FHIR in use string FhirVersion = "4.0.1"; //Get FHIR bundle - Bundle FhirBundleResource = FhirDataSupport.GetCovid19FhirBundleExample1(); + //Bundle FhirBundleResource = FhirDataSupport.GetCovid19DetectedFhirBundleExample(); + Bundle FhirBundleResource = FhirDataSupport.GetCovid19NotDetectedFhirBundleExample(); string FhirBundleJson = FhirSerializer.SerializeToJson(FhirBundleResource); //The base of the URL where a validator will retie the public keys from (e.g : [Issuer]/.well-known/jwks.json) @@ -65,10 +67,10 @@ public async void Encode_QRCode() //Write out QR Code to file - for (int i = 0; i < QRCodeImageList.Count; i++) - { - QRCodeImageList[i].Save(@$"C:\Temp\SMARTHealthCard\QRCode-{i}.png", ImageFormat.Png); - } + //for (int i = 0; i < QRCodeImageList.Count; i++) + //{ + // QRCodeImageList[i].Save(@$"C:\Temp\SMARTHealthCard\QRCode-{i}.png", ImageFormat.Png); + //} //### Assert ####################################################### @@ -83,14 +85,15 @@ public async void Decode_QRCodeRawData() { //### Prepare ###################################################### - //Get the ECC certificate from the Windows Certificate Store by Thumb-print - X509Certificate2 Certificate = CertificateSupport.GetCertificate(Thumbprint: CertificateSupport.TestingThumbprint); + //Get the ECC certificate from the Cert and Private key PEM files + X509Certificate2 Certificate = CertificateSupport.GetCertificateFromPemFiles(); //The Version of FHIR in use string FhirVersion = "4.0.1"; //Get FHIR bundle - Bundle FhirBundleResource = FhirDataSupport.GetCovid19FhirBundleExample1(); + //Bundle FhirBundleResource = FhirDataSupport.GetCovid19DetectedFhirBundleExample(); + Bundle FhirBundleResource = FhirDataSupport.GetCovid19NotDetectedFhirBundleExample(); string FhirBundleJson = FhirSerializer.SerializeToJson(FhirBundleResource); //The base of the URL where a validator will retie the public keys from (e.g : [Issuer]/.well-known/jwks.json) @@ -122,15 +125,19 @@ public async void Decode_QRCodeRawData() List QRCodeRawDataList = SmartHealthCardQRCodeEncoder.GetQRCodeRawDataList(SmartHealthCardJwsToken); //Write out Raw QR Code data to file - for (int i = 0; i < QRCodeRawDataList.Count; i++) - { - File.WriteAllText(@$"C:\Temp\SMARTHealthCard\RawQRCodeData-{i}.txt", QRCodeRawDataList[i]); - } + //for (int i = 0; i < QRCodeRawDataList.Count; i++) + //{ + // File.WriteAllText(@$"C:\Temp\SMARTHealthCard\RawQRCodeData-{i}.txt", QRCodeRawDataList[i]); + //} SmartHealthCardQRCodeDecoder SmartHealthCardQRCodeDecoder = new SmartHealthCardQRCodeDecoder(); string JWS = SmartHealthCardQRCodeDecoder.GetToken(QRCodeRawDataList); - SmartHealthCardDecoder SmartHealthCardDecoder = new SmartHealthCardDecoder(); + + //This testing JwksSupport class provides us with a mocked IJwksProvider that will inject the JWKS file + //rather than make the HTTP call to go get it from a public endpoint. + IJwksProvider MockedIJwksProvider = JwksSupport.GetMockedIJwksProvider(Certificate, Issuer); + SmartHealthCardDecoder SmartHealthCardDecoder = new SmartHealthCardDecoder(MockedIJwksProvider); SmartHealthCardModel = await SmartHealthCardDecoder.DecodeAsync(JWS, true); //### Assert ####################################################### diff --git a/SmartHealthCard.Test/Support/CertificateSupport.cs b/SmartHealthCard.Test/Support/CertificateSupport.cs index 0c3ba0d..2c62b11 100644 --- a/SmartHealthCard.Test/Support/CertificateSupport.cs +++ b/SmartHealthCard.Test/Support/CertificateSupport.cs @@ -1,4 +1,5 @@ using SmartHealthCard.Token.Certificates; +using SmartHealthCard.Token.Encoders; using System; using System.Collections.Generic; using System.Linq; @@ -22,5 +23,15 @@ public static X509Certificate2 GetCertificate(string Thumbprint) true ); } + + public static X509Certificate2 GetCertificateFromPemFiles() + { + string CertificatePEM = Utf8EncodingSupport.GetString(ResourceData.TestECC256Cert); + string PrivateKeyPEM = Utf8EncodingSupport.GetString(ResourceData.TestECC256Private_key); + return X509Certificate2.CreateFromPem(CertificatePEM, PrivateKeyPEM); + } + + + } } diff --git a/SmartHealthCard.Test/Support/JwksSupport.cs b/SmartHealthCard.Test/Support/JwksSupport.cs index 8c0f0d7..c963c73 100644 --- a/SmartHealthCard.Test/Support/JwksSupport.cs +++ b/SmartHealthCard.Test/Support/JwksSupport.cs @@ -25,5 +25,7 @@ public static IJwksProvider GetMockedIJwksProvider(X509Certificate2 Certificate, JWKSProviderMock.Setup(x => x.GetJwksAsync(WellKnownJwksUri, null)).ReturnsAsync(Result.Ok(JsonWebKeySet)); return JWKSProviderMock.Object; } + + } } diff --git a/SmartHealthCard.Test/Support/SmartHealthCardJwsSupport.cs b/SmartHealthCard.Test/Support/SmartHealthCardJwsSupport.cs index bbb7aa9..cdfc8e8 100644 --- a/SmartHealthCard.Test/Support/SmartHealthCardJwsSupport.cs +++ b/SmartHealthCard.Test/Support/SmartHealthCardJwsSupport.cs @@ -20,7 +20,7 @@ public static async Task GetJWSCovidExampleOneAsync(X509Certificate2 Cer string FhirVersion = "4.0.1"; //Get FHIR bundle - Bundle FhirBundleResource = FhirDataSupport.GetCovid19FhirBundleExample1(); + Bundle FhirBundleResource = FhirDataSupport.GetCovid19DetectedFhirBundleExample(); string FhirBundleJson = FhirSerializer.SerializeToJson(FhirBundleResource); //When the Smart Health Card became valid, the from date. diff --git a/SmartHealthCard.Token/Model/Jwks/JsonWebKey.cs b/SmartHealthCard.Token/Model/Jwks/JsonWebKey.cs index ce55312..660b3d3 100644 --- a/SmartHealthCard.Token/Model/Jwks/JsonWebKey.cs +++ b/SmartHealthCard.Token/Model/Jwks/JsonWebKey.cs @@ -12,22 +12,22 @@ public JsonWebKey(string Kty, string Kid, string Use, string Alg, string Crv, st this.Alg = Alg; this.Crv = Crv; this.X = X; - this.Y = Y; + this.Y = Y; } - [JsonProperty("kty")] + [JsonProperty("kty", Required = Required.Always)] public string Kty { get; set; } - [JsonProperty("kid")] + [JsonProperty("kid", Required = Required.Always)] public string Kid { get; set; } - [JsonProperty("use")] + [JsonProperty("use", Required = Required.Always)] public string Use { get; set; } - [JsonProperty("alg")] + [JsonProperty("alg", Required = Required.Always)] public string Alg { get; set; } - [JsonProperty("crv")] + [JsonProperty("crv", Required = Required.Always)] public string Crv { get; set; } - [JsonProperty("x")] + [JsonProperty("x", Required = Required.Always)] public string X { get; set; } - [JsonProperty("y")] + [JsonProperty("y", Required = Required.Always)] public string Y { get; set; } } } diff --git a/SmartHealthCard.Token/Model/Jwks/JsonWebKeySet.cs b/SmartHealthCard.Token/Model/Jwks/JsonWebKeySet.cs index e199195..5ebd82f 100644 --- a/SmartHealthCard.Token/Model/Jwks/JsonWebKeySet.cs +++ b/SmartHealthCard.Token/Model/Jwks/JsonWebKeySet.cs @@ -9,7 +9,7 @@ public class JsonWebKeySet [JsonConstructor] public JsonWebKeySet(List Keys) { - this.Keys = Keys ?? throw new ArgumentNullException(nameof(Keys)); + this.Keys = Keys; } public JsonWebKeySet() @@ -17,7 +17,7 @@ public JsonWebKeySet() this.Keys = new List(); } - [JsonProperty("keys")] + [JsonProperty("keys", Required = Required.Always)] public List Keys { get; set; } } } diff --git a/SmartHealthCard.sln b/SmartHealthCard.sln index 2755699..682cbec 100644 --- a/SmartHealthCard.sln +++ b/SmartHealthCard.sln @@ -12,9 +12,10 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{35043A8B-BD2F-4113-9DAF-59BE7E211F75}" ProjectSection(SolutionItems) = preProject .gitignore = .gitignore - NugetPublishReadme.txt = NugetPublishReadme.txt ReadMe.angus = ReadMe.angus README.md = README.md + ReadmeNugetPublish.txt = ReadmeNugetPublish.txt + ReadMeOpenSSLCreateECCkeys.txt = ReadMeOpenSSLCreateECCkeys.txt EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartHealthCard.EncoderDemo", "SmartHealthCard.EncoderDemo\SmartHealthCard.EncoderDemo.csproj", "{9207F04B-A1C8-47FE-8F7D-7D6C2780A159}" From b4e0f8b7d4c3d646cb3e2488bb7c7cd47786223c Mon Sep 17 00:00:00 2001 From: angusmillar Date: Fri, 4 Jun 2021 19:11:19 +1000 Subject: [PATCH 4/6] A lot of refactoring around error handing using the Result class --- .../SmartHealthCardEncoderTest.cs | 2 +- .../SmartHealthCardJwksTest.cs | 5 +- .../SmartHealthCardModelJsonSerializerTest.cs | 6 +- .../Algorithms/ES256Algorithm.cs | 57 ++++++----- .../Algorithms/IAlgorithm.cs | 10 +- .../Exceptions/InvalidTokenException.cs | 8 -- .../Exceptions/InvalidTokenPartsException.cs | 8 ++ .../Exceptions/JsonWebKeySetException.cs | 8 -- .../Exceptions/SignatureSigningException.cs | 9 -- .../SignatureVerificationException.cs | 8 -- .../SmartHealthCardHeaderException.cs | 10 -- SmartHealthCard.Token/JwsToken/IJwsDecoder.cs | 5 +- SmartHealthCard.Token/JwsToken/IJwsEncoder.cs | 5 +- .../JwsToken/IJwsHeaderValidator.cs | 6 +- .../JwsToken/IJwsPayloadValidator.cs | 6 +- .../JwsToken/IJwsSignatureValidator.cs | 3 +- SmartHealthCard.Token/JwsToken/JwsDecoder.cs | 81 ++------------- SmartHealthCard.Token/JwsToken/JwsEncoder.cs | 21 ++-- .../JwsToken/JwsSignatureValidator.cs | 27 +++-- .../JwsToken/SmartHealthCardJwsDecoder.cs | 98 ++++++++++++------- .../JwsToken/SmartHealthCardJwsEncoder.cs | 22 +++-- SmartHealthCard.Token/Model/Jws/JwsParts.cs | 35 +++---- .../Model/Shc/SmartHealthCardModel.cs | 14 +-- .../Shc/SmartHealthCareJWSHeaderModel.cs | 36 ++++--- .../Providers/JwksProviderHttpClient.cs | 22 +---- .../Serializers/Json/IJsonSerializer.cs | 4 +- .../Serializers/Json/JsonSerializer.cs | 57 +++++++---- .../Serializers/Jws/IJwsSerializer.cs | 7 +- .../Shc/SmartHealthCardJwsHeaderSerializer.cs | 26 ++--- .../SmartHealthCardJwsPayloadSerializer.cs | 32 ++++-- .../SmartHealthCardDecoder.cs | 18 +++- .../SmartHealthCardEncoder.cs | 24 ++++- SmartHealthCard.Token/SmartHealthCardJWKS.cs | 24 ++++- SmartHealthCard.Token/Support/Result.cs | 45 ++++++++- .../SmartHealthCardHeaderValidator.cs | 9 +- .../SmartHealthCardPayloadValidator.cs | 11 ++- 36 files changed, 434 insertions(+), 335 deletions(-) delete mode 100644 SmartHealthCard.Token/Exceptions/InvalidTokenException.cs create mode 100644 SmartHealthCard.Token/Exceptions/InvalidTokenPartsException.cs delete mode 100644 SmartHealthCard.Token/Exceptions/JsonWebKeySetException.cs delete mode 100644 SmartHealthCard.Token/Exceptions/SignatureSigningException.cs delete mode 100644 SmartHealthCard.Token/Exceptions/SignatureVerificationException.cs delete mode 100644 SmartHealthCard.Token/Exceptions/SmartHealthCardHeaderException.cs diff --git a/SmartHealthCard.Test/SmartHealthCardEncoderTest.cs b/SmartHealthCard.Test/SmartHealthCardEncoderTest.cs index bf2600e..08d5ec3 100644 --- a/SmartHealthCard.Test/SmartHealthCardEncoderTest.cs +++ b/SmartHealthCard.Test/SmartHealthCardEncoderTest.cs @@ -71,7 +71,7 @@ public async void Create_Token_Decode_Token() Assert.True(!string.IsNullOrWhiteSpace(SmartHealthCardJwsToken)); - SmartHealthCardModel DecodedSmartHealthCardModle = await Decoder .DecodeAsync(SmartHealthCardJwsToken, Verify: true); + SmartHealthCardModel DecodedSmartHealthCardModle = await Decoder.DecodeAsync(SmartHealthCardJwsToken, Verify: true); //Check the IssuanceDate as the same to seconds precision DateTimeOffset ActualIssuanceDate = DecodedSmartHealthCardModle.GetIssuanceDate(); diff --git a/SmartHealthCard.Test/SmartHealthCardJwksTest.cs b/SmartHealthCard.Test/SmartHealthCardJwksTest.cs index 0cd2fd4..d6cc57a 100644 --- a/SmartHealthCard.Test/SmartHealthCardJwksTest.cs +++ b/SmartHealthCard.Test/SmartHealthCardJwksTest.cs @@ -12,6 +12,7 @@ using SmartHealthCard.Token.Encoders; using System.IO; using System.Security.Cryptography; +using SmartHealthCard.Token.Support; namespace SmartHealthCard.Test { @@ -38,8 +39,8 @@ public void Jwks_Json_get() string JwksJson = SmartHealthCardJwks.Get(CertificateList, Minified: false); //Parse the JSON back to the model - JsonWebKeySet JsonWebKeySet = JsonSerializer.FromJson(JwksJson); - + Result JsonWebKeySetResult = JsonSerializer.FromJson(JwksJson); + JsonWebKeySet JsonWebKeySet = JsonWebKeySetResult.Value; //### Assert ####################################################### Assert.NotNull(JwksJson); diff --git a/SmartHealthCard.Test/SmartHealthCardModelJsonSerializerTest.cs b/SmartHealthCard.Test/SmartHealthCardModelJsonSerializerTest.cs index b0b2ce5..9f048be 100644 --- a/SmartHealthCard.Test/SmartHealthCardModelJsonSerializerTest.cs +++ b/SmartHealthCard.Test/SmartHealthCardModelJsonSerializerTest.cs @@ -8,6 +8,7 @@ using SmartHealthCard.Token.Serializers.Shc; using SmartHealthCard.Token.Model.Shc; using SmartHealthCard.Token.Encoders; +using SmartHealthCard.Token.Support; namespace SmartHealthCard.Test { @@ -22,8 +23,9 @@ public void Valid_SHC_Json() //### Act ########################################################## JsonSerializer JsonSerializer = new JsonSerializer(); - SmartHealthCardModel SmartHealthCardModel = JsonSerializer.FromJson(SmartHealthCardCovidJson); - + Result SmartHealthCardModelResult = JsonSerializer.FromJson(SmartHealthCardCovidJson); + SmartHealthCardModel SmartHealthCardModel = SmartHealthCardModelResult.Value; + //### Assert ####################################################### Assert.Equal("1621444043.769", SmartHealthCardModel.IssuanceDate); diff --git a/SmartHealthCard.Token/Algorithms/ES256Algorithm.cs b/SmartHealthCard.Token/Algorithms/ES256Algorithm.cs index 10cc716..3933d58 100644 --- a/SmartHealthCard.Token/Algorithms/ES256Algorithm.cs +++ b/SmartHealthCard.Token/Algorithms/ES256Algorithm.cs @@ -2,6 +2,7 @@ using SmartHealthCard.Token.Exceptions; using SmartHealthCard.Token.Model.Jwks; using SmartHealthCard.Token.Serializers.Json; +using SmartHealthCard.Token.Support; using System; using System.Linq; using System.Security.Cryptography; @@ -54,42 +55,43 @@ public ES256Algorithm(ECDsa? PublicKey, ECDsa? PrivateKey, IJsonSerializer JsonS private HashAlgorithmName HashAlgorithmName => HashAlgorithmName.SHA256; - public byte[] Sign(byte[] bytesToSign) + public Result Sign(byte[] bytesToSign) { if (this.PrivateKey is null) - throw new SignatureSigningException("Unable to sign as no private key has been provided."); + return Result.Fail("Unable to sign as no private key has been provided."); - return this.PrivateKey.SignData(bytesToSign, this.HashAlgorithmName); + return Result.Ok(this.PrivateKey.SignData(bytesToSign, this.HashAlgorithmName)); } - public bool Verify(byte[] bytesToSign, byte[] signature) + + public Result Verify(byte[] bytesToSign, byte[] signature) { if (this.PublicKey is null) - throw new SignatureSigningException("Unable to verify signature as no public key has been provided."); + return Result.Fail("Unable to verify signature as no public key has been provided."); - return this.PublicKey.VerifyData(bytesToSign, signature, this.HashAlgorithmName); + return Result.Ok(this.PublicKey.VerifyData(bytesToSign, signature, this.HashAlgorithmName)); } - public string GetPointCoordinateX() + public Result GetPointCoordinateX() { if (this.PublicKey is null) - throw new SignatureSigningException("Unable to obtain Point Coordinate X from public key as no public key has been provided."); + return Result.Fail("Unable to obtain Point Coordinate X from public key as no public key has been provided."); ECParameters ECParameters = this.PublicKey.ExportExplicitParameters(false); if (ECParameters.Q.X is null) throw new NullReferenceException(nameof(ECParameters.Q.X)); - return Base64UrlEncoder.Encode(ECParameters.Q.X); + return Result.Ok(Base64UrlEncoder.Encode(ECParameters.Q.X)); } - public string GetPointCoordinateY() + public Result GetPointCoordinateY() { if (this.PublicKey is null) - throw new SignatureSigningException("Unable to obtain Point Coordinate Y from public key as no public key has been provided."); + return Result.Fail("Unable to obtain Point Coordinate Y from public key as no public key has been provided."); ECParameters ECParameters = this.PublicKey.ExportExplicitParameters(false); if (ECParameters.Q.Y is null) throw new NullReferenceException(nameof(ECParameters.Q.Y)); - return Base64UrlEncoder.Encode(ECParameters.Q.Y); + return Result.Ok(Base64UrlEncoder.Encode(ECParameters.Q.Y)); } private static ECDsa? GetPrivateKey(X509Certificate2 cert) @@ -108,12 +110,12 @@ public string GetPointCoordinateY() return cert.GetECDsaPublicKey(); } - public static ES256Algorithm FromJWKS(string Kid, JsonWebKeySet JsonWebKeySet, IJsonSerializer JsonSerializer) + public static Result FromJWKS(string Kid, JsonWebKeySet JsonWebKeySet, IJsonSerializer JsonSerializer) { JsonWebKey? Key = JsonWebKeySet.Keys.Find(x => x.Kid.Equals(Kid, StringComparison.CurrentCulture)); if (Key is null) - throw new JsonWebKeySetException($"No key matching the token's header kid value of {Kid} found in the sourced JWKS file."); - + return Result.Fail($"No key matching the token's header kid value of: {Kid} could be found in the sourced Jason Web Key Set (JWKS) file."); + ECDsa PublicKey = ECDsa.Create(new ECParameters { Curve = ECCurve.NamedCurves.nistP256, @@ -124,23 +126,32 @@ public static ES256Algorithm FromJWKS(string Kid, JsonWebKeySet JsonWebKeySet, I } }); - return new ES256Algorithm(PublicKey: PublicKey, PrivateKey: null, JsonSerializer: JsonSerializer); + return Result.Ok(new ES256Algorithm(PublicKey: PublicKey, PrivateKey: null, JsonSerializer: JsonSerializer)); } - public string GetKid() + public Result GetKid() { if (this.Certificate is null) - throw new NullReferenceException("Unable to get certificate thumb-print as no certificate provided."); + return Result.Fail("Unable to get certificate thumb-print as no certificate provided."); + + Result PointCoordinateXResult = this.GetPointCoordinateX(); + Result PointCoordinateYResult = this.GetPointCoordinateY(); + var CombinedResult = Result.Combine(PointCoordinateXResult, PointCoordinateYResult); + if (CombinedResult.Failure) + return Result.Fail(CombinedResult.ErrorMessage); var Intermediate = new JWKThumbprintComputationIntermediate( this.CurveName, this.KeyTypeName, - this.GetPointCoordinateX(), - this.GetPointCoordinateY()); + PointCoordinateXResult.Value, + PointCoordinateYResult.Value); - string Json = JsonSerializer.ToJson(Intermediate, Minified: true); - byte[] Bytes = Hashers.SHA256Hasher.GetSHA256Hash(Json); - return Encoders.Base64UrlEncoder.Encode(Bytes); + Result ToJsonResult = JsonSerializer.ToJson(Intermediate, Minified: true); + if (ToJsonResult.Failure) + return ToJsonResult; + + byte[] Bytes = Hashers.SHA256Hasher.GetSHA256Hash(ToJsonResult.Value); + return Result.Ok(Base64UrlEncoder.Encode(Bytes)); } } } diff --git a/SmartHealthCard.Token/Algorithms/IAlgorithm.cs b/SmartHealthCard.Token/Algorithms/IAlgorithm.cs index ef26431..6143746 100644 --- a/SmartHealthCard.Token/Algorithms/IAlgorithm.cs +++ b/SmartHealthCard.Token/Algorithms/IAlgorithm.cs @@ -1,12 +1,14 @@ -namespace SmartHealthCard.Token.Algorithms +using SmartHealthCard.Token.Support; + +namespace SmartHealthCard.Token.Algorithms { public interface IAlgorithm { string Name { get; } string KeyTypeName { get; } string CurveName { get; } - string GetKid(); - byte[] Sign(byte[] bytesToSign); - bool Verify(byte[] bytesToSign, byte[] signature); + Result GetKid(); + Result Sign(byte[] bytesToSign); + Result Verify(byte[] bytesToSign, byte[] signature); } } \ No newline at end of file diff --git a/SmartHealthCard.Token/Exceptions/InvalidTokenException.cs b/SmartHealthCard.Token/Exceptions/InvalidTokenException.cs deleted file mode 100644 index 59ae094..0000000 --- a/SmartHealthCard.Token/Exceptions/InvalidTokenException.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SmartHealthCard.Token.Exceptions -{ - public class InvalidTokenException : SmartHealthCardException - { - public InvalidTokenException(string Message) - : base(Message){ } - } -} diff --git a/SmartHealthCard.Token/Exceptions/InvalidTokenPartsException.cs b/SmartHealthCard.Token/Exceptions/InvalidTokenPartsException.cs new file mode 100644 index 0000000..70fa1d4 --- /dev/null +++ b/SmartHealthCard.Token/Exceptions/InvalidTokenPartsException.cs @@ -0,0 +1,8 @@ +namespace SmartHealthCard.Token.Exceptions +{ + public class InvalidTokenPartsException : SmartHealthCardException + { + public InvalidTokenPartsException(string Message) + : base(Message){ } + } +} diff --git a/SmartHealthCard.Token/Exceptions/JsonWebKeySetException.cs b/SmartHealthCard.Token/Exceptions/JsonWebKeySetException.cs deleted file mode 100644 index 2d16f08..0000000 --- a/SmartHealthCard.Token/Exceptions/JsonWebKeySetException.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SmartHealthCard.Token.Exceptions -{ - public class JsonWebKeySetException : SmartHealthCardException - { - public JsonWebKeySetException(string Message) - : base(Message) { } - } -} \ No newline at end of file diff --git a/SmartHealthCard.Token/Exceptions/SignatureSigningException.cs b/SmartHealthCard.Token/Exceptions/SignatureSigningException.cs deleted file mode 100644 index 28c96bb..0000000 --- a/SmartHealthCard.Token/Exceptions/SignatureSigningException.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace SmartHealthCard.Token.Exceptions -{ - public class SignatureSigningException : SmartHealthCardException - { - public SignatureSigningException(string Message) - : base(Message) { } - - } -} \ No newline at end of file diff --git a/SmartHealthCard.Token/Exceptions/SignatureVerificationException.cs b/SmartHealthCard.Token/Exceptions/SignatureVerificationException.cs deleted file mode 100644 index 4cbfdb9..0000000 --- a/SmartHealthCard.Token/Exceptions/SignatureVerificationException.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SmartHealthCard.Token.Exceptions -{ - public class SignatureVerificationException : SmartHealthCardException - { - public SignatureVerificationException(string message) - : base(message) { } - } -} \ No newline at end of file diff --git a/SmartHealthCard.Token/Exceptions/SmartHealthCardHeaderException.cs b/SmartHealthCard.Token/Exceptions/SmartHealthCardHeaderException.cs deleted file mode 100644 index 7da5781..0000000 --- a/SmartHealthCard.Token/Exceptions/SmartHealthCardHeaderException.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace SmartHealthCard.Token.Exceptions -{ - public class SmartHealthCardHeaderException : SmartHealthCardException - { - public SmartHealthCardHeaderException(string Message) - : base(Message) { } - } -} diff --git a/SmartHealthCard.Token/JwsToken/IJwsDecoder.cs b/SmartHealthCard.Token/JwsToken/IJwsDecoder.cs index 75c4d02..889d784 100644 --- a/SmartHealthCard.Token/JwsToken/IJwsDecoder.cs +++ b/SmartHealthCard.Token/JwsToken/IJwsDecoder.cs @@ -1,10 +1,11 @@ using System.Threading.Tasks; +using SmartHealthCard.Token.Support; namespace SmartHealthCard.Token.JwsToken { public interface IJwsDecoder { - Task DecodeHeaderAsync(string Token); - Task DecodePayloadAsync(string Token, bool Verity = false); + Task> DecodeHeaderAsync(string Token); + Task> DecodePayloadAsync(string Token, bool Verity = false); } } \ No newline at end of file diff --git a/SmartHealthCard.Token/JwsToken/IJwsEncoder.cs b/SmartHealthCard.Token/JwsToken/IJwsEncoder.cs index 4020636..48dad61 100644 --- a/SmartHealthCard.Token/JwsToken/IJwsEncoder.cs +++ b/SmartHealthCard.Token/JwsToken/IJwsEncoder.cs @@ -1,9 +1,10 @@ -using System.Threading.Tasks; +using SmartHealthCard.Token.Support; +using System.Threading.Tasks; namespace SmartHealthCard.Token.JwsToken { public interface IJwsEncoder { - Task EncodeAsync(HeaderType Header, PayloadType Payload); + Task> EncodeAsync(HeaderType Header, PayloadType Payload); } } \ No newline at end of file diff --git a/SmartHealthCard.Token/JwsToken/IJwsHeaderValidator.cs b/SmartHealthCard.Token/JwsToken/IJwsHeaderValidator.cs index 2db319d..88dd265 100644 --- a/SmartHealthCard.Token/JwsToken/IJwsHeaderValidator.cs +++ b/SmartHealthCard.Token/JwsToken/IJwsHeaderValidator.cs @@ -1,8 +1,10 @@ -namespace SmartHealthCard.Token.JwsToken +using SmartHealthCard.Token.Support; + +namespace SmartHealthCard.Token.JwsToken { public interface IJwsHeaderValidator { - void Validate(T Obj); + Result Validate(T Obj); } } \ No newline at end of file diff --git a/SmartHealthCard.Token/JwsToken/IJwsPayloadValidator.cs b/SmartHealthCard.Token/JwsToken/IJwsPayloadValidator.cs index 9fe5a8b..ad436bc 100644 --- a/SmartHealthCard.Token/JwsToken/IJwsPayloadValidator.cs +++ b/SmartHealthCard.Token/JwsToken/IJwsPayloadValidator.cs @@ -1,7 +1,9 @@ -namespace SmartHealthCard.Token.JwsToken +using SmartHealthCard.Token.Support; + +namespace SmartHealthCard.Token.JwsToken { public interface IJwsPayloadValidator { - void Validate(T Obj); + Result Validate(T Obj); } } \ No newline at end of file diff --git a/SmartHealthCard.Token/JwsToken/IJwsSignatureValidator.cs b/SmartHealthCard.Token/JwsToken/IJwsSignatureValidator.cs index b2d0a30..84ba996 100644 --- a/SmartHealthCard.Token/JwsToken/IJwsSignatureValidator.cs +++ b/SmartHealthCard.Token/JwsToken/IJwsSignatureValidator.cs @@ -1,10 +1,11 @@ using SmartHealthCard.Token.Algorithms; +using SmartHealthCard.Token.Support; namespace SmartHealthCard.Token.JwsToken { public interface IJwsSignatureValidator { - void Validate(IAlgorithm Algorithm, string Token); + Result Validate(IAlgorithm Algorithm, string Token); } } \ No newline at end of file diff --git a/SmartHealthCard.Token/JwsToken/JwsDecoder.cs b/SmartHealthCard.Token/JwsToken/JwsDecoder.cs index 02aa580..23ff35b 100644 --- a/SmartHealthCard.Token/JwsToken/JwsDecoder.cs +++ b/SmartHealthCard.Token/JwsToken/JwsDecoder.cs @@ -47,88 +47,27 @@ public JwsDecoder( this.JwsPayloadValidator = IJwsPayloadValidator; } - public async Task DecodePayloadAsync(string Token, bool Verity = false) + public async Task> DecodePayloadAsync(string Token, bool Verity = false) { if (string.IsNullOrEmpty(Token)) - throw new InvalidTokenException("The Token provided was found to be null or empty."); + return await Task.FromResult(Result.Fail("The provided Token was found to be null or empty.")); - string Payload = new JwsParts(Token).Payload; - byte[] DecodedPayload = Base64UrlEncoder.Decode(Payload); - PayloadType PayloadDeserialized = await PayloadSerializer.DeserializeAsync(DecodedPayload); - - if (!Verity) - { - return PayloadDeserialized; - } - else - { - //We must use the PayloadSerializer.Deserialize and not the JsonSerializer.FromJson() because for - //SMART HEalth Cards the payload is DEFLATE compressed bytes and not a JSON string - JwsBody JwsBody = await PayloadSerializer.DeserializeAsync(DecodedPayload); - - if (JwsBody.Iss is null) - throw new SignatureVerificationException("No Issuer (iss) claim found in JWS Token body."); - - if (!Uri.TryCreate($"{JwsBody.Iss}/.well-known/jwks.json", UriKind.Absolute, out Uri? WellKnownJwksUri)) - throw new SignatureVerificationException($"Unable to parse the Issuer (iss) claim to a absolute Uri, value was {$"{JwsBody.Iss}/.well-known/jwks.json"}"); - - if (JwksProvider is null) - throw new SignatureVerificationException($"When Verify is true {nameof(this.JwksProvider)} must be not null."); - - Result JsonWebKeySetResult = await JwksProvider.GetJwksAsync(WellKnownJwksUri); - JsonWebKeySet JsonWebKeySet; - if (JsonWebKeySetResult.Failure) - { - throw new SignatureVerificationException($"Unable to obtain the JsonWebKeySet (JWKS) from : {WellKnownJwksUri.OriginalString}. ErrorMessage: {JsonWebKeySetResult.ErrorMessage}"); - } - else - { - JsonWebKeySet = JsonWebKeySetResult.Value; - } - - string Header = new JwsParts(Token).Header; - byte[] DecodedHeader = Base64UrlEncoder.Decode(Header); - string HeaderJson = Utf8EncodingSupport.GetString(DecodedHeader); - - if (JsonSerializer is null) - throw new SignatureVerificationException($"When Verify is true {nameof(this.JsonSerializer)} must be not null."); - - JwsHeader JwsHeader = JsonSerializer.FromJson(HeaderJson); - - if (JwsHeader.Kid is null) - throw new SignatureVerificationException("No key JWK Thumb-print (kid) claim found in JWS Token header."); - - Algorithms.IAlgorithm Algorithm = Algorithms.ES256Algorithm.FromJWKS(JwsHeader.Kid, JsonWebKeySet, JsonSerializer); - - if (this.JwsSignatureValidator is null) - throw new NullReferenceException($"When Verify is true {nameof(this.JwsSignatureValidator)} must be not null."); - - JwsSignatureValidator.Validate(Algorithm, Token); - - if (this.JwsHeaderValidator is null) - throw new NullReferenceException($"When Verify is true {nameof(this.JwsHeaderValidator)} must be not null."); - - this.JwsHeaderValidator.Validate(this.DecodeHeaderAsync(Token)); - - if (this.JwsPayloadValidator is null) - throw new NullReferenceException($"When Verify is true {nameof(this.JwsPayloadValidator)} must be not null."); - - this.JwsPayloadValidator.Validate(PayloadDeserialized); - - return PayloadDeserialized; - } + throw await Task.FromResult(new NotImplementedException()); } - public async Task DecodeHeaderAsync(string Token) + public async Task> DecodeHeaderAsync(string Token) { if (string.IsNullOrEmpty(Token)) { throw new ArgumentException(nameof(Token)); } - string Header = new JwsParts(Token).Header; - byte[] DecodedHeader = Base64UrlEncoder.Decode(Header); - return await HeaderSerializer.DeserializeAsync(DecodedHeader); + Result JwsPartsParseResult = JwsParts.ParseToken(Token); + if (JwsPartsParseResult.Failure) + return await Task.FromResult(Result.Fail(JwsPartsParseResult.ErrorMessage)); + + byte[] DecodedHeader = Base64UrlEncoder.Decode(JwsPartsParseResult.Value.Header); + return await HeaderSerializer.DeserializeAsync(DecodedHeader); } diff --git a/SmartHealthCard.Token/JwsToken/JwsEncoder.cs b/SmartHealthCard.Token/JwsToken/JwsEncoder.cs index e2fc034..61c30e0 100644 --- a/SmartHealthCard.Token/JwsToken/JwsEncoder.cs +++ b/SmartHealthCard.Token/JwsToken/JwsEncoder.cs @@ -1,6 +1,7 @@ using SmartHealthCard.Token.Algorithms; using SmartHealthCard.Token.Encoders; using SmartHealthCard.Token.Serializers.Jws; +using SmartHealthCard.Token.Support; using System.Threading.Tasks; namespace SmartHealthCard.Token.JwsToken @@ -17,16 +18,24 @@ public JwsEncoder(IJwsHeaderSerializer HeaderSerializer, IJwsPayloadSerializer P this.Algorithm = Algorithm; } - public async Task EncodeAsync(HeaderType Header, PayloadType Payload) + public async Task> EncodeAsync(HeaderType Header, PayloadType Payload) { - var HeaderSegment = Base64UrlEncoder.Encode(await HeaderSerializer.SerializeAsync(Header)); - var PayloadSegment = Base64UrlEncoder.Encode(await PayloadSerializer.SerializeAsync(Payload)); + Result HeaderSerializeResult = await HeaderSerializer.SerializeAsync(Header); + Result PayloadSerializeResult = await PayloadSerializer.SerializeAsync(Payload); + var CombineResult = Result.Combine(HeaderSerializeResult, PayloadSerializeResult); + if (CombineResult.Failure) + return await Task.FromResult(Result.Fail(CombineResult.ErrorMessage)); + + var HeaderSegment = Base64UrlEncoder.Encode(HeaderSerializeResult.Value); + var PayloadSegment = Base64UrlEncoder.Encode(PayloadSerializeResult.Value); var BytesToSign = Utf8EncodingSupport.GetBytes($"{HeaderSegment}.{PayloadSegment}"); - var Signature = Algorithm.Sign(BytesToSign); - var SignatureSegment = Base64UrlEncoder.Encode(Signature); + Result SignResult = Algorithm.Sign(BytesToSign); + if (SignResult.Failure) + return await Task.FromResult(Result.Fail(SignResult.ErrorMessage)); - return $"{HeaderSegment}.{PayloadSegment}.{ SignatureSegment}"; + var SignatureSegment = Base64UrlEncoder.Encode(SignResult.Value); + return Result.Ok($"{HeaderSegment}.{PayloadSegment}.{SignatureSegment}"); } } } diff --git a/SmartHealthCard.Token/JwsToken/JwsSignatureValidator.cs b/SmartHealthCard.Token/JwsToken/JwsSignatureValidator.cs index 1cb5357..68df135 100644 --- a/SmartHealthCard.Token/JwsToken/JwsSignatureValidator.cs +++ b/SmartHealthCard.Token/JwsToken/JwsSignatureValidator.cs @@ -2,24 +2,31 @@ using SmartHealthCard.Token.Encoders; using SmartHealthCard.Token.Exceptions; using SmartHealthCard.Token.Model.Jws; +using SmartHealthCard.Token.Support; using System; namespace SmartHealthCard.Token.JwsToken { public sealed class JwsSignatureValidator : IJwsSignatureValidator { - public void Validate(IAlgorithm Algorithm, string Token) + public Result Validate(IAlgorithm Algorithm, string Token) { - if (string.IsNullOrEmpty(Token)) - throw new ArgumentException($"The provided {nameof(Token)} was found to be empty."); - - JwsParts JwtParts = new JwsParts(Token); - byte[] BytesToSign = Utf8EncodingSupport.GetBytes(JwtParts.Header, (byte)'.', JwtParts.Payload); - byte[] Signature = Base64UrlEncoder.Decode(JwtParts.Signature); - if (!Algorithm.Verify(BytesToSign, Signature)) + if (string.IsNullOrEmpty(Token)) + return Result.Fail("The provided Token was found to be null or empty."); + + Result JwtPartsParseResult = JwsParts.ParseToken(Token); + if (JwtPartsParseResult.Failure) + return Result.Fail(JwtPartsParseResult.ErrorMessage); + + byte[] BytesToSign = Utf8EncodingSupport.GetBytes(JwtPartsParseResult.Value.Header, (byte)'.', JwtPartsParseResult.Value.Payload); + byte[] Signature = Base64UrlEncoder.Decode(JwtPartsParseResult.Value.Signature); + Result VerifyResult = Algorithm.Verify(BytesToSign, Signature); + + if (VerifyResult.Failure) { - throw new SignatureVerificationException("The JWS signature is invalid."); - } + return Result.Fail($"The JWS signing signature is invalid. {VerifyResult.ErrorMessage}"); + } + return Result.Ok(); } } } diff --git a/SmartHealthCard.Token/JwsToken/SmartHealthCardJwsDecoder.cs b/SmartHealthCard.Token/JwsToken/SmartHealthCardJwsDecoder.cs index 94cfed9..d97a8e7 100644 --- a/SmartHealthCard.Token/JwsToken/SmartHealthCardJwsDecoder.cs +++ b/SmartHealthCard.Token/JwsToken/SmartHealthCardJwsDecoder.cs @@ -47,33 +47,40 @@ public SmartHealthCardJwsDecoder( this.JwsPayloadValidator = IJwsPayloadValidator ?? new SmartHealthCardPayloadValidator(); } - public async Task DecodePayloadAsync(string Token, bool Verity = false) + public async Task> DecodePayloadAsync(string Token, bool Verity = false) { if (string.IsNullOrEmpty(Token)) - throw new InvalidTokenException("The Token provided was found to be null or empty."); + return await Task.FromResult(Result.Fail("The provided Token was found to be null or empty.")); - string Payload = new JwsParts(Token).Payload; - byte[] DecodedPayload = Base64UrlEncoder.Decode(Payload); - PayloadType PayloadDeserialized = await PayloadSerializer.DeserializeAsync(DecodedPayload); + Result JwsPartsParseResult = JwsParts.ParseToken(Token); + if (JwsPartsParseResult.Failure) + return await Task.FromResult(Result.Fail(JwsPartsParseResult.ErrorMessage)); + + byte[] DecodedPayload = Base64UrlEncoder.Decode(JwsPartsParseResult.Value.Payload); + Result PayloadDeserializedResult = await PayloadSerializer.DeserializeAsync(DecodedPayload); + if (PayloadDeserializedResult.Failure) + return PayloadDeserializedResult; if (!Verity) - { - return await Task.FromResult(PayloadDeserialized); + { + return await Task.FromResult(Result.Ok(PayloadDeserializedResult.Value)); } else { //We must use the PayloadSerializer.Deserialize and not the JsonSerializer.FromJson() because for //SMART Health Cards the payload is DEFLATE compressed bytes and not a JSON string - JwsBody JwsBody = await PayloadSerializer.DeserializeAsync(DecodedPayload); - - if (JwsBody.Iss is null) - throw new SignatureVerificationException("No Issuer (iss) claim found in JWS Token body."); + Result JwsBodyDeserializeResult = await PayloadSerializer.DeserializeAsync(DecodedPayload); + if (JwsBodyDeserializeResult.Failure) + return Result.Fail(JwsBodyDeserializeResult.ErrorMessage); + + if (JwsBodyDeserializeResult.Value.Iss is null) + return await Task.FromResult(Result.Fail("No Issuer (iss) claim found in JWS Token body.")); - if (!Uri.TryCreate($"{JwsBody.Iss}/.well-known/jwks.json", UriKind.Absolute, out Uri? WellKnownJwksUri)) - throw new SignatureVerificationException($"Unable to parse the Issuer (iss) claim to a absolute Uri, value was {$"{JwsBody.Iss}/.well-known/jwks.json"}"); + if (!Uri.TryCreate($"{JwsBodyDeserializeResult.Value.Iss}/.well-known/jwks.json", UriKind.Absolute, out Uri? WellKnownJwksUri)) + return await Task.FromResult(Result.Fail($"Unable to parse the Issuer (iss) claim to a absolute Uri, value was {$"{JwsBodyDeserializeResult.Value.Iss}/.well-known/jwks.json"}")); if (JwksProvider is null) - throw new SignatureVerificationException($"When Verify is true {nameof(this.JwksProvider)} must be not null."); + return await Task.FromResult(Result.Fail($"When Verify is true {nameof(this.JwksProvider)} must be not null.")); Result JsonWebKeySetResult = await JwksProvider.GetJwksAsync(WellKnownJwksUri); JsonWebKeySet JsonWebKeySet; @@ -83,50 +90,65 @@ public async Task DecodePayloadAsync(strin } else { - throw new SignatureVerificationException($"Unable to obtain the JsonWebKeySet (JWKS) from : {WellKnownJwksUri.OriginalString}. ErrorMessage: {JsonWebKeySetResult.ErrorMessage}"); + return await Task.FromResult(Result.Fail($"Unable to obtain the JsonWebKeySet (JWKS) from : {WellKnownJwksUri.OriginalString}. ErrorMessage: {JsonWebKeySetResult.ErrorMessage}")); } - - string Header = new JwsParts(Token).Header; - byte[] DecodedHeader = Base64UrlEncoder.Decode(Header); + + byte[] DecodedHeader = Base64UrlEncoder.Decode(JwsPartsParseResult.Value.Header); string HeaderJson = Utf8EncodingSupport.GetString(DecodedHeader); if (JsonSerializer is null) - throw new SignatureVerificationException($"When Verify is true {nameof(this.JsonSerializer)} must be not null."); - - JwsHeader JwsHeader = JsonSerializer.FromJson(HeaderJson); + return await Task.FromResult(Result.Fail($"When Verify is true {nameof(this.JsonSerializer)} must be not null.")); - if (JwsHeader.Kid is null) - throw new SignatureVerificationException("No key JWK Thumb-print (kid) claim found in JWS Token header."); + Result JwsHeaderFromJsonResult = JsonSerializer.FromJson(HeaderJson); + if (JwsHeaderFromJsonResult.Failure) + return Result.Fail(JwsBodyDeserializeResult.ErrorMessage); - Algorithms.IAlgorithm Algorithm = Algorithms.ES256Algorithm.FromJWKS(JwsHeader.Kid, JsonWebKeySet, JsonSerializer); + if (JwsHeaderFromJsonResult.Value.Kid is null) + return await Task.FromResult(Result.Fail("No key JWK Thumb-print (kid) claim found in JWS Token header.")); + + Result AlgorithmResult = Algorithms.ES256Algorithm.FromJWKS(JwsHeaderFromJsonResult.Value.Kid, JsonWebKeySet, JsonSerializer); + if (AlgorithmResult.Failure) + return await Task.FromResult(Result.Fail(AlgorithmResult.ErrorMessage)); if (this.JwsSignatureValidator is null) - throw new NullReferenceException($"When Verify is true {nameof(this.JwsSignatureValidator)} must be not null."); + return await Task.FromResult(Result.Fail($"When Verify is true {nameof(this.JwsSignatureValidator)} must be not null.")); - JwsSignatureValidator.Validate(Algorithm, Token); + Result JwsSignatureValidatorResult = JwsSignatureValidator.Validate(AlgorithmResult.Value, Token); + if (JwsSignatureValidatorResult.Failure) + return await Task.FromResult(Result.Fail(JwsSignatureValidatorResult.ErrorMessage)); if (this.JwsHeaderValidator is null) - throw new NullReferenceException($"When Verify is true {nameof(this.JwsHeaderValidator)} must be not null."); + return await Task.FromResult(Result.Fail($"When Verify is true {nameof(this.JwsHeaderValidator)} must be not null.")); - this.JwsHeaderValidator.Validate(this.DecodeHeaderAsync(Token)); + Result DecodeHeaderResult = await this.DecodeHeaderAsync(Token); + if (DecodeHeaderResult.Failure) + return await Task.FromResult(Result.Fail(DecodeHeaderResult.ErrorMessage)); - if (this.JwsPayloadValidator is null) - throw new NullReferenceException($"When Verify is true {nameof(this.JwsPayloadValidator)} must be not null."); + Result JwsHeaderValidateResult = this.JwsHeaderValidator.Validate(DecodeHeaderResult.Value); + if (JwsHeaderValidateResult.Failure) + return await Task.FromResult(Result.Fail(JwsHeaderValidateResult.ErrorMessage)); - this.JwsPayloadValidator.Validate(PayloadDeserialized); + if (this.JwsPayloadValidator is null) + return await Task.FromResult(Result.Fail($"When Verify is true {nameof(this.JwsPayloadValidator)} must be not null.")); + + Result JwsPayloadValidatorResult = this.JwsPayloadValidator.Validate(PayloadDeserializedResult.Value); + if (JwsPayloadValidatorResult.Failure) + return await Task.FromResult(Result.Fail(JwsPayloadValidatorResult.ErrorMessage)); - return PayloadDeserialized; + return Result.Ok(PayloadDeserializedResult.Value); } } - public async Task DecodeHeaderAsync(string Token) + public async Task> DecodeHeaderAsync(string Token) { if (string.IsNullOrEmpty(Token)) - { - throw new ArgumentException(nameof(Token)); - } - string Header = new JwsParts(Token).Header; - byte[] DecodedHeader = Base64UrlEncoder.Decode(Header); + return await Task.FromResult(Result.Fail("The provided Token was found to be null or empty.")); + + Result JwsPartsParseTokenResult = JwsParts.ParseToken(Token); + if (JwsPartsParseTokenResult.Failure) + return await Task.FromResult(Result.Fail(JwsPartsParseTokenResult.ErrorMessage)); + + byte[] DecodedHeader = Base64UrlEncoder.Decode(JwsPartsParseTokenResult.Value.Header); return await HeaderSerializer.DeserializeAsync(DecodedHeader); } diff --git a/SmartHealthCard.Token/JwsToken/SmartHealthCardJwsEncoder.cs b/SmartHealthCard.Token/JwsToken/SmartHealthCardJwsEncoder.cs index 95ba710..5552586 100644 --- a/SmartHealthCard.Token/JwsToken/SmartHealthCardJwsEncoder.cs +++ b/SmartHealthCard.Token/JwsToken/SmartHealthCardJwsEncoder.cs @@ -1,6 +1,7 @@ using SmartHealthCard.Token.Algorithms; using SmartHealthCard.Token.Encoders; using SmartHealthCard.Token.Serializers.Jws; +using SmartHealthCard.Token.Support; using System.Threading.Tasks; namespace SmartHealthCard.Token.JwsToken @@ -17,16 +18,25 @@ public SmartHealthCardJwsEncoder(IJwsHeaderSerializer HeaderSerializer, IJwsPayl this.Algorithm = Algorithm; } - public async Task EncodeAsync(HeaderType Header, PayloadType Payload) + public async Task> EncodeAsync(HeaderType Header, PayloadType Payload) { - var HeaderSegment = Base64UrlEncoder.Encode(await HeaderSerializer .SerializeAsync(Header)); - var PayloadSegment = Base64UrlEncoder.Encode(await PayloadSerializer.SerializeAsync(Payload)); + Result HeaderSerializeResult = await HeaderSerializer.SerializeAsync(Header); + Result PayloadSerializeResult = await PayloadSerializer.SerializeAsync(Payload); + var CombineResult = Result.Combine(HeaderSerializeResult, PayloadSerializeResult); + if (CombineResult.Failure) + return Result.Fail(CombineResult.ErrorMessage); + + var HeaderSegment = Base64UrlEncoder.Encode(HeaderSerializeResult.Value); + var PayloadSegment = Base64UrlEncoder.Encode(PayloadSerializeResult.Value); var BytesToSign = Utf8EncodingSupport.GetBytes($"{HeaderSegment}.{PayloadSegment}"); - var Signature = Algorithm.Sign(BytesToSign); - var SignatureSegment = Base64UrlEncoder.Encode(Signature); + Result SignatureResult = Algorithm.Sign(BytesToSign); + if (SignatureResult.Failure) + return Result.Fail(SignatureResult.ErrorMessage); + + var SignatureSegment = Base64UrlEncoder.Encode(SignatureResult.Value); - return $"{HeaderSegment}.{PayloadSegment}.{ SignatureSegment}"; + return Result.Ok($"{HeaderSegment}.{PayloadSegment}.{ SignatureSegment}"); } } } diff --git a/SmartHealthCard.Token/Model/Jws/JwsParts.cs b/SmartHealthCard.Token/Model/Jws/JwsParts.cs index 05e5880..81c1ac2 100644 --- a/SmartHealthCard.Token/Model/Jws/JwsParts.cs +++ b/SmartHealthCard.Token/Model/Jws/JwsParts.cs @@ -1,43 +1,40 @@ using System; using SmartHealthCard.Token.Enums; using SmartHealthCard.Token.Exceptions; - +using SmartHealthCard.Token.Support; namespace SmartHealthCard.Token.Model.Jws { public class JwsParts { - public JwsParts(string token) - { - if (string.IsNullOrWhiteSpace(token)) - { - throw new ArgumentException(nameof(token)); - } - - var parts = token.Split('.'); - if (parts.Length != 3) - throw new InvalidTokenException($"{nameof(Token)}: A JWS Token must have three parts separated by dots (e.g Header.Payload.Signature)."); - - this.Parts = parts; - } - public JwsParts(string[] Parts) { if (Parts is null) throw new ArgumentNullException(nameof(Parts)); if (Parts.Length != 3) - throw new InvalidTokenException($"{nameof(Parts)}: A JWS Token must have three parts separated by dots (e.g Header.Payload.Signature)."); - + throw new InvalidTokenPartsException($"{nameof(Parts)}: A JWS Token must have three parts separated by dots (e.g Header.Payload.Signature)."); this.Parts = Parts; } - + + public static Result ParseToken(string Token) + { + if (string.IsNullOrEmpty(Token)) + return Result.Fail("The provided Token was found to be null or empty."); + + var parts = Token.Split('.'); + if (parts.Length != 3) + return Result.Fail($"{nameof(Token)}: A JWS Token must have three parts separated by dots (e.g Header.Payload.Signature)."); + + return Result.Ok(new JwsParts(parts)); + } + public string Header => this.Parts[(int)JwtPartsIndex.Header]; public string Payload => this.Parts[(int)JwtPartsIndex.Payload]; public string Signature => this.Parts[(int)JwtPartsIndex.Signature]; - public string[] Parts { get; } + public string[] Parts { get; private set; } } diff --git a/SmartHealthCard.Token/Model/Shc/SmartHealthCardModel.cs b/SmartHealthCard.Token/Model/Shc/SmartHealthCardModel.cs index d8d2888..99047b0 100644 --- a/SmartHealthCard.Token/Model/Shc/SmartHealthCardModel.cs +++ b/SmartHealthCard.Token/Model/Shc/SmartHealthCardModel.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json; using SmartHealthCard.Token.DateTimeSupport; using SmartHealthCard.Token.Exceptions; +using SmartHealthCard.Token.Support; using System; using System.Collections.Generic; using System.Linq; @@ -45,12 +46,12 @@ public DateTimeOffset GetIssuanceDate() return new DateTimeOffset(UnixEpoch.UnixTimeStampToDateTime(NbfDouble)); } - internal void Validate() + internal Result Validate() { - this.ValidateIssuanceDate(); + return this.ValidateIssuanceDate(); } - private void ValidateIssuanceDate() + private Result ValidateIssuanceDate() { var EpochNow = UnixEpoch.GetSecondsSince(DateTimeOffset.UtcNow); int ExtraTimeMargin = 0; //Can add extra seconds onto expiry if required @@ -61,14 +62,15 @@ private void ValidateIssuanceDate() } catch { - throw new SmartHealthCardPayloadException($"IssuanceDate (nbf) must be a number, found the value of {this.IssuanceDate}."); + return Result.Fail($"IssuanceDate (nbf) must be a number, found the value of {this.IssuanceDate}."); } if (IssuanceDateEpoch > (EpochNow - ExtraTimeMargin)) { DateTime Date = UnixEpoch.UnixTimeStampToDateTime(EpochNow + ExtraTimeMargin); - throw new SmartHealthCardPayloadException($"The token's Issuance Date of {Date} is earlier than the current date."); - } + return Result.Fail($"The token's Issuance Date of {Date} is earlier than the current date."); + } + return Result.Ok(); } } } diff --git a/SmartHealthCard.Token/Model/Shc/SmartHealthCareJWSHeaderModel.cs b/SmartHealthCard.Token/Model/Shc/SmartHealthCareJWSHeaderModel.cs index f2863f8..c19f2c2 100644 --- a/SmartHealthCard.Token/Model/Shc/SmartHealthCareJWSHeaderModel.cs +++ b/SmartHealthCard.Token/Model/Shc/SmartHealthCareJWSHeaderModel.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json; using SmartHealthCard.Token.Exceptions; +using SmartHealthCard.Token.Support; namespace SmartHealthCard.Token.Model.Shc { @@ -21,45 +22,50 @@ public SmartHealthCareJWSHeaderModel(string Alg, string Zip, string Kid) [JsonProperty("kid", Required = Required.Always)] public string Kid { get; set; } - internal void Validate() - { - this.ValidateAlg(); - this.ValidateZip(); - this.ValidateKid(); + internal Result Validate() + { + Result ValidateAlgResult = this.ValidateAlg(); + Result ValidateZipResult = this.ValidateZip(); + Result ValidateKidResult = this.ValidateKid(); + + return Result.Combine(ValidateAlgResult, ValidateZipResult, ValidateKidResult); } - private void ValidateAlg() + private Result ValidateAlg() { string ExpectedAlg = "ES256"; if (string.IsNullOrWhiteSpace(this.Alg)) { - throw new SmartHealthCardHeaderException("The Algorithm (alg) property was empty."); + return Result.Fail("The Algorithm (alg) property was empty."); } if (!this.Alg.ToUpper().Equals(ExpectedAlg)) { - throw new SmartHealthCardHeaderException($"For Smart Health Cards the JWS header Algorithm (alg) property must be '{ExpectedAlg}', yet found {this.Alg}."); + return Result.Fail($"For Smart Health Cards the JWS header Algorithm (alg) property must be '{ExpectedAlg}', yet found {this.Alg}."); } + return Result.Ok(); } - private void ValidateZip() + private Result ValidateZip() { string ExpectedZip = "DEF"; if (string.IsNullOrWhiteSpace(this.Zip)) { - throw new SmartHealthCardHeaderException("The Algorithm (alg) property was empty."); + return Result.Fail("The Compression (zip) property was empty."); } if (!this.Zip.ToUpper().Equals(ExpectedZip)) { - throw new SmartHealthCardHeaderException($"For Smart Health Cards the JWS header Compression (zip) property must be '{ExpectedZip}', yet found {this.Zip}."); - } + return Result.Fail($"For Smart Health Cards the JWS header Compression (zip) property must be '{ExpectedZip}', yet found {this.Zip}."); + } + return Result.Ok(); } - private void ValidateKid() + private Result ValidateKid() { if (string.IsNullOrWhiteSpace(this.Kid)) { - throw new SmartHealthCardHeaderException("The JWK Thumb-print of the key (kid) property was empty."); - } + return Result.Fail("The JWK Thumb-print of the key (kid) property was empty."); + } + return Result.Ok(); } diff --git a/SmartHealthCard.Token/Providers/JwksProviderHttpClient.cs b/SmartHealthCard.Token/Providers/JwksProviderHttpClient.cs index ee8866f..5be56b7 100644 --- a/SmartHealthCard.Token/Providers/JwksProviderHttpClient.cs +++ b/SmartHealthCard.Token/Providers/JwksProviderHttpClient.cs @@ -23,7 +23,6 @@ public JwksProviderHttpClient(IJsonSerializer JsonSerializer, HttpClient HttpCl this.JsonSerializer = JsonSerializer; } - public async Task> Get(Uri WellKnownUrl, CancellationToken? CancellationToken = null) { var request = new HttpRequestMessage(HttpMethod.Get, WellKnownUrl); @@ -37,21 +36,11 @@ public async Task> Get(Uri WellKnownUrl, CancellationToken { return Result.Fail("Response content was null"); } - - System.IO.Stream ResponseStream = await response.Content.ReadAsStreamAsync(); - //var responseJson = await response.Content.ReadAsStringAsync(); + System.IO.Stream ResponseStream = await response.Content.ReadAsStreamAsync(); Result JsonWebKeySetResult = JsonSerializer.FromJsonStream(ResponseStream); - - if (JsonWebKeySetResult.Success) - { - return JsonWebKeySetResult; - } - else - { - - - return Result.Fail($"Failed to deserialize response: {JsonWebKeySetResult.ErrorMessage}"); - } + if (JsonWebKeySetResult.Failure) + return Result.Fail($"Failed to deserialize the JsonWebKeySet (JWKS) which was returned from the endpoint {WellKnownUrl.OriginalString}. {JsonWebKeySetResult.ErrorMessage}"); + return JsonWebKeySetResult; } else { @@ -66,8 +55,7 @@ public async Task> Get(Uri WellKnownUrl, CancellationToken return Result.Fail($"Response status: {response.StatusCode}, Content: [None]"); } } - } - + } } catch(HttpRequestException HttpRequestException) { diff --git a/SmartHealthCard.Token/Serializers/Json/IJsonSerializer.cs b/SmartHealthCard.Token/Serializers/Json/IJsonSerializer.cs index c05a204..3e76b80 100644 --- a/SmartHealthCard.Token/Serializers/Json/IJsonSerializer.cs +++ b/SmartHealthCard.Token/Serializers/Json/IJsonSerializer.cs @@ -9,12 +9,12 @@ public interface IJsonSerializer /// /// Serialize an object to a JSON string respecting the Minified flag to produce a Minified JSON string /// - string ToJson(T Obj, bool Minified = true); + Result ToJson(T Obj, bool Minified = true); /// /// De-serialize a JSON string to typed object. /// - T FromJson(string Json); + Result FromJson(string Json); public Result FromJsonStream(Stream JsonStream); } diff --git a/SmartHealthCard.Token/Serializers/Json/JsonSerializer.cs b/SmartHealthCard.Token/Serializers/Json/JsonSerializer.cs index ffe2f95..e1888ca 100644 --- a/SmartHealthCard.Token/Serializers/Json/JsonSerializer.cs +++ b/SmartHealthCard.Token/Serializers/Json/JsonSerializer.cs @@ -21,20 +21,21 @@ public JsonSerializer() this.Serializer = Newtonsoft.Json.JsonSerializer.CreateDefault(); } - public virtual async Task SerializeAsync(T Obj, bool Minified = true) + public virtual async Task> SerializeAsync(T Obj, bool Minified = true) { - return await Task.Run(() => GetBytes(this.ToJson(Obj, Minified))); + Result ToJsonResult = await Task.Run(() => this.ToJson(Obj, Minified)); + if (ToJsonResult.Failure) + return Result.Fail(ToJsonResult.ErrorMessage); + + return Result.Ok(GetBytes(ToJsonResult.Value)); } - public virtual async Task DeserializeAsync(byte[] bytes) + public virtual async Task> DeserializeAsync(byte[] bytes) { - T? Item = await Task.Run(() => this.FromJson(GetString(bytes))); - if (Item is null) - throw new DeserializationException($"Unable to deserialize the JWS Header to type {typeof(T).Name}"); - return Item; + return await Task.Run(() => this.FromJson(GetString(bytes))); } - public string ToJson(T Obj, bool Minified = true) + public Result ToJson(T Obj, bool Minified = true) { if (!Minified) Serializer.Formatting = Formatting.Indented; @@ -42,18 +43,33 @@ public string ToJson(T Obj, bool Minified = true) var Builder = new StringBuilder(); using var StringWriter = new StringWriter(Builder); using var JsonWriter = new JsonTextWriter(StringWriter); - Serializer.Serialize(JsonWriter, Obj); - return Builder.ToString(); + try + { + Serializer.Serialize(JsonWriter, Obj); + } + catch(JsonException JsonException) + { + return Result.Fail($"Error occurred while converting an instance of the object type {typeof(T).FullName} to JSON. The JSON serializer error was: {JsonException.Message}"); + } + return Result.Ok(Builder.ToString()); } - public T FromJson(string Json) + public Result FromJson(string Json) { using var StringReader = new StringReader(Json); using var JsonReader = new JsonTextReader(StringReader); - T? Item = Serializer.Deserialize(JsonReader); - if (Item is null) - throw new DeserializationException($"Unable to deserialize the JWS Header to type {typeof(T).Name}"); - return Item; + try + { + T? Item = Serializer.Deserialize(JsonReader); + if (Item is null) + return Result.Fail($"The desalinizing of a JSON string failed while attempting to Deserialize to a type of {typeof(T).Name}, desalinizing returned an null object."); + + return Result.Ok(Item); + } + catch(JsonException JsonException) + { + return Result.Fail($"Error occurred while converting a JSON string to an instance of object type {typeof(T).FullName}. The JSON deserialize error was: {JsonException.Message}"); + } } public Result FromJsonStream(Stream JsonStream) @@ -66,18 +82,17 @@ public Result FromJsonStream(Stream JsonStream) { T? Item = Serializer.Deserialize(jsonReader); if (Item is null) - throw new DeserializationException($"Unable to deserialize the JWS Header to type {typeof(T).Name}"); - + return Result.Fail($"The desalinizing of a JSON stream failed while attempting to Deserialize to a type of {typeof(T).Name}, desalinizing returned an null object."); + return Result.Ok(Item); } } } - catch(Exception Exec) + catch (JsonException JsonException) { - return Result.Fail($"Unable to parser the returned content to JWKS. Message: {Exec.Message }"); - } + return Result.Fail($"Error occurred while converting a JSON stream to an instance of object type {typeof(T).FullName}. The JSON deserialize error was: {JsonException.Message}"); + } } - } } diff --git a/SmartHealthCard.Token/Serializers/Jws/IJwsSerializer.cs b/SmartHealthCard.Token/Serializers/Jws/IJwsSerializer.cs index d7be728..3612438 100644 --- a/SmartHealthCard.Token/Serializers/Jws/IJwsSerializer.cs +++ b/SmartHealthCard.Token/Serializers/Jws/IJwsSerializer.cs @@ -1,18 +1,19 @@ using SmartHealthCard.Token.Serializers.Json; +using SmartHealthCard.Token.Support; using System.Threading.Tasks; namespace SmartHealthCard.Token.Serializers.Jws { - public interface IJwsSerializer //: IJsonSerializer + public interface IJwsSerializer { /// /// Serialize an object to a JSON string byte[] /// - Task SerializeAsync(T Obj, bool Minified = true); + Task> SerializeAsync(T Obj, bool Minified = true); /// /// De-serialize a JSON string to typed object. /// - Task DeserializeAsync(byte[] bytes); + Task> DeserializeAsync(byte[] bytes); } } diff --git a/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsHeaderSerializer.cs b/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsHeaderSerializer.cs index b4b9323..ca6cd3f 100644 --- a/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsHeaderSerializer.cs +++ b/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsHeaderSerializer.cs @@ -16,36 +16,38 @@ public SmartHealthCardJwsHeaderSerializer(IJsonSerializer JsonSerializer) this.JsonSerializer = JsonSerializer; } - public async Task SerializeAsync(T Obj, bool Minified = true) + public async Task> SerializeAsync(T Obj, bool Minified = true) { if (Obj is SmartHealthCareJWSHeaderModel SmartHealthCareJWSHeaderModel) { - string Json = ToJson(SmartHealthCareJWSHeaderModel, Minified); - return await Task.Run(() => Encoders.Utf8EncodingSupport.GetBytes(Json)); + Result ToJsonResult = ToJson(SmartHealthCareJWSHeaderModel, Minified); + if (ToJsonResult.Failure) + return Result.Fail($"{ToJsonResult.ErrorMessage}"); + + return await Task.Run(() => Result.Ok(Encoders.Utf8EncodingSupport.GetBytes(ToJsonResult.Value))); } else { - throw new ArgumentException($"The {this.GetType().Name} Serialize method can only work with an input of type {typeof(SmartHealthCareJWSHeaderModel).Name}"); + throw new InvalidCastException($"The {this.GetType().Name} Serialize method can only work with an input of type {typeof(SmartHealthCareJWSHeaderModel).Name}"); } } - public async Task DeserializeAsync(byte[] bytes) + public async Task> DeserializeAsync(byte[] bytes) { string json = Encoders.Utf8EncodingSupport.GetString(bytes); if (typeof(T) == typeof(SmartHealthCareJWSHeaderModel)) { - return await Task.Run(() => (T)(object)FromJson(json)); - } + return await Task.Run(() => FromJson(json)); + } else { - throw new TypeAccessException(typeof(T).Name); + throw new InvalidCastException(typeof(T).Name); } - } + + public Result ToJson(T Obj, bool Minified = true) => JsonSerializer.ToJson(Obj); - public string ToJson(T Obj, bool Minified = true) => JsonSerializer.ToJson(Obj); - - public T FromJson(string Json) => JsonSerializer.FromJson(Json); + public Result FromJson(string Json) => JsonSerializer.FromJson(Json); public Result FromJsonStream(Stream JsonStream) { diff --git a/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsPayloadSerializer.cs b/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsPayloadSerializer.cs index 1cecdda..fb4354f 100644 --- a/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsPayloadSerializer.cs +++ b/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsPayloadSerializer.cs @@ -18,14 +18,18 @@ public SmartHealthCardJwsPayloadSerializer(IJsonSerializer JsonSerializer) this.JsonSerializer = JsonSerializer; } - public async Task SerializeAsync(T Obj, bool Minified = true) + public async Task> SerializeAsync(T Obj, bool Minified = true) { if (!Minified) throw new ArgumentException($"{nameof(Minified)} must be true for Smart Health Card JWS Payload JSON."); if (Obj is SmartHealthCardModel SmartHealthCardModel) { - return await DeflateCompression.CompressAsync(ToJson(SmartHealthCardModel, true)); + Result ToJsonResult = ToJson(SmartHealthCardModel, true); + if (ToJsonResult.Failure) + return Result.Fail(ToJsonResult.ErrorMessage); + + return Result.Ok(await DeflateCompression.CompressAsync(ToJsonResult.Value)); } else { @@ -33,17 +37,29 @@ public async Task SerializeAsync(T Obj, bool Minified = true) } } - public async Task DeserializeAsync(byte[] bytes) + public async Task> DeserializeAsync(byte[] bytes) { string MinifiedSmartHealthCardJson = await DeflateCompression.UncompressAsync(bytes); if (typeof(T) == typeof(SmartHealthCardModel)) { - SmartHealthCardModel SmartHealthCardModel = FromJson(MinifiedSmartHealthCardJson); - return (T)(object)SmartHealthCardModel; + Result SmartHealthCardModelResult = FromJson(MinifiedSmartHealthCardJson); + if (SmartHealthCardModelResult.Failure) + return Result.Fail(SmartHealthCardModelResult.ErrorMessage); + + return await Task.Run(() => FromJson(MinifiedSmartHealthCardJson)); + //return (T)(object)SmartHealthCardModel; } else if (typeof(T) == typeof(JwsBody)) { - return (T)(object)FromJson(MinifiedSmartHealthCardJson); + //We must use the PayloadSerializer.Deserialize and not the JsonSerializer.FromJson() because for + //SMART Health Cards the payload is DEFLATE compressed bytes and not a JSON string + Result JwsBodyResult = FromJson(MinifiedSmartHealthCardJson); + if (JwsBodyResult.Failure) + return Result.Fail(JwsBodyResult.ErrorMessage); + + return await Task.Run(() => FromJson(MinifiedSmartHealthCardJson)); + + //return (T)(object)FromJson(MinifiedSmartHealthCardJson); } else { @@ -52,9 +68,9 @@ public async Task DeserializeAsync(byte[] bytes) } - public string ToJson(T Obj, bool Minified = true) => JsonSerializer.ToJson(Obj); + public Result ToJson(T Obj, bool Minified = true) => JsonSerializer.ToJson(Obj); - public T FromJson(string Json) => JsonSerializer.FromJson(Json); + public Result FromJson(string Json) => JsonSerializer.FromJson(Json); public Result FromJsonStream(Stream JsonStream) { diff --git a/SmartHealthCard.Token/SmartHealthCardDecoder.cs b/SmartHealthCard.Token/SmartHealthCardDecoder.cs index 064a51f..17d0377 100644 --- a/SmartHealthCard.Token/SmartHealthCardDecoder.cs +++ b/SmartHealthCard.Token/SmartHealthCardDecoder.cs @@ -6,6 +6,7 @@ using SmartHealthCard.Token.Serializers.Json; using SmartHealthCard.Token.Serializers.Jws; using SmartHealthCard.Token.Serializers.Shc; +using SmartHealthCard.Token.Support; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; @@ -95,7 +96,11 @@ public SmartHealthCardDecoder(IJsonSerializer? JsonSerializer, IJwsHeaderSeriali public async Task DecodeToJsonAsync(string Token, bool Verify = true) { SmartHealthCardModel SmartHealthCardModel = await DecodeAsync(Token, Verify); - return JsonSerializer.ToJson(SmartHealthCardModel, Minified: false); + Result ToJsonResult = JsonSerializer.ToJson(SmartHealthCardModel, Minified: false); + if (ToJsonResult.Failure) + throw new System.Exception(ToJsonResult.ErrorMessage); + + return ToJsonResult.Value; } /// @@ -116,8 +121,11 @@ public async Task DecodeAsync(string Token, bool Verify = this.JwsSignatureValidator, this.JwsHeaderValidator, this.JwsPayloadValidator); + Result DecodePayloadResult = await JwsDecoder.DecodePayloadAsync(Token: Token, Verity: Verify); + if (DecodePayloadResult.Failure) + throw new System.Exception(DecodePayloadResult.ErrorMessage); - return await JwsDecoder.DecodePayloadAsync(Token: Token, Verity: Verify); + return DecodePayloadResult.Value; } else { @@ -125,7 +133,11 @@ public async Task DecodeAsync(string Token, bool Verify = this.HeaderSerializer, this.PayloadSerializer); - return await JwsDecoder.DecodePayloadAsync(Token: Token, Verity: Verify); + Result DecodePayloadResult = await JwsDecoder.DecodePayloadAsync(Token: Token, Verity: Verify); + if (DecodePayloadResult.Failure) + throw new System.Exception(DecodePayloadResult.ErrorMessage); + + return DecodePayloadResult.Value; } } } diff --git a/SmartHealthCard.Token/SmartHealthCardEncoder.cs b/SmartHealthCard.Token/SmartHealthCardEncoder.cs index 2bb12b5..090562f 100644 --- a/SmartHealthCard.Token/SmartHealthCardEncoder.cs +++ b/SmartHealthCard.Token/SmartHealthCardEncoder.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; +using SmartHealthCard.Token.Support; namespace SmartHealthCard.Token { @@ -59,7 +60,10 @@ public async Task GetTokenAsync(X509Certificate2 Certificate, SmartHealt IAlgorithm Algorithm = new ES256Algorithm(Certificate, JsonSerializer); SmartHealthCareJWSHeaderModel Header = GetHeader(Algorithm); IJwsEncoder JwsEncoder = GetEncoder(Certificate, Algorithm); - return await JwsEncoder.EncodeAsync(Header, SmartHealthCard); + Result EncoderResult = await JwsEncoder.EncodeAsync(Header, SmartHealthCard); + if (EncoderResult.Failure) + throw new System.Exception(EncoderResult.ErrorMessage); + return EncoderResult.Value; } /// @@ -81,10 +85,18 @@ public async Task GetSmartHealthCardFile(X509Certificate2 Certificate, L SmartHealthCardFile SmartHealthCardFile = new SmartHealthCardFile(); foreach (SmartHealthCardModel SmartHealthCard in SmartHealthCardList) { - SmartHealthCardFile.VerifiableCredentialList.Add(await JwsEncoder.EncodeAsync(Header, SmartHealthCard)); + Result EncoderResult = await JwsEncoder.EncodeAsync(Header, SmartHealthCard); + if (EncoderResult.Success) + SmartHealthCardFile.VerifiableCredentialList.Add(EncoderResult.Value); + else + throw new System.Exception(EncoderResult.ErrorMessage); } + Result ToJsonResult = JsonSerializer.ToJson(SmartHealthCardFile); + if (ToJsonResult.Failure) + throw new System.Exception(ToJsonResult.ErrorMessage); - return JsonSerializer.ToJson(SmartHealthCardFile); + + return ToJsonResult.Value; } private IJwsEncoder GetEncoder(X509Certificate2 Certificate, IAlgorithm Algorithm) @@ -95,8 +107,12 @@ private IJwsEncoder GetEncoder(X509Certificate2 Certificate, IAlgorithm Algorith private SmartHealthCareJWSHeaderModel GetHeader(IAlgorithm Algorithm) { + Result KidResult = Algorithm.GetKid(); + if (KidResult.Failure) + throw new System.Exception(KidResult.ErrorMessage); + //Create the Smart Health Card JWS Header Model - return new SmartHealthCareJWSHeaderModel(Algorithm.Name, "DEF", Algorithm.GetKid()); + return new SmartHealthCareJWSHeaderModel(Algorithm.Name, "DEF", KidResult.Value); } } } diff --git a/SmartHealthCard.Token/SmartHealthCardJWKS.cs b/SmartHealthCard.Token/SmartHealthCardJWKS.cs index 0c1cdc3..4a19e20 100644 --- a/SmartHealthCard.Token/SmartHealthCardJWKS.cs +++ b/SmartHealthCard.Token/SmartHealthCardJWKS.cs @@ -1,6 +1,7 @@ using SmartHealthCard.Token.Algorithms; using SmartHealthCard.Token.Model.Jwks; using SmartHealthCard.Token.Serializers.Json; +using SmartHealthCard.Token.Support; using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; @@ -44,15 +45,26 @@ public JsonWebKeySet GetJsonWebKeySet(IEnumerable CertificateL foreach (X509Certificate2 Certificate in CertificateList) { ES256Algorithm Algorithm = new ES256Algorithm(Certificate, JsonSerializer); + Result KidResult = Algorithm.GetKid(); + if (KidResult.Failure) + throw new System.Exception(KidResult.ErrorMessage); + + Result XResult = Algorithm.GetPointCoordinateX(); + if (XResult.Failure) + throw new System.Exception(XResult.ErrorMessage); + + Result YResult = Algorithm.GetPointCoordinateY(); + if (YResult.Failure) + throw new System.Exception(YResult.ErrorMessage); JsonWebKey JsonWebKeySetModel = new JsonWebKey( Kty: Algorithm.KeyTypeName, - Kid: Algorithm.GetKid(), + Kid: KidResult.Value, Use: "sig", Alg: Algorithm.Name, Crv: Algorithm.CurveName, - X: Algorithm.GetPointCoordinateX(), - Y: Algorithm.GetPointCoordinateY()); + X: XResult.Value, + Y: YResult.Value); JsonWebKeySetModelList.Add(JsonWebKeySetModel); } @@ -69,7 +81,11 @@ public string Get(IEnumerable CertificateList, bool Minified = { JsonWebKeySet JsonWebKeySet = GetJsonWebKeySet(CertificateList); IJsonSerializer JsonSerializer = new JsonSerializer(); - return JsonSerializer.ToJson(JsonWebKeySet, Minified); + Result ToJsonResult = JsonSerializer.ToJson(JsonWebKeySet, Minified); + if (ToJsonResult.Failure) + throw new System.Exception(ToJsonResult.ErrorMessage); + + return ToJsonResult.Value; } } } diff --git a/SmartHealthCard.Token/Support/Result.cs b/SmartHealthCard.Token/Support/Result.cs index 11e75d3..747cefa 100644 --- a/SmartHealthCard.Token/Support/Result.cs +++ b/SmartHealthCard.Token/Support/Result.cs @@ -6,7 +6,7 @@ namespace SmartHealthCard.Token.Support { - public class Result + public partial class Result { protected Result(bool Success, bool Retryable, string ErrorMessage) { @@ -14,6 +14,9 @@ protected Result(bool Success, bool Retryable, string ErrorMessage) this.ErrorMessage = ErrorMessage; this.Retryable = Retryable; } + + public static string ErrorMessagesDelimiter = ", "; + /// /// The action was Successful and the result value property will be set. /// @@ -49,6 +52,46 @@ public static Result Fail(string message) return new Result(Success: false, Retryable: false, ErrorMessage: message); } + public static Result Combine(params Result[] ResultArray) + => Combine(ResultArray, ErrorMessagesDelimiter); + public static Result Combine(string ErrorMessageDelimiter, params Result[] ResultArray) + => Combine(ResultArray, ErrorMessageDelimiter); + + public static Result Combine(params Result[] results) + => Combine(results, ErrorMessagesDelimiter); + public static Result Combine(string ErrorMessageDelimiter, params Result[] ResultArray) + => Combine(ResultArray, ErrorMessageDelimiter); + + public static Result Combine(IEnumerable ResultList, string? ErrorMessageDelimiter = null) + { + List FailedResultList = ResultList.Where(x => x.Failure).ToList(); + + if (FailedResultList.Count == 0) + return Result.Ok(); + + string CombinedMessages = string.Join(ErrorMessageDelimiter ?? ErrorMessagesDelimiter, AggregateMessages(FailedResultList.Select(x => x.ErrorMessage))); + + return Result.Fail(CombinedMessages); + } + + public static Result Combine(IEnumerable> TypedResultList, string? ErrorMessageDelimiter = null) + { + IEnumerable UntypedResultList = TypedResultList.Select(result => (Result)result); + return Combine(UntypedResultList, ErrorMessageDelimiter); + } + + private static IEnumerable AggregateMessages(IEnumerable messages) + { + var dict = new Dictionary(); + foreach (var message in messages) + { + if (!dict.ContainsKey(message)) + dict.Add(message, 0); + + dict[message]++; + } + return dict.Select(x => x.Value == 1 ? x.Key : $"{x.Key} ({x.Value}×)"); + } } diff --git a/SmartHealthCard.Token/Validators/SmartHealthCardHeaderValidator.cs b/SmartHealthCard.Token/Validators/SmartHealthCardHeaderValidator.cs index 62d41f6..cf4db86 100644 --- a/SmartHealthCard.Token/Validators/SmartHealthCardHeaderValidator.cs +++ b/SmartHealthCard.Token/Validators/SmartHealthCardHeaderValidator.cs @@ -1,17 +1,22 @@ using SmartHealthCard.Token.JwsToken; using SmartHealthCard.Token.Model.Shc; +using SmartHealthCard.Token.Support; namespace SmartHealthCard.Token.Validators { public class SmartHealthCardHeaderValidator : IJwsHeaderValidator { - public void Validate(T Obj) + public Result Validate(T Obj) { if (Obj is SmartHealthCareJWSHeaderModel SmartHealthCareJWSHeaderModel) { //Validate the JWS Header is a valid Smart Health Care JWS Header - SmartHealthCareJWSHeaderModel.Validate(); + return SmartHealthCareJWSHeaderModel.Validate(); } + else + { + throw new System.InvalidCastException($"{typeof(SmartHealthCardHeaderValidator)} can only validate instances of type {typeof(SmartHealthCareJWSHeaderModel).Name}."); + } } } diff --git a/SmartHealthCard.Token/Validators/SmartHealthCardPayloadValidator.cs b/SmartHealthCard.Token/Validators/SmartHealthCardPayloadValidator.cs index d763a28..1e9fd6a 100644 --- a/SmartHealthCard.Token/Validators/SmartHealthCardPayloadValidator.cs +++ b/SmartHealthCard.Token/Validators/SmartHealthCardPayloadValidator.cs @@ -1,17 +1,22 @@ using SmartHealthCard.Token.JwsToken; using SmartHealthCard.Token.Model.Shc; +using SmartHealthCard.Token.Support; namespace SmartHealthCard.Token.Validators { public class SmartHealthCardPayloadValidator : IJwsPayloadValidator { - public void Validate(T Obj) + public Result Validate(T Obj) { if (Obj is SmartHealthCardModel SmartHealthCardModel) { //Validate the JWS Payload is a valid Smart Health Care JWS Payload - SmartHealthCardModel.Validate(); - } + return SmartHealthCardModel.Validate(); + } + else + { + throw new System.InvalidCastException($"{typeof(SmartHealthCardPayloadValidator).Name} can only validate objects of type {typeof(SmartHealthCardModel).Name}"); + } } } From f94e6a6edcf3703aee86e71548fbdfaf15be3fd8 Mon Sep 17 00:00:00 2001 From: angusmillar Date: Fri, 4 Jun 2021 19:21:02 +1000 Subject: [PATCH 5/6] Added new succinct Exception classes --- .../Exceptions/DeserializationException.cs | 8 -------- .../SmartHealthCardDecoderException.cs | 8 ++++++++ .../SmartHealthCardEncoderException.cs | 8 ++++++++ .../SmartHealthCardJwksException.cs | 8 ++++++++ .../SmartHealthCardJwsPayloadSerializer.cs | 8 +------- .../SmartHealthCardDecoder.cs | 7 ++++--- .../SmartHealthCardEncoder.cs | 10 +++++----- SmartHealthCard.Token/SmartHealthCardJWKS.cs | 20 ++++++++----------- 8 files changed, 42 insertions(+), 35 deletions(-) delete mode 100644 SmartHealthCard.Token/Exceptions/DeserializationException.cs create mode 100644 SmartHealthCard.Token/Exceptions/SmartHealthCardDecoderException.cs create mode 100644 SmartHealthCard.Token/Exceptions/SmartHealthCardEncoderException.cs create mode 100644 SmartHealthCard.Token/Exceptions/SmartHealthCardJwksException.cs diff --git a/SmartHealthCard.Token/Exceptions/DeserializationException.cs b/SmartHealthCard.Token/Exceptions/DeserializationException.cs deleted file mode 100644 index 724aa8c..0000000 --- a/SmartHealthCard.Token/Exceptions/DeserializationException.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SmartHealthCard.Token.Exceptions -{ - class DeserializationException : SmartHealthCardException - { - public DeserializationException(string Message) - : base(Message){ } - } -} diff --git a/SmartHealthCard.Token/Exceptions/SmartHealthCardDecoderException.cs b/SmartHealthCard.Token/Exceptions/SmartHealthCardDecoderException.cs new file mode 100644 index 0000000..e2a7a90 --- /dev/null +++ b/SmartHealthCard.Token/Exceptions/SmartHealthCardDecoderException.cs @@ -0,0 +1,8 @@ +namespace SmartHealthCard.Token.Exceptions +{ + public class SmartHealthCardDecoderException : SmartHealthCardException + { + public SmartHealthCardDecoderException(string Message) + : base(Message){ } + } +} diff --git a/SmartHealthCard.Token/Exceptions/SmartHealthCardEncoderException.cs b/SmartHealthCard.Token/Exceptions/SmartHealthCardEncoderException.cs new file mode 100644 index 0000000..acad015 --- /dev/null +++ b/SmartHealthCard.Token/Exceptions/SmartHealthCardEncoderException.cs @@ -0,0 +1,8 @@ +namespace SmartHealthCard.Token.Exceptions +{ + public class SmartHealthCardEncoderException : SmartHealthCardException + { + public SmartHealthCardEncoderException(string Message) + : base(Message){ } + } +} diff --git a/SmartHealthCard.Token/Exceptions/SmartHealthCardJwksException.cs b/SmartHealthCard.Token/Exceptions/SmartHealthCardJwksException.cs new file mode 100644 index 0000000..8390233 --- /dev/null +++ b/SmartHealthCard.Token/Exceptions/SmartHealthCardJwksException.cs @@ -0,0 +1,8 @@ +namespace SmartHealthCard.Token.Exceptions +{ + public class SmartHealthCardJwksException : SmartHealthCardException + { + public SmartHealthCardJwksException(string Message) + : base(Message){ } + } +} diff --git a/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsPayloadSerializer.cs b/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsPayloadSerializer.cs index fb4354f..4c6865f 100644 --- a/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsPayloadSerializer.cs +++ b/SmartHealthCard.Token/Serializers/Shc/SmartHealthCardJwsPayloadSerializer.cs @@ -45,9 +45,7 @@ public async Task> DeserializeAsync(byte[] bytes) Result SmartHealthCardModelResult = FromJson(MinifiedSmartHealthCardJson); if (SmartHealthCardModelResult.Failure) return Result.Fail(SmartHealthCardModelResult.ErrorMessage); - - return await Task.Run(() => FromJson(MinifiedSmartHealthCardJson)); - //return (T)(object)SmartHealthCardModel; + return await Task.Run(() => FromJson(MinifiedSmartHealthCardJson)); } else if (typeof(T) == typeof(JwsBody)) { @@ -56,16 +54,12 @@ public async Task> DeserializeAsync(byte[] bytes) Result JwsBodyResult = FromJson(MinifiedSmartHealthCardJson); if (JwsBodyResult.Failure) return Result.Fail(JwsBodyResult.ErrorMessage); - return await Task.Run(() => FromJson(MinifiedSmartHealthCardJson)); - - //return (T)(object)FromJson(MinifiedSmartHealthCardJson); } else { throw new TypeAccessException(typeof(T).Name); } - } public Result ToJson(T Obj, bool Minified = true) => JsonSerializer.ToJson(Obj); diff --git a/SmartHealthCard.Token/SmartHealthCardDecoder.cs b/SmartHealthCard.Token/SmartHealthCardDecoder.cs index 17d0377..17b719c 100644 --- a/SmartHealthCard.Token/SmartHealthCardDecoder.cs +++ b/SmartHealthCard.Token/SmartHealthCardDecoder.cs @@ -1,4 +1,5 @@ using SmartHealthCard.Token.Algorithms; +using SmartHealthCard.Token.Exceptions; using SmartHealthCard.Token.JwsToken; using SmartHealthCard.Token.Model.Jwks; using SmartHealthCard.Token.Model.Shc; @@ -98,7 +99,7 @@ public async Task DecodeToJsonAsync(string Token, bool Verify = true) SmartHealthCardModel SmartHealthCardModel = await DecodeAsync(Token, Verify); Result ToJsonResult = JsonSerializer.ToJson(SmartHealthCardModel, Minified: false); if (ToJsonResult.Failure) - throw new System.Exception(ToJsonResult.ErrorMessage); + throw new SmartHealthCardDecoderException(ToJsonResult.ErrorMessage); return ToJsonResult.Value; } @@ -123,7 +124,7 @@ public async Task DecodeAsync(string Token, bool Verify = this.JwsPayloadValidator); Result DecodePayloadResult = await JwsDecoder.DecodePayloadAsync(Token: Token, Verity: Verify); if (DecodePayloadResult.Failure) - throw new System.Exception(DecodePayloadResult.ErrorMessage); + throw new SmartHealthCardDecoderException(DecodePayloadResult.ErrorMessage); return DecodePayloadResult.Value; } @@ -135,7 +136,7 @@ public async Task DecodeAsync(string Token, bool Verify = Result DecodePayloadResult = await JwsDecoder.DecodePayloadAsync(Token: Token, Verity: Verify); if (DecodePayloadResult.Failure) - throw new System.Exception(DecodePayloadResult.ErrorMessage); + throw new SmartHealthCardDecoderException(DecodePayloadResult.ErrorMessage); return DecodePayloadResult.Value; } diff --git a/SmartHealthCard.Token/SmartHealthCardEncoder.cs b/SmartHealthCard.Token/SmartHealthCardEncoder.cs index 090562f..1cfa033 100644 --- a/SmartHealthCard.Token/SmartHealthCardEncoder.cs +++ b/SmartHealthCard.Token/SmartHealthCardEncoder.cs @@ -8,6 +8,7 @@ using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using SmartHealthCard.Token.Support; +using SmartHealthCard.Token.Exceptions; namespace SmartHealthCard.Token { @@ -62,7 +63,7 @@ public async Task GetTokenAsync(X509Certificate2 Certificate, SmartHealt IJwsEncoder JwsEncoder = GetEncoder(Certificate, Algorithm); Result EncoderResult = await JwsEncoder.EncodeAsync(Header, SmartHealthCard); if (EncoderResult.Failure) - throw new System.Exception(EncoderResult.ErrorMessage); + throw new SmartHealthCardEncoderException(EncoderResult.ErrorMessage); return EncoderResult.Value; } @@ -89,12 +90,11 @@ public async Task GetSmartHealthCardFile(X509Certificate2 Certificate, L if (EncoderResult.Success) SmartHealthCardFile.VerifiableCredentialList.Add(EncoderResult.Value); else - throw new System.Exception(EncoderResult.ErrorMessage); + throw new SmartHealthCardEncoderException(EncoderResult.ErrorMessage); } Result ToJsonResult = JsonSerializer.ToJson(SmartHealthCardFile); if (ToJsonResult.Failure) - throw new System.Exception(ToJsonResult.ErrorMessage); - + throw new SmartHealthCardEncoderException(ToJsonResult.ErrorMessage); return ToJsonResult.Value; } @@ -109,7 +109,7 @@ private SmartHealthCareJWSHeaderModel GetHeader(IAlgorithm Algorithm) { Result KidResult = Algorithm.GetKid(); if (KidResult.Failure) - throw new System.Exception(KidResult.ErrorMessage); + throw new SmartHealthCardEncoderException(KidResult.ErrorMessage); //Create the Smart Health Card JWS Header Model return new SmartHealthCareJWSHeaderModel(Algorithm.Name, "DEF", KidResult.Value); diff --git a/SmartHealthCard.Token/SmartHealthCardJWKS.cs b/SmartHealthCard.Token/SmartHealthCardJWKS.cs index 4a19e20..37d1e76 100644 --- a/SmartHealthCard.Token/SmartHealthCardJWKS.cs +++ b/SmartHealthCard.Token/SmartHealthCardJWKS.cs @@ -1,4 +1,5 @@ using SmartHealthCard.Token.Algorithms; +using SmartHealthCard.Token.Exceptions; using SmartHealthCard.Token.Model.Jwks; using SmartHealthCard.Token.Serializers.Json; using SmartHealthCard.Token.Support; @@ -45,17 +46,12 @@ public JsonWebKeySet GetJsonWebKeySet(IEnumerable CertificateL foreach (X509Certificate2 Certificate in CertificateList) { ES256Algorithm Algorithm = new ES256Algorithm(Certificate, JsonSerializer); - Result KidResult = Algorithm.GetKid(); - if (KidResult.Failure) - throw new System.Exception(KidResult.ErrorMessage); - - Result XResult = Algorithm.GetPointCoordinateX(); - if (XResult.Failure) - throw new System.Exception(XResult.ErrorMessage); - - Result YResult = Algorithm.GetPointCoordinateY(); - if (YResult.Failure) - throw new System.Exception(YResult.ErrorMessage); + Result KidResult = Algorithm.GetKid(); + Result XResult = Algorithm.GetPointCoordinateX(); + Result YResult = Algorithm.GetPointCoordinateY(); + Result ResultCombine = Result.Combine(KidResult, XResult, YResult); + if (ResultCombine.Failure) + throw new SmartHealthCardJwksException(ResultCombine.ErrorMessage); JsonWebKey JsonWebKeySetModel = new JsonWebKey( Kty: Algorithm.KeyTypeName, @@ -83,7 +79,7 @@ public string Get(IEnumerable CertificateList, bool Minified = IJsonSerializer JsonSerializer = new JsonSerializer(); Result ToJsonResult = JsonSerializer.ToJson(JsonWebKeySet, Minified); if (ToJsonResult.Failure) - throw new System.Exception(ToJsonResult.ErrorMessage); + throw new SmartHealthCardJwksException(ToJsonResult.ErrorMessage); return ToJsonResult.Value; } From d6b2e2212c0efb6b6e793bcaaa0aeaddb2eb26c0 Mon Sep 17 00:00:00 2001 From: angusmillar Date: Fri, 4 Jun 2021 19:32:19 +1000 Subject: [PATCH 6/6] Updated doco in readme file --- README.md | 42 +++++++++++++++++++++----- SmartHealthCard.DecoderDemo/Program.cs | 11 +++++-- SmartHealthCard.EncoderDemo/Program.cs | 19 ++++++++++-- 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 354226f..e36cd6f 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ ## [SmartHealthCard.Token](https://www.nuget.org/packages/SmartHealthCard.Token/0.1.0-alpha.1): Encode, Decode & Verifiy SMART Health Card JWS tokens ``` -Install-Package SmartHealthCard.QRCode -Version 0.1.0-alpha.1 +Install-Package SmartHealthCard.QRCode -Version 0.1.0-alpha.3 ``` @@ -43,6 +43,7 @@ Install-Package SmartHealthCard.QRCode -Version 0.1.0-alpha.1 using SmartHealthCard.QRCode; using SmartHealthCard.Token; using SmartHealthCard.Token.Certificates; +using SmartHealthCard.Token.Exceptions; using SmartHealthCard.Token.Model.Shc; using System; using System.Collections.Generic; @@ -100,8 +101,22 @@ namespace SHC.EncoderDemo //Instantiate the Smart Health Card Encoder SmartHealthCardEncoder SmartHealthCardEncoder = new SmartHealthCardEncoder(); - //Get the Smart Health Card JWS Token - string SmartHealthCardJwsToken = await SmartHealthCardEncoder.GetTokenAsync(Certificate, SmartHealthCard); + string SmartHealthCardJwsToken = string.Empty; + try + { + //Get the Smart Health Card JWS Token + SmartHealthCardJwsToken = await SmartHealthCardEncoder.GetTokenAsync(Certificate, SmartHealthCard); + } + catch (SmartHealthCardEncoderException EncoderException) + { + Console.WriteLine("The SMART Health Card Encoder has found an error, please see message below:"); + Console.WriteLine(EncoderException.Message); + } + catch (Exception Exception) + { + Console.WriteLine("Oops, there is an unexpected development exception"); + Console.WriteLine(Exception.Message); + } //Instantiate the Smart Health Card QR Code Factory SmartHealthCardQRCodeEncoder SmartHealthCardQRCodeEncoder = new SmartHealthCardQRCodeEncoder(); @@ -126,11 +141,15 @@ namespace SHC.EncoderDemo ```C# using SmartHealthCard.Token; using SmartHealthCard.Token.Certificates; +using SmartHealthCard.Token.Exceptions; using SmartHealthCard.Token.Model.Jwks; using SmartHealthCard.Token.Model.Shc; +using SmartHealthCard.Token.Providers; +using SmartHealthCard.Token.Support; using System; using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; +using System.Threading; using System.Threading.Tasks; namespace SHC.DecoderDemo @@ -187,10 +206,15 @@ namespace SHC.DecoderDemo //Or decode and verify, returning the Smart Health Card as a JSON string, throws exceptions if not valid //string DecodedSmartHealthCardJson = await Decoder.DecodeToJsonAsync(SmartHealthCardJwsToken, Verify: true); } - catch (Exception Exec) + catch (SmartHealthCardDecoderException DecoderException) { Console.WriteLine("The SMART Health Card JWS token was invalid, please see message below:"); - Console.WriteLine(Exec.Message); + Console.WriteLine(DecoderException.Message); + } + catch (Exception Exception) + { + Console.WriteLine("Oops, there is an unexpected development exception"); + Console.WriteLine(Exception.Message); } } } @@ -204,7 +228,7 @@ namespace SHC.DecoderDemo this.Certificate = Certificate; } - public Task GetJwksAsync(Uri WellKnownJwksUri) + public Task> GetJwksAsync(Uri WellKnownJwksUri, CancellationToken? CancellationToken = null) { //In production the default implementation of this IJwksProvider interface would //retrieve the JWKS file from the provided 'WellKnownJwksUri' URL that is found in @@ -213,9 +237,11 @@ namespace SHC.DecoderDemo //own JWKS which we have generated from our certificate as seen below. //This allows you to test before you have a publicly exposed endpoint for you JWKS. SmartHealthCardJwks SmartHealthCardJwks = new SmartHealthCardJwks(); - SmartHealthCard.Token.Model.Jwks.JsonWebKeySet Jwks = SmartHealthCardJwks.GetJsonWebKeySet(new List() { Certificate }); - return Task.FromResult(Jwks); + JsonWebKeySet Jwks = SmartHealthCardJwks.GetJsonWebKeySet(new List() { Certificate }); + return Task.FromResult(Result.Ok(Jwks)); } + + } } ``` diff --git a/SmartHealthCard.DecoderDemo/Program.cs b/SmartHealthCard.DecoderDemo/Program.cs index 41d2ad4..c415cce 100644 --- a/SmartHealthCard.DecoderDemo/Program.cs +++ b/SmartHealthCard.DecoderDemo/Program.cs @@ -1,5 +1,6 @@ using SmartHealthCard.Token; using SmartHealthCard.Token.Certificates; +using SmartHealthCard.Token.Exceptions; using SmartHealthCard.Token.Model.Jwks; using SmartHealthCard.Token.Model.Shc; using SmartHealthCard.Token.Providers; @@ -64,10 +65,15 @@ static async Task DecoderDemoRunner() //Or decode and verify, returning the Smart Health Card as a JSON string, throws exceptions if not valid //string DecodedSmartHealthCardJson = await Decoder.DecodeToJsonAsync(SmartHealthCardJwsToken, Verify: true); } - catch (Exception Exec) + catch (SmartHealthCardDecoderException DecoderException) { Console.WriteLine("The SMART Health Card JWS token was invalid, please see message below:"); - Console.WriteLine(Exec.Message); + Console.WriteLine(DecoderException.Message); + } + catch (Exception Exception) + { + Console.WriteLine("Oops, there is an unexpected development exception"); + Console.WriteLine(Exception.Message); } } } @@ -91,7 +97,6 @@ public Task> GetJwksAsync(Uri WellKnownJwksUri, Cancellati //This allows you to test before you have a publicly exposed endpoint for you JWKS. SmartHealthCardJwks SmartHealthCardJwks = new SmartHealthCardJwks(); JsonWebKeySet Jwks = SmartHealthCardJwks.GetJsonWebKeySet(new List() { Certificate }); - return Task.FromResult(Result.Ok(Jwks)); } diff --git a/SmartHealthCard.EncoderDemo/Program.cs b/SmartHealthCard.EncoderDemo/Program.cs index 6897c53..48ac77b 100644 --- a/SmartHealthCard.EncoderDemo/Program.cs +++ b/SmartHealthCard.EncoderDemo/Program.cs @@ -1,6 +1,7 @@ using SmartHealthCard.QRCode; using SmartHealthCard.Token; using SmartHealthCard.Token.Certificates; +using SmartHealthCard.Token.Exceptions; using SmartHealthCard.Token.Model.Shc; using System; using System.Collections.Generic; @@ -58,8 +59,22 @@ static async Task EncoderDemoRunner() //Instantiate the Smart Health Card Encoder SmartHealthCardEncoder SmartHealthCardEncoder = new SmartHealthCardEncoder(); - //Get the Smart Health Card JWS Token - string SmartHealthCardJwsToken = await SmartHealthCardEncoder.GetTokenAsync(Certificate, SmartHealthCard); + string SmartHealthCardJwsToken = string.Empty; + try + { + //Get the Smart Health Card JWS Token + SmartHealthCardJwsToken = await SmartHealthCardEncoder.GetTokenAsync(Certificate, SmartHealthCard); + } + catch (SmartHealthCardEncoderException EncoderException) + { + Console.WriteLine("The SMART Health Card Encoder has found an error, please see message below:"); + Console.WriteLine(EncoderException.Message); + } + catch (Exception Exception) + { + Console.WriteLine("Oops, there is an unexpected development exception"); + Console.WriteLine(Exception.Message); + } //Instantiate the Smart Health Card QR Code Factory SmartHealthCardQRCodeEncoder SmartHealthCardQRCodeEncoder = new SmartHealthCardQRCodeEncoder();