From fe02f53ebb86e3c31aea453a48f18cb3602b5774 Mon Sep 17 00:00:00 2001 From: MattiLE <48024891+MattiLE@users.noreply.github.com> Date: Thu, 16 May 2024 13:20:17 +0200 Subject: [PATCH] Init RSA PSS SignatureAlgorithm Implementation (#867) #### Summary As far as I know, the existing RSACryptoServiceProvider implementation does not offer the option of specifying the PSS padding. My online research has also shown that this is not possible either. Therefore, I would like to add my own implementation of the "SignatureAlgorithm" interface based on the "System.Security.Cryptography.RSA" class. For my use case, only a function that signs the data is sufficient. However, I think that the interface implementation is more similar to the styleguid. #### Work Item(s) Fixes #866 Fixes [AB#524188](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/524188) --- .../App/Cryptography Management/app.json | 2 +- .../src/CryptographyManagement.Codeunit.al | 13 + .../CryptographyManagementImpl.Codeunit.al | 24 ++ .../src/RSA.Codeunit.al | 89 ++++++ .../src/RSAImpl.Codeunit.al | 250 ++++++++++++++++ .../src/SignatureAlgorithm.Enum.al | 12 + .../App/Cryptography Management/src/dotnet.al | 1 + .../Test/Cryptography Management/app.json | 2 +- .../src/RSATest.Codeunit.al | 283 ++++++++++++++++++ 9 files changed, 674 insertions(+), 2 deletions(-) create mode 100644 src/System Application/App/Cryptography Management/src/RSA.Codeunit.al create mode 100644 src/System Application/App/Cryptography Management/src/RSAImpl.Codeunit.al create mode 100644 src/System Application/Test/Cryptography Management/src/RSATest.Codeunit.al diff --git a/src/System Application/App/Cryptography Management/app.json b/src/System Application/App/Cryptography Management/app.json index 2a85ae9f4d..c57f4b1f7a 100644 --- a/src/System Application/App/Cryptography Management/app.json +++ b/src/System Application/App/Cryptography Management/app.json @@ -85,7 +85,7 @@ }, { "from": 1473, - "to": 1474 + "to": 1476 } ], "target": "OnPrem", diff --git a/src/System Application/App/Cryptography Management/src/CryptographyManagement.Codeunit.al b/src/System Application/App/Cryptography Management/src/CryptographyManagement.Codeunit.al index 7dc6e77ffd..27903a6f1d 100644 --- a/src/System Application/App/Cryptography Management/src/CryptographyManagement.Codeunit.al +++ b/src/System Application/App/Cryptography Management/src/CryptographyManagement.Codeunit.al @@ -330,6 +330,19 @@ codeunit 1266 "Cryptography Management" CryptographyManagementImpl.SignData(DataInStream, SignatureKey, HashAlgorithm, SignatureOutStream); end; + /// + /// Computes the hash value of the specified string and signs it. + /// + /// Input string for signing. + /// The private key to use in the hash algorithm. + /// The available hash algorithms are MD5, SHA1, SHA256, SHA384, and SHA512. + /// The padding mode to use for the RSA signature. + /// The stream to write the signature for the specified string. + procedure SignData(InputString: Text; XmlString: SecretText; HashAlgorithm: Enum "Hash Algorithm"; RSASignaturePadding: Enum "RSA Signature Padding"; SignatureOutStream: OutStream) + begin + CryptographyManagementImpl.SignData(InputString, XmlString, HashAlgorithm, RSASignaturePadding, SignatureOutStream); + end; + /// /// Verifies that a digital signature is valid. /// diff --git a/src/System Application/App/Cryptography Management/src/CryptographyManagementImpl.Codeunit.al b/src/System Application/App/Cryptography Management/src/CryptographyManagementImpl.Codeunit.al index 7e0562286c..215100a851 100644 --- a/src/System Application/App/Cryptography Management/src/CryptographyManagementImpl.Codeunit.al +++ b/src/System Application/App/Cryptography Management/src/CryptographyManagementImpl.Codeunit.al @@ -406,6 +406,30 @@ codeunit 1279 "Cryptography Management Impl." SignData(DataInStream, SignatureKey.ToXmlString(), HashAlgorithm, SignatureOutStream); end; + procedure SignData(InputString: Text; XmlString: SecretText; HashAlgorithm: Enum "Hash Algorithm"; RSASignaturePadding: Enum "RSA Signature Padding"; SignatureOutStream: OutStream) + var + TempBlob: Codeunit "Temp Blob"; + DataOutStream: OutStream; + DataInStream: InStream; + begin + if InputString = '' then + exit; + TempBlob.CreateOutStream(DataOutStream, TextEncoding::UTF8); + TempBlob.CreateInStream(DataInStream, TextEncoding::UTF8); + DataOutStream.WriteText(InputString); + SignData(DataInStream, XmlString, HashAlgorithm, SignatureOutStream); + end; + + procedure SignData(DataInStream: InStream; XmlString: SecretText; HashAlgorithm: Enum "Hash Algorithm"; RSASignaturePadding: Enum "RSA Signature Padding"; SignatureOutStream: OutStream) + var + RSAImpl: Codeunit "RSA Impl."; + begin + if DataInStream.EOS() then + exit; + + RSAImpl.SignData(XmlString, DataInStream, HashAlgorithm, RSASignaturePadding, SignatureOutStream); + end; + procedure VerifyData(InputString: Text; XmlString: SecretText; HashAlgorithm: Enum "Hash Algorithm"; SignatureInStream: InStream): Boolean var TempBlob: Codeunit "Temp Blob"; diff --git a/src/System Application/App/Cryptography Management/src/RSA.Codeunit.al b/src/System Application/App/Cryptography Management/src/RSA.Codeunit.al new file mode 100644 index 0000000000..6e3c1d32a8 --- /dev/null +++ b/src/System Application/App/Cryptography Management/src/RSA.Codeunit.al @@ -0,0 +1,89 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.Security.Encryption; + +/// +/// Performs asymmetric encryption and digital signature using the implementation of the RSA class. +/// +codeunit 1475 "RSA" +{ + Access = Public; + InherentEntitlements = X; + InherentPermissions = X; + + var + RSAImpl: Codeunit "RSA Impl."; + + /// + /// Initializes a new instance of RSA with the specified key size. + /// + /// The size of the key in bits. + procedure InitializeRSA(KeySize: Integer) + begin + RSAImpl.InitializeRSA(KeySize); + end; + + /// + /// Creates and returns an XML string containing the key of the current RSA object. + /// + /// true to include a public and private RSA key; false to include only the public key. + /// An XML string containing the key of the current RSA object. + procedure ToSecretXmlString(IncludePrivateParameters: Boolean): SecretText + begin + exit(RSAImpl.ToSecretXmlString(IncludePrivateParameters)); + end; + + /// + /// Computes the hash value of the specified data and signs it. + /// + /// The XML string containing RSA key information. + /// The input stream to hash and sign. + /// The hash algorithm to use to create the hash value. + /// The padding mode to use for the RSA signature. + /// The RSA signature stream for the specified data. + procedure SignData(XmlString: SecretText; DataInStream: InStream; HashAlgorithm: Enum "Hash Algorithm"; RSASignaturePadding: Enum "RSA Signature Padding"; SignatureOutStream: OutStream) + begin + RSAImpl.SignData(XmlString, DataInStream, HashAlgorithm, RSASignaturePadding, SignatureOutStream); + end; + + /// + /// Verifies that a digital signature is valid by determining the hash value in the signature using the provided public key and comparing it to the hash value of the provided data. + /// + /// The XML string containing RSA key information. + /// The input stream of data that was signed. + /// The name of the hash algorithm used to create the hash value of the data. + /// The padding mode to use for the RSA signature. + /// The stream of signature data to be verified. + /// True if the signature is valid; otherwise, false. + procedure VerifyData(XmlString: SecretText; DataInStream: InStream; HashAlgorithm: Enum "Hash Algorithm"; RSASignaturePadding: Enum "RSA Signature Padding"; SignatureInStream: InStream): Boolean + begin + exit(RSAImpl.VerifyData(XmlString, DataInStream, HashAlgorithm, RSASignaturePadding, SignatureInStream)); + end; + + /// + /// Encrypts the specified text with the RSA algorithm. + /// + /// The XML string containing RSA key information. + /// The input stream to encrypt. + /// True to perform RSA encryption using OAEP padding; otherwise, false to use PKCS#1 padding. + /// The RSA encryption stream for the specified text. + procedure Encrypt(XmlString: SecretText; PlainTextInStream: InStream; OaepPadding: Boolean; EncryptedTextOutStream: OutStream) + begin + RSAImpl.Encrypt(XmlString, PlainTextInStream, OaepPadding, EncryptedTextOutStream); + end; + + /// + /// Decrypts the specified text that was previously encrypted with the RSA algorithm. + /// + /// The XML string containing RSA key information. + /// The input stream to decrypt. + /// true to perform RSA encryption using OAEP padding; otherwise, false to use PKCS#1 padding. + /// The RSA decryption stream for the specified text. + procedure Decrypt(XmlString: SecretText; EncryptedTextInStream: InStream; OaepPadding: Boolean; DecryptedTextOutStream: OutStream) + begin + RSAImpl.Decrypt(XmlString, EncryptedTextInStream, OaepPadding, DecryptedTextOutStream); + end; +} \ No newline at end of file diff --git a/src/System Application/App/Cryptography Management/src/RSAImpl.Codeunit.al b/src/System Application/App/Cryptography Management/src/RSAImpl.Codeunit.al new file mode 100644 index 0000000000..4449b0330c --- /dev/null +++ b/src/System Application/App/Cryptography Management/src/RSAImpl.Codeunit.al @@ -0,0 +1,250 @@ +namespace System.Security.Encryption; + +using System; +#if not CLEAN24 +#pragma warning disable AL0432 +codeunit 1476 "RSA Impl." implements SignatureAlgorithm, "Signature Algorithm v2" +#pragma warning restore AL0432 +#else +codeunit 1476 "RSA Impl." implements "Signature Algorithm v2" +#endif +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + + var + DotNetRSA: DotNet RSA; + + procedure InitializeRSA(KeySize: Integer) + begin + DotNetRSA := DotNetRSA.Create(KeySize); + end; + + procedure GetInstance(var DotNetAsymmetricAlgorithm: DotNet AsymmetricAlgorithm) + begin + DotNetAsymmetricAlgorithm := DotNetRSA; + end; + + #region SignData + procedure SignData(XmlString: SecretText; DataInStream: InStream; HashAlgorithm: Enum "Hash Algorithm"; SignatureOutStream: OutStream) + begin + FromSecretXmlString(XmlString); + SignData(DataInStream, HashAlgorithm, Enum::"RSA Signature Padding"::Pss, SignatureOutStream); + end; + + procedure SignData(XmlString: SecretText; DataInStream: InStream; HashAlgorithm: Enum "Hash Algorithm"; RSASignaturePadding: Enum "RSA Signature Padding"; SignatureOutStream: OutStream) + begin + FromSecretXmlString(XmlString); + SignData(DataInStream, HashAlgorithm, RSASignaturePadding, SignatureOutStream); + end; + + procedure SignData(DataInStream: InStream; HashAlgorithm: Enum "Hash Algorithm"; SignatureOutStream: OutStream) + begin + SignData(DataInStream, HashAlgorithm, Enum::"RSA Signature Padding"::Pss, SignatureOutStream); + end; + + procedure SignData(DataInStream: InStream; HashAlgorithm: Enum "Hash Algorithm"; RSASignaturePadding: Enum "RSA Signature Padding"; SignatureOutStream: OutStream) + var + Bytes: DotNet Array; + Signature: DotNet Array; + begin + if DataInStream.EOS() then + exit; + InStreamToArray(DataInStream, Bytes); + SignData(Bytes, HashAlgorithm, RSASignaturePadding, Signature); + ArrayToOutStream(Signature, SignatureOutStream); + end; + + local procedure SignData(Bytes: DotNet Array; HashAlgorithm: Enum "Hash Algorithm"; RSASignaturePadding: Enum "RSA Signature Padding"; var Signature: DotNet Array) + begin + if Bytes.Length() = 0 then + exit; + TrySignData(Bytes, HashAlgorithm, RSASignaturePadding, Signature); + end; + + [TryFunction] + local procedure TrySignData(Bytes: DotNet Array; HashAlgorithm: Enum "Hash Algorithm"; RSASignaturePadding: Enum "RSA Signature Padding"; var Signature: DotNet Array) + var + DotNetHashAlgorithmName: DotNet HashAlgorithmName; + DotNetRSASignaturePadding: DotNet RSASignaturePadding; + begin + HashAlgorithmEnumToDotNet(HashAlgorithm, DotNetHashAlgorithmName); + RSASignaturePaddingToDotNet(RSASignaturePadding, DotNetRSASignaturePadding); + Signature := DotNetRSA.SignData(Bytes, DotNetHashAlgorithmName, DotNetRSASignaturePadding); + end; + #endregion + + #region VerifyData + procedure VerifyData(XmlString: SecretText; DataInStream: InStream; HashAlgorithm: Enum "Hash Algorithm"; SignatureInStream: InStream): Boolean + begin + FromSecretXmlString(XmlString); + exit(VerifyData(DataInStream, HashAlgorithm, Enum::"RSA Signature Padding"::Pss, SignatureInStream)); + end; + + procedure VerifyData(XmlString: SecretText; DataInStream: InStream; HashAlgorithm: Enum "Hash Algorithm"; RSASignaturePadding: Enum "RSA Signature Padding"; SignatureInStream: InStream): Boolean + begin + FromSecretXmlString(XmlString); + exit(VerifyData(DataInStream, HashAlgorithm, RSASignaturePadding, SignatureInStream)); + end; + + procedure VerifyData(DataInStream: InStream; HashAlgorithm: Enum "Hash Algorithm"; SignatureInStream: InStream): Boolean + begin + VerifyData(DataInStream, HashAlgorithm, Enum::"RSA Signature Padding"::Pss, SignatureInStream); + end; + + procedure VerifyData(DataInStream: InStream; HashAlgorithm: Enum "Hash Algorithm"; RSASignaturePadding: Enum "RSA Signature Padding"; SignatureInStream: InStream): Boolean + var + Bytes: DotNet Array; + Signature: DotNet Array; + begin + if DataInStream.EOS() or SignatureInStream.EOS() then + exit(false); + InStreamToArray(DataInStream, Bytes); + InStreamToArray(SignatureInStream, Signature); + exit(VerifyData(Bytes, HashAlgorithm, RSASignaturePadding, Signature)); + end; + + local procedure VerifyData(Bytes: DotNet Array; HashAlgorithm: Enum "Hash Algorithm"; RSASignaturePadding: Enum "RSA Signature Padding"; Signature: DotNet Array): Boolean + var + Verified: Boolean; + begin + if Bytes.Length() = 0 then + exit(false); + Verified := TryVerifyData(Bytes, HashAlgorithm, RSASignaturePadding, Signature); + if not Verified and (GetLastErrorText() <> '') then + Error(GetLastErrorText()); + exit(Verified); + end; + + [TryFunction] + local procedure TryVerifyData(Bytes: DotNet Array; HashAlgorithm: Enum "Hash Algorithm"; RSASignaturePadding: Enum "RSA Signature Padding"; Signature: DotNet Array) + var + DotNetHashAlgorithmName: DotNet HashAlgorithmName; + DotNetRSASignaturePadding: DotNet RSASignaturePadding; + begin + HashAlgorithmEnumToDotNet(HashAlgorithm, DotNetHashAlgorithmName); + RSASignaturePaddingToDotNet(RSASignaturePadding, DotNetRSASignaturePadding); + if not DotNetRSA.VerifyData(Bytes, Signature, DotNetHashAlgorithmName, DotNetRSASignaturePadding) then + Error(''); + end; + #endregion + + #region Encryption & Decryption + [NonDebuggable] + procedure Encrypt(XmlString: SecretText; PlainTextInStream: InStream; OaepPadding: Boolean; EncryptedTextOutStream: OutStream) + var + PlainTextBytes: DotNet Array; + EncryptedTextBytes: DotNet Array; + DotNetRSAEncryptionPadding: DotNet RSAEncryptionPadding; + begin + FromSecretXmlString(XmlString); + InStreamToArray(PlainTextInStream, PlainTextBytes); + + if OaepPadding then + DotNetRSAEncryptionPadding := DotNetRSAEncryptionPadding.OaepSHA256 + else + DotNetRSAEncryptionPadding := DotNetRSAEncryptionPadding.Pkcs1; + + EncryptedTextBytes := DotNetRSA.Encrypt(PlainTextBytes, DotNetRSAEncryptionPadding); + ArrayToOutStream(EncryptedTextBytes, EncryptedTextOutStream); + end; + + [NonDebuggable] + procedure Decrypt(XmlString: SecretText; EncryptedTextInStream: InStream; OaepPadding: Boolean; DecryptedTextOutStream: OutStream) + var + EncryptedTextBytes: DotNet Array; + DecryptedTextBytes: DotNet Array; + DotNetRSAEncryptionPadding: DotNet RSAEncryptionPadding; + begin + FromSecretXmlString(XmlString); + InStreamToArray(EncryptedTextInStream, EncryptedTextBytes); + if OaepPadding then + DotNetRSAEncryptionPadding := DotNetRSAEncryptionPadding.OaepSHA256 + else + DotNetRSAEncryptionPadding := DotNetRSAEncryptionPadding.Pkcs1; + DecryptedTextBytes := DotNetRSA.Decrypt(EncryptedTextBytes, DotNetRSAEncryptionPadding); + ArrayToOutStream(DecryptedTextBytes, DecryptedTextOutStream); + end; + #endregion + + + #region XmlString +#if not CLEAN24 + [NonDebuggable] + [Obsolete('Replaced by ToSecretXmlString with SecretText data type for XmlString.', '25.0')] + procedure ToXmlString(IncludePrivateParameters: Boolean): Text + begin + exit(DotNetRSA.ToXmlString(IncludePrivateParameters)); + end; + + [NonDebuggable] + [Obsolete('Replaced by FromSecretXmlString with SecretText data type for XmlString.', '25.0')] + procedure FromXmlString(XmlString: Text) + begin + RSA(); + DotNetRSA.FromXmlString(XmlString); + end; +#endif + procedure ToSecretXmlString(IncludePrivateParameters: Boolean): SecretText + begin + exit(DotNetRSA.ToXmlString(IncludePrivateParameters)); + end; + + [NonDebuggable] + procedure FromSecretXmlString(XmlString: SecretText) + begin + RSA(); + DotNetRSA.FromXmlString(XmlString.Unwrap()); + end; + #endregion + + local procedure RSA() + begin + DotNetRSA := DotNetRSA.Create(2048); + end; + + local procedure ArrayToOutStream(Bytes: DotNet Array; OutputOutStream: OutStream) + var + DotNetMemoryStream: DotNet MemoryStream; + begin + DotNetMemoryStream := DotNetMemoryStream.MemoryStream(Bytes); + CopyStream(OutputOutStream, DotNetMemoryStream); + end; + + local procedure InStreamToArray(InputInStream: InStream; var Bytes: DotNet Array) + var + DotNetMemoryStream: DotNet MemoryStream; + begin + DotNetMemoryStream := DotNetMemoryStream.MemoryStream(); + CopyStream(DotNetMemoryStream, InputInStream); + Bytes := DotNetMemoryStream.ToArray(); + end; + + local procedure HashAlgorithmEnumToDotNet(HashAlgorithm: Enum "Hash Algorithm"; var DotNetHashAlgorithmName: DotNet HashAlgorithmName) + begin + case + HashAlgorithm of + HashAlgorithm::MD5: + DotNetHashAlgorithmName := DotNetHashAlgorithmName.MD5; + HashAlgorithm::SHA1: + DotNetHashAlgorithmName := DotNetHashAlgorithmName.SHA1; + HashAlgorithm::SHA256: + DotNetHashAlgorithmName := DotNetHashAlgorithmName.SHA256; + HashAlgorithm::SHA384: + DotNetHashAlgorithmName := DotNetHashAlgorithmName.SHA384; + HashAlgorithm::SHA512: + DotNetHashAlgorithmName := DotNetHashAlgorithmName.SHA512; + end; + end; + + local procedure RSASignaturePaddingToDotNet(RSASignaturePadding: Enum "RSA Signature Padding"; var DotNetRSASignaturePadding: DotNet RSASignaturePadding) + begin + case RSASignaturePadding of + RSASignaturePadding::Pkcs1: + DotNetRSASignaturePadding := DotNetRSASignaturePadding.Pkcs1; + RSASignaturePadding::Pss: + DotNetRSASignaturePadding := DotNetRSASignaturePadding.Pss; + end; + end; +} \ No newline at end of file diff --git a/src/System Application/App/Cryptography Management/src/SignatureAlgorithm.Enum.al b/src/System Application/App/Cryptography Management/src/SignatureAlgorithm.Enum.al index b9ad3af6cb..26060233a5 100644 --- a/src/System Application/App/Cryptography Management/src/SignatureAlgorithm.Enum.al +++ b/src/System Application/App/Cryptography Management/src/SignatureAlgorithm.Enum.al @@ -42,6 +42,18 @@ enum 1446 SignatureAlgorithm implements "Signature Algorithm v2" "Signature Algorithm v2" = "DSACryptoServiceProvider Impl."; #else Implementation = "Signature Algorithm v2" = "DSACryptoServiceProvider Impl."; +#endif + } + /// + /// Specifies the RSASSA-PSS algorithm implemented by RSA + /// + value(2; "RSASSA-PSS") + { +#if not CLEAN24 + Implementation = SignatureAlgorithm = "RSA Impl.", + "Signature Algorithm v2" = "RSA Impl."; +#else + Implementation = "Signature Algorithm v2" = "RSA Impl."; #endif } } \ No newline at end of file diff --git a/src/System Application/App/Cryptography Management/src/dotnet.al b/src/System Application/App/Cryptography Management/src/dotnet.al index 461e8df9b9..17b0cc2735 100644 --- a/src/System Application/App/Cryptography Management/src/dotnet.al +++ b/src/System Application/App/Cryptography Management/src/dotnet.al @@ -38,6 +38,7 @@ dotnet type("System.Security.Cryptography.SymmetricAlgorithm"; "Cryptography.SymmetricAlgorithm") { } type("System.Security.Cryptography.DESCryptoServiceProvider"; "Cryptography.DESCryptoServiceProvider") { } type("System.Security.Cryptography.RSASignaturePadding"; RSASignaturePadding) { } + type("System.Security.Cryptography.RSAEncryptionPadding"; RSAEncryptionPadding) { } type("System.Security.Cryptography.TripleDESCryptoServiceProvider"; "Cryptography.TripleDESCryptoServiceProvider") { } type("System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension"; X509BasicConstraintsExtension) { } type("System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension"; X509EnhancedKeyUsageExtension) { } diff --git a/src/System Application/Test/Cryptography Management/app.json b/src/System Application/Test/Cryptography Management/app.json index ea59b1703a..68568c8ce4 100644 --- a/src/System Application/Test/Cryptography Management/app.json +++ b/src/System Application/Test/Cryptography Management/app.json @@ -67,7 +67,7 @@ }, { "from": 132611, - "to": 132615 + "to": 132617 } ] } diff --git a/src/System Application/Test/Cryptography Management/src/RSATest.Codeunit.al b/src/System Application/Test/Cryptography Management/src/RSATest.Codeunit.al new file mode 100644 index 0000000000..3878668c68 --- /dev/null +++ b/src/System Application/Test/Cryptography Management/src/RSATest.Codeunit.al @@ -0,0 +1,283 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.Test.Security.Encryption; + +using System.Security.Encryption; +using System.Text; +using System.Utilities; +using System.TestLibraries.Utilities; + +codeunit 132617 "RSA Test" +{ + Subtype = Test; + + var + LibraryAssert: Codeunit "Library Assert"; + Base64Convert: Codeunit "Base64 Convert"; + Any: Codeunit Any; + IsInitialized: Boolean; + PrivateKeyXmlStringSecret: SecretText; + + local procedure Initialize() + var + RSA: Codeunit RSA; + begin + if IsInitialized then + exit; + RSA.InitializeRSA(2048); + PrivateKeyXmlStringSecret := RSA.ToSecretXmlString(true); + IsInitialized := true; + end; + + [Test] + procedure InitializeKeys() + var + RSA: Codeunit RSA; + KeyXml: XmlDocument; + Root: XmlElement; + Node: XmlNode; + KeyXmlText: SecretText; + begin + RSA.InitializeRSA(2048); + KeyXmlText := RSA.ToSecretXmlString(true); + + LibraryAssert.IsTrue(XmlDocument.ReadFrom(GetXmlString(KeyXmlText), KeyXml), 'RSA key is not valid xml data.'); + LibraryAssert.IsTrue(KeyXml.GetRoot(Root), 'Could not get Root element of key.'); + + LibraryAssert.IsTrue(Root.SelectSingleNode('Modulus', Node), 'Could not find in key.'); + LibraryAssert.IsTrue(Root.SelectSingleNode('DQ', Node), 'Could not find in key.'); + end; + + [Test] + procedure TestSignDataAndVerifyDataWithMD5AndPSS() + begin + LibraryAssert.IsTrue(SignAndVerifyData(enum::"Hash Algorithm"::MD5, enum::"RSA Signature Padding"::Pss), 'Failed to verify signed data'); + end; + + [Test] + procedure TestSignDataAndVerifyDataWithMD5AndPkcs1() + begin + LibraryAssert.IsTrue(SignAndVerifyData(enum::"Hash Algorithm"::MD5, enum::"RSA Signature Padding"::Pkcs1), 'Failed to verify signed data'); + end; + + [Test] + procedure TestSignDataAndVerifyDataWithSHA1AndPSS() + begin + LibraryAssert.IsTrue(SignAndVerifyData(enum::"Hash Algorithm"::SHA1, enum::"RSA Signature Padding"::Pss), 'Failed to verify signed data'); + end; + + [Test] + procedure TestSignDataAndVerifyDataWithSHA1AndPkcs1() + begin + LibraryAssert.IsTrue(SignAndVerifyData(enum::"Hash Algorithm"::SHA1, enum::"RSA Signature Padding"::Pkcs1), 'Failed to verify signed data'); + end; + + [Test] + procedure TestSignDataAndVerifyDataWithSHA256AndPSS() + begin + LibraryAssert.IsTrue(SignAndVerifyData(enum::"Hash Algorithm"::SHA256, enum::"RSA Signature Padding"::Pss), 'Failed to verify signed data'); + end; + + [Test] + procedure TestSignDataAndVerifyDataWithSHA256AndPkcs1() + begin + LibraryAssert.IsTrue(SignAndVerifyData(enum::"Hash Algorithm"::SHA256, enum::"RSA Signature Padding"::Pkcs1), 'Failed to verify signed data'); + end; + + [Test] + procedure TestSignDataAndVerifyDataWithSHA384AndPSS() + begin + LibraryAssert.IsTrue(SignAndVerifyData(enum::"Hash Algorithm"::SHA384, enum::"RSA Signature Padding"::Pss), 'Failed to verify signed data'); + end; + + [Test] + procedure TestSignDataAndVerifyDataWithSHA384AndPkcs1() + begin + LibraryAssert.IsTrue(SignAndVerifyData(enum::"Hash Algorithm"::SHA384, enum::"RSA Signature Padding"::Pkcs1), 'Failed to verify signed data'); + end; + + [Test] + procedure TestSignDataAndVerifyDataWithSHA512AndPSS() + begin + LibraryAssert.IsTrue(SignAndVerifyData(enum::"Hash Algorithm"::SHA512, enum::"RSA Signature Padding"::Pss), 'Failed to verify signed data'); + end; + + [Test] + procedure TestSignDataAndVerifyDataWithSHA512AndPkcs1() + begin + LibraryAssert.IsTrue(SignAndVerifyData(enum::"Hash Algorithm"::SHA512, enum::"RSA Signature Padding"::Pkcs1), 'Failed to verify signed data'); + end; + + local procedure SignAndVerifyData(HashAlgorithm: Enum "Hash Algorithm"; RSASignaturePadding: Enum "RSA Signature Padding"): Boolean + var + TempBlob, TempBlobStringToSign : Codeunit "Temp Blob"; + RSA: Codeunit RSA; + SignatureOutStream, StringToSignOutStream : OutStream; + SignatureInStream, StringToSignInStream : InStream; + XMLString: SecretText; + PlainText: Text; + begin + // [SCENARIO] Sign random text and verify the signed signature + TempBlob.CreateInStream(SignatureInStream); + TempBlob.CreateOutStream(SignatureOutStream); + + TempBlobStringToSign.CreateOutStream(StringToSignOutStream); + PlainText := SaveRandomTextToOutStream(StringToSignOutStream); + TempBlobStringToSign.CreateInStream(StringToSignInStream); + + RSA.InitializeRSA(2048); + XMLString := RSA.ToSecretXmlString(true); + RSA.SignData(XMLString, StringToSignInStream, HashAlgorithm, RSASignaturePadding, SignatureOutStream); + TempBlobStringToSign.CreateInStream(StringToSignInStream); + + SignatureInStream.Position(1); + StringToSignInStream.Position(1); + exit(RSA.VerifyData(XMLString, StringToSignInStream, HashAlgorithm, RSASignaturePadding, SignatureInStream)); + end; + + + [Test] + procedure DecryptEncryptedTextWithOaepPadding() + var + RSA: Codeunit RSA; + EncryptingTempBlob: Codeunit "Temp Blob"; + EncryptedTempBlob: Codeunit "Temp Blob"; + DecryptingTempBlob: Codeunit "Temp Blob"; + EncryptingInStream: InStream; + EncryptingOutStream: OutStream; + EncryptedInStream: InStream; + EncryptedOutStream: OutStream; + DecryptedInStream: InStream; + DecryptedOutStream: OutStream; + PlainText: Text; + begin + // [SCENARIO] Verify decrypted text with OAEP padding encryption. + Initialize(); + + // [GIVEN] With RSA pair of keys, plain text and its encryption stream + EncryptingTempBlob.CreateOutStream(EncryptingOutStream); + PlainText := SaveRandomTextToOutStream(EncryptingOutStream); + EncryptingTempBlob.CreateInStream(EncryptingInStream); + EncryptedTempBlob.CreateOutStream(EncryptedOutStream); + RSA.Encrypt(PrivateKeyXmlStringSecret, EncryptingInStream, true, EncryptedOutStream); + EncryptedTempBlob.CreateInStream(EncryptedInStream); + + // [WHEN] Decrypt encrypted text stream + DecryptingTempBlob.CreateOutStream(DecryptedOutStream); + RSA.Decrypt(PrivateKeyXmlStringSecret, EncryptedInStream, true, DecryptedOutStream); + DecryptingTempBlob.CreateInStream(DecryptedInStream); + + // [THEN] Decrypted text is the same as the plain text + LibraryAssert.AreEqual(PlainText, Base64Convert.FromBase64(Base64Convert.ToBase64(DecryptedInStream)), + 'Unexpected decrypted text value.'); + end; + + [Test] + procedure DecryptEncryptedTextWithPKCS1Padding() + var + RSA: Codeunit RSA; + EncryptingTempBlob: Codeunit "Temp Blob"; + EncryptedTempBlob: Codeunit "Temp Blob"; + DecryptingTempBlob: Codeunit "Temp Blob"; + EncryptingInStream: InStream; + EncryptingOutStream: OutStream; + EncryptedInStream: InStream; + EncryptedOutStream: OutStream; + DecryptedInStream: InStream; + DecryptedOutStream: OutStream; + PlainText: Text; + begin + // [SCENARIO] Verify decrypted text with PKCS#1 padding encryption. + Initialize(); + + // [GIVEN] With RSA pair of keys, plain text and its encryption stream + EncryptingTempBlob.CreateOutStream(EncryptingOutStream); + PlainText := SaveRandomTextToOutStream(EncryptingOutStream); + EncryptingTempBlob.CreateInStream(EncryptingInStream); + EncryptedTempBlob.CreateOutStream(EncryptedOutStream); + RSA.Encrypt(PrivateKeyXmlStringSecret, EncryptingInStream, false, EncryptedOutStream); + EncryptedTempBlob.CreateInStream(EncryptedInStream); + + // [WHEN] Decrypt encrypted text stream + DecryptingTempBlob.CreateOutStream(DecryptedOutStream); + RSA.Decrypt(PrivateKeyXmlStringSecret, EncryptedInStream, false, DecryptedOutStream); + DecryptingTempBlob.CreateInStream(DecryptedInStream); + + // [THEN] Decrypted text is the same as the plain text + LibraryAssert.AreEqual(PlainText, Base64Convert.FromBase64(Base64Convert.ToBase64(DecryptedInStream)), + 'Unexpected decrypted text value.'); + end; + + [Test] + procedure DecryptWithOAEPPaddingTextEncryptedWithPKCS1Padding() + var + RSA: Codeunit RSA; + EncryptingTempBlob: Codeunit "Temp Blob"; + EncryptedTempBlob: Codeunit "Temp Blob"; + DecryptingTempBlob: Codeunit "Temp Blob"; + EncryptingInStream: InStream; + EncryptingOutStream: OutStream; + EncryptedInStream: InStream; + EncryptedOutStream: OutStream; + DecryptedOutStream: OutStream; + begin + // [SCENARIO] Decrypt text encrypted with use of PKCS#1 padding, using OAEP padding. + Initialize(); + + // [GIVEN] With RSA pair of keys, plain text and encryption stream + EncryptingTempBlob.CreateOutStream(EncryptingOutStream); + SaveRandomTextToOutStream(EncryptingOutStream); + EncryptingTempBlob.CreateInStream(EncryptingInStream); + EncryptedTempBlob.CreateOutStream(EncryptedOutStream); + RSA.Encrypt(PrivateKeyXmlStringSecret, EncryptingInStream, false, EncryptedOutStream); + EncryptedTempBlob.CreateInStream(EncryptedInStream); + + // [WHEN] Decrypt encrypted text stream using OAEP Padding + DecryptingTempBlob.CreateOutStream(DecryptedOutStream); + asserterror RSA.Decrypt(PrivateKeyXmlStringSecret, EncryptedInStream, true, DecryptedOutStream); + end; + + [Test] + procedure DecryptWithPKCS1PaddingTextEncryptedWithOAEPPadding() + var + RSA: Codeunit RSA; + EncryptingTempBlob: Codeunit "Temp Blob"; + EncryptedTempBlob: Codeunit "Temp Blob"; + DecryptingTempBlob: Codeunit "Temp Blob"; + EncryptingInStream: InStream; + EncryptingOutStream: OutStream; + EncryptedInStream: InStream; + EncryptedOutStream: OutStream; + DecryptedOutStream: OutStream; + begin + // [SCENARIO] Decrypt text encrypted with use of OAEP padding, using PKCS#1 padding. + Initialize(); + + // [GIVEN] With RSA pair of keys, plain text, padding and encryption stream + EncryptingTempBlob.CreateOutStream(EncryptingOutStream); + SaveRandomTextToOutStream(EncryptingOutStream); + EncryptingTempBlob.CreateInStream(EncryptingInStream); + EncryptedTempBlob.CreateOutStream(EncryptedOutStream); + RSA.Encrypt(PrivateKeyXmlStringSecret, EncryptingInStream, true, EncryptedOutStream); + EncryptedTempBlob.CreateInStream(EncryptedInStream); + + // [WHEN] Decrypt encrypted text stream using PKCS#1 padding. + DecryptingTempBlob.CreateOutStream(DecryptedOutStream); + asserterror RSA.Decrypt(PrivateKeyXmlStringSecret, EncryptedInStream, false, DecryptedOutStream); + end; + + local procedure SaveRandomTextToOutStream(OutStream: OutStream) PlainText: Text + begin + PlainText := Any.AlphanumericText(Any.IntegerInRange(80)); + OutStream.WriteText(PlainText); + end; + + [NonDebuggable] + local procedure GetXmlString(XmlString: SecretText): Text + begin + exit(XmlString.Unwrap()); + end; +} \ No newline at end of file