From 15a3c1b9ca280aba5bce26e2117c277c09534fd3 Mon Sep 17 00:00:00 2001 From: angusmillar Date: Fri, 28 May 2021 18:02:37 +1000 Subject: [PATCH] Added support to decode the raw QR Code data to a SMART Health Card JWS token, see SmartHealthCardQRCodeDecoder --- .../Encoder/INumericalModeDecoder.cs | 10 ++++ .../Encoder/IQRCodeDecoder.cs | 10 ++++ .../Encoder/NumericalModeDecoder.cs | 34 +++++++++++ .../Encoder/QRCodeDecoder.cs | 41 +++++++++++++ .../Exceptions/QRCodeChunkFormatException.cs | 15 +++++ .../SmartHealthCard.QRCode.csproj | 4 +- .../SmartHealthCardQRCodeDecoder.cs | 53 +++++++++++++++++ .../SmartHealthCardQRCodeEncoder.cs | 3 +- ...cs => SmartHealthCardQRCodeEncoderTest.cs} | 58 ++++++++++++++++++- 9 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 SmartHealthCard.QRCode/Encoder/INumericalModeDecoder.cs create mode 100644 SmartHealthCard.QRCode/Encoder/IQRCodeDecoder.cs create mode 100644 SmartHealthCard.QRCode/Encoder/NumericalModeDecoder.cs create mode 100644 SmartHealthCard.QRCode/Encoder/QRCodeDecoder.cs create mode 100644 SmartHealthCard.QRCode/Exceptions/QRCodeChunkFormatException.cs create mode 100644 SmartHealthCard.QRCode/SmartHealthCardQRCodeDecoder.cs rename SmartHealthCard.Test/{SmartHealthCardQRCodeFactoryTest.cs => SmartHealthCardQRCodeEncoderTest.cs} (54%) diff --git a/SmartHealthCard.QRCode/Encoder/INumericalModeDecoder.cs b/SmartHealthCard.QRCode/Encoder/INumericalModeDecoder.cs new file mode 100644 index 0000000..ea7897f --- /dev/null +++ b/SmartHealthCard.QRCode/Encoder/INumericalModeDecoder.cs @@ -0,0 +1,10 @@ +using SmartHealthCard.QRCode.Model; +using System.Collections.Generic; + +namespace SmartHealthCard.QRCode.Encoder +{ + public interface INumericalModeDecoder + { + string Decode(IEnumerable ChunkList); + } +} \ No newline at end of file diff --git a/SmartHealthCard.QRCode/Encoder/IQRCodeDecoder.cs b/SmartHealthCard.QRCode/Encoder/IQRCodeDecoder.cs new file mode 100644 index 0000000..d819c9b --- /dev/null +++ b/SmartHealthCard.QRCode/Encoder/IQRCodeDecoder.cs @@ -0,0 +1,10 @@ +using SmartHealthCard.QRCode.Model; +using System.Collections.Generic; + +namespace SmartHealthCard.QRCode.Encoder +{ + public interface IQRCodeDecoder + { + IEnumerable GetQRCodeChunkList(IEnumerable QRCodeRawDataList); + } +} \ No newline at end of file diff --git a/SmartHealthCard.QRCode/Encoder/NumericalModeDecoder.cs b/SmartHealthCard.QRCode/Encoder/NumericalModeDecoder.cs new file mode 100644 index 0000000..e6a7927 --- /dev/null +++ b/SmartHealthCard.QRCode/Encoder/NumericalModeDecoder.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using SmartHealthCard.QRCode.Model; + +namespace SmartHealthCard.QRCode.Encoder +{ + public class NumericalModeDecoder : INumericalModeDecoder + { + public string Decode(IEnumerable ChunkList) + { + StringBuilder StringBuilder = new StringBuilder(); + foreach (Chunk Chunk in ChunkList) + { + string Numeric = Chunk.NumericSegment; + foreach (string Number in Spliter(Numeric, 2)) + { + if (int.TryParse(Number, out int IntNumber)) + { + StringBuilder.Append(Convert.ToChar(IntNumber + 45)); + } + } + } + return StringBuilder.ToString(); + } + + private IEnumerable Spliter(string str, int chunkSize) + { + return Enumerable.Range(0, str.Length / chunkSize) + .Select(i => str.Substring(i * chunkSize, chunkSize)); + } + } +} diff --git a/SmartHealthCard.QRCode/Encoder/QRCodeDecoder.cs b/SmartHealthCard.QRCode/Encoder/QRCodeDecoder.cs new file mode 100644 index 0000000..e2e7cdf --- /dev/null +++ b/SmartHealthCard.QRCode/Encoder/QRCodeDecoder.cs @@ -0,0 +1,41 @@ +using Net.Codecrete.QrCodeGenerator; +using SmartHealthCard.QRCode.Exceptions; +using SmartHealthCard.QRCode.Model; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartHealthCard.QRCode.Encoder +{ + public class QRCodeDecoder : IQRCodeDecoder + { + public IEnumerable GetQRCodeChunkList(IEnumerable QRCodeRawDataList) + { + //shc:/2/3/56762909524320603460292437404460 + //shc:/56762909524320603460292437404460 + List ChunkList = new List(); + Chunk? Chunk = null; + foreach (string QRCodeRawData in QRCodeRawDataList) + { + string[] Split = QRCodeRawData.Split('/'); + if (Split.Length == 2) + { + Chunk = new Chunk($"{Split[0]}/", Split[1]); + } + else if (Split.Length == 4) + { + Chunk = new Chunk($"{Split[0]}/{Split[1]}/{Split[2]}/", Split[3]); + } + else + { + throw new QRCodeChunkFormatException($"The raw QR Code data was incorrectly formated, found {Split.Length} chunks where only 2 or 4 are allowed."); + } + ChunkList.Add(Chunk); + } + return ChunkList; + } + } +} diff --git a/SmartHealthCard.QRCode/Exceptions/QRCodeChunkFormatException.cs b/SmartHealthCard.QRCode/Exceptions/QRCodeChunkFormatException.cs new file mode 100644 index 0000000..b99ab30 --- /dev/null +++ b/SmartHealthCard.QRCode/Exceptions/QRCodeChunkFormatException.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartHealthCard.QRCode.Exceptions +{ + public class QRCodeChunkFormatException : FormatException + { + public QRCodeChunkFormatException(string message) : base(message) + { + } + } +} diff --git a/SmartHealthCard.QRCode/SmartHealthCard.QRCode.csproj b/SmartHealthCard.QRCode/SmartHealthCard.QRCode.csproj index 8e8c7d0..737681a 100644 --- a/SmartHealthCard.QRCode/SmartHealthCard.QRCode.csproj +++ b/SmartHealthCard.QRCode/SmartHealthCard.QRCode.csproj @@ -3,7 +3,7 @@ net5.0 enable - 0.1.0-alpha.1 + 0.1.0-alpha.2 Angus Millar FHIR SMART Health Card JWS token QR Code encoder libaray PyroHealth @@ -12,7 +12,7 @@ https://github.com/angusmillar/SmartHealthCard git SmartHealthCard JWS JWT FHIR covid19 immunization laboratory VerifiableCredential - This is the first alpha.1 release of the library for alpha development use + Added support to decode the raw QR Code data to a SMART Health Card JWS token true diff --git a/SmartHealthCard.QRCode/SmartHealthCardQRCodeDecoder.cs b/SmartHealthCard.QRCode/SmartHealthCardQRCodeDecoder.cs new file mode 100644 index 0000000..48534d3 --- /dev/null +++ b/SmartHealthCard.QRCode/SmartHealthCardQRCodeDecoder.cs @@ -0,0 +1,53 @@ +using SmartHealthCard.QRCode.Encoder; +using SmartHealthCard.QRCode.Model; +using System.Collections.Generic; + +namespace SmartHealthCard.QRCode +{ + /// + /// A decoder to decode from the raw QR Code data to a SMART Health Card JWS token + /// Sorry but this does not accept a QR image directly, it requires a scanner to + /// have gotten the raw QR Code data from the QR Code image + /// + public class SmartHealthCardQRCodeDecoder + { + private readonly INumericalModeDecoder NumericalModeDecoder; + private readonly IQRCodeDecoder QRCodeDecoder; + + /// + /// Default Constructor + /// + public SmartHealthCardQRCodeDecoder() + { + this.NumericalModeDecoder = new NumericalModeDecoder(); + this.QRCodeDecoder = new QRCodeDecoder(); + } + + /// + /// Provide any implementation of the following interfaces to override their default implementation + /// + /// Provides an implementation of the Numerical decoder used for SMART Health Card QR Codes, see: https://smarthealth.cards/#encoding-chunks-as-qr-codes + /// Provides an implementation of the chuck decoder used for SMART Health Card QR Codes, see: https://smarthealth.cards/#encoding-chunks-as-qr-codes + public SmartHealthCardQRCodeDecoder( + INumericalModeDecoder? NumericalModeDecoder = null, + IQRCodeDecoder? QRCodeDecoder = null) + { + this.NumericalModeDecoder = NumericalModeDecoder ?? new NumericalModeDecoder(); + this.QRCodeDecoder = QRCodeDecoder ?? new QRCodeDecoder(); + } + + /// + /// Provided SMART Health Card QR Code raw data and it will return a SMART Health Card JWS Token + /// + /// + /// + 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; + } + } +} diff --git a/SmartHealthCard.QRCode/SmartHealthCardQRCodeEncoder.cs b/SmartHealthCard.QRCode/SmartHealthCardQRCodeEncoder.cs index 49ca264..c7d0357 100644 --- a/SmartHealthCard.QRCode/SmartHealthCardQRCodeEncoder.cs +++ b/SmartHealthCard.QRCode/SmartHealthCardQRCodeEncoder.cs @@ -20,7 +20,7 @@ public class SmartHealthCardQRCodeEncoder /// - /// An encoder to generate SMART Health Card QR Code Images or there raw payloads + /// Default Constructor /// public SmartHealthCardQRCodeEncoder() : this(new QRCodeEncoderSettings()) @@ -83,5 +83,6 @@ public List GetQRCodeRawDataList(string SmartHealthCardJWSToken) Chunk[] ChunkArray = SmartHealthCardJwsChunker.Chunk(SmartHealthCardJWSToken); return QRCodeEncoder.GetQRCodeRawDataList(ChunkArray); } + } } diff --git a/SmartHealthCard.Test/SmartHealthCardQRCodeFactoryTest.cs b/SmartHealthCard.Test/SmartHealthCardQRCodeEncoderTest.cs similarity index 54% rename from SmartHealthCard.Test/SmartHealthCardQRCodeFactoryTest.cs rename to SmartHealthCard.Test/SmartHealthCardQRCodeEncoderTest.cs index 48e0018..d6ce72d 100644 --- a/SmartHealthCard.Test/SmartHealthCardQRCodeFactoryTest.cs +++ b/SmartHealthCard.Test/SmartHealthCardQRCodeEncoderTest.cs @@ -17,10 +17,10 @@ namespace SmartHealthCard.Test { - public class SmartHealthCardQRCodeFactoryTest + public class SmartHealthCardQRCodeEncoderTest { [Fact] - public async void Create_QRCode() + public async void Encode_QRCode() { //### Prepare ###################################################### @@ -77,5 +77,59 @@ public async void Create_QRCode() } + [Fact] + 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); + + //The Version of FHIR in use + string FhirVersion = "4.0.1"; + + //Get FHIR bundle + Bundle FhirBundleResource = FhirDataSupport.GetCovid19FhirBundleExample1(); + 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"); + + //When the Smart Health Card became valid, the from date. + DateTimeOffset IssuanceDateTimeOffset = DateTimeOffset.Now.AddMinutes(-1); + + //The Uri for the type of VerifiableCredentials + // Uri VerifiableCredentialType = new Uri("https://smarthealth.cards#covid19"); + List VerifiableCredentialTypeList = new List() { VerifiableCredentialType.Covid19 }; + + //Create the SmartHealthCardModel + SmartHealthCardModel SmartHealthCardToEncode = new SmartHealthCardModel(Issuer, IssuanceDateTimeOffset, + new VerifiableCredential(VerifiableCredentialTypeList, + new CredentialSubject(FhirVersion, FhirBundleJson))); + + //Instantiate the SmartHealthCard Encoder + SmartHealthCardEncoder SmartHealthCardEncoder = new SmartHealthCardEncoder(); + X509Certificate2[] CertArray = new X509Certificate2[] { Certificate }; + + //### Act ########################################################## + + //Get the Smart Health Card retrieve Token + string SmartHealthCardJwsToken = await SmartHealthCardEncoder.GetTokenAsync(Certificate, SmartHealthCardToEncode); + + //Create list of QR Codes + SmartHealthCardQRCodeEncoder SmartHealthCardQRCodeEncoder = new SmartHealthCardQRCodeEncoder(); + List QRCodeRawDataList = SmartHealthCardQRCodeEncoder.GetQRCodeRawDataList(SmartHealthCardJwsToken); + + + SmartHealthCardQRCodeDecoder SmartHealthCardQRCodeDecoder = new SmartHealthCardQRCodeDecoder(); + string JWS = SmartHealthCardQRCodeDecoder.GetToken(QRCodeRawDataList); + + //### Assert ####################################################### + + Assert.True(!string.IsNullOrWhiteSpace(JWS)); + Assert.Equal(3, JWS.Split('.').Length); + + } + } }