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