diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d19255d52..e12a0a6d6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,14 +25,15 @@ concurrency: # SEQUENTIAL_ENV: SEQUENTIAL_TEST_RUN jobs: - test-windows: - name: Tests on Windows + test-windows-core: + name: Tests on Windows for Core runs-on: windows-latest strategy: fail-fast: false matrix: - dotnet: ['net6.0', 'net7.0', 'net8.0', 'net9.0', 'net462', 'net471', 'net472', 'net48', 'net481'] + dotnet: ['net6.0', 'net7.0', 'net8.0', 'net9.0'] cloud_env: ['AZURE', 'GCP', 'AWS'] + target_framework: ['netstandard2.0', 'netstandard2.1'] steps: - name: Checkout code uses: actions/checkout@v4 @@ -62,31 +63,101 @@ jobs: cd Snowflake.Data.Tests dotnet restore dotnet build -f ${{ matrix.dotnet }} '-p:DefineAdditionalConstants=SF_PUBLIC_ENVIRONMENT' + env: + SF_TARGET_FRAMEWORK: ${{ matrix.target_framework }} - name: Run Tests run: | cd Snowflake.Data.Tests - dotnet-coverage collect "dotnet test --framework ${{ matrix.dotnet }} --no-build -l console;verbosity=normal" --output windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_coverage.xml --output-format cobertura --settings coverage.config + dotnet-coverage collect "dotnet test --framework ${{ matrix.dotnet }} --no-build -l console;verbosity=normal" --output windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_coverage.xml --output-format cobertura --settings coverage.config env: snowflake_cloud_env: ${{ matrix.cloud_env }} net_version: ${{ matrix.dotnet }} + SF_TARGET_FRAMEWORK: ${{ matrix.target_framework }} - name: Upload Code Coverage Report uses: actions/upload-artifact@v4 with: - name: code-coverage-report_windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }} - path: Snowflake.Data.Tests\windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_coverage.xml + name: code-coverage-report_windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }} + path: Snowflake.Data.Tests\windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_coverage.xml - name: Upload Test Performance Report uses: actions/upload-artifact@v4 with: - name: tests-performance_windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }} - path: Snowflake.Data.Tests\windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_performance.csv + name: tests-performance_windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }} + path: Snowflake.Data.Tests\windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_performance.csv - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 with: # without the token code cov may fail because of Github limits https://github.com/codecov/codecov-action/issues/557 token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} fail_ci_if_error: true - files: Snowflake.Data.Tests\windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_coverage.xml + files: Snowflake.Data.Tests\windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_coverage.xml + + test-windows-framework: + name: Tests on Windows for Framework + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + dotnet: ['net462', 'net471', 'net472', 'net48', 'net481'] + cloud_env: ['AZURE', 'GCP', 'AWS'] + target_framework: ['netstandard2.0'] + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup Dotnet + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 6.0.x + 8.0.x + 9.0.x + dotnet-quality: 'ga' + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Setup dotnet-coverage + run: dotnet tool install --global dotnet-coverage --version 17.8.4 + - name: Decrypt Parameters + shell: bash + env: + PARAMETER_SECRET: ${{ secrets.PARAMETER_SECRET }} + run: | + gpg --quiet --batch --yes --decrypt --passphrase="$PARAMETER_SECRET" \ + ./.github/workflows/parameters/parameters_${{ matrix.cloud_env }}.json.gpg > Snowflake.Data.Tests/parameters.json + - name: Build Driver + run: | + cd Snowflake.Data.Tests + dotnet restore + dotnet build -f ${{ matrix.dotnet }} '-p:DefineAdditionalConstants=SF_PUBLIC_ENVIRONMENT' + env: + SF_TARGET_FRAMEWORK: ${{ matrix.target_framework }} + - name: Run Tests + run: | + cd Snowflake.Data.Tests + dotnet-coverage collect "dotnet test --framework ${{ matrix.dotnet }} --no-build -l console;verbosity=normal" --output windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_coverage.xml --output-format cobertura --settings coverage.config + env: + snowflake_cloud_env: ${{ matrix.cloud_env }} + net_version: ${{ matrix.dotnet }} + SF_TARGET_FRAMEWORK: ${{ matrix.target_framework }} + - name: Upload Code Coverage Report + uses: actions/upload-artifact@v4 + with: + name: code-coverage-report_windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }} + path: Snowflake.Data.Tests\windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_coverage.xml + + - name: Upload Test Performance Report + uses: actions/upload-artifact@v4 + with: + name: tests-performance_windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }} + path: Snowflake.Data.Tests\windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_performance.csv + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4 + with: + # without the token code cov may fail because of Github limits https://github.com/codecov/codecov-action/issues/557 + token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} + fail_ci_if_error: true + files: Snowflake.Data.Tests\windows_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_coverage.xml test-linux: name: Tests on Linux @@ -96,6 +167,7 @@ jobs: matrix: dotnet: ['net6.0', 'net7.0', 'net8.0', 'net9.0'] cloud_env: ['AZURE', 'GCP', 'AWS'] + target_framework: ['netstandard2.0', 'netstandard2.1'] steps: - uses: actions/checkout@v4 - name: Setup Dotnet @@ -123,30 +195,33 @@ jobs: run: | dotnet restore dotnet build '-p:DefineAdditionalConstants=SF_PUBLIC_ENVIRONMENT' + env: + SF_TARGET_FRAMEWORK: ${{ matrix.target_framework }} - name: Run Tests run: | cd Snowflake.Data.Tests - dotnet-coverage collect "dotnet test --framework ${{ matrix.dotnet }} --no-build -l console;verbosity=normal" --output linux_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_coverage.xml --output-format cobertura --settings coverage.config + dotnet-coverage collect "dotnet test --framework ${{ matrix.dotnet }} --no-build -l console;verbosity=normal" --output linux_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_coverage.xml --output-format cobertura --settings coverage.config env: snowflake_cloud_env: ${{ matrix.cloud_env }} net_version: ${{ matrix.dotnet }} + SF_TARGET_FRAMEWORK: ${{ matrix.target_framework }} - name: Upload Code Coverage Report uses: actions/upload-artifact@v4 with: - name: code-coverage-report_linux_${{ matrix.dotnet }}_${{ matrix.cloud_env }} - path: Snowflake.Data.Tests/linux_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_coverage.xml + name: code-coverage-report_linux_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }} + path: Snowflake.Data.Tests/linux_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_coverage.xml - name: Upload Test Performance Report uses: actions/upload-artifact@v4 with: - name: tests-performance_linux_${{ matrix.dotnet }}_${{ matrix.cloud_env }} - path: Snowflake.Data.Tests/linux_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_performance.csv + name: tests-performance_linux_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }} + path: Snowflake.Data.Tests/linux_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_performance.csv - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 with: # without the token code cov may fail because of Github limits https://github.com/codecov/codecov-action/issues/557 token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} fail_ci_if_error: true - files: Snowflake.Data.Tests/linux_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_coverage.xml + files: Snowflake.Data.Tests/linux_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_coverage.xml test-mac: name: Tests on MAC @@ -156,6 +231,7 @@ jobs: matrix: dotnet: ['net6.0', 'net7.0', 'net8.0', 'net9.0'] cloud_env: ['AZURE', 'GCP', 'AWS'] + target_framework: ['netstandard2.0', 'netstandard2.1'] steps: - uses: actions/checkout@v4 - name: Setup Dotnet @@ -183,27 +259,30 @@ jobs: run: | dotnet restore dotnet build '-p:DefineAdditionalConstants=SF_PUBLIC_ENVIRONMENT' + env: + SF_TARGET_FRAMEWORK: ${{ matrix.target_framework }} - name: Run Tests run: | cd Snowflake.Data.Tests - dotnet-coverage collect "dotnet test --framework ${{ matrix.dotnet }} --no-build -l console;verbosity=normal" --output macos_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_coverage.xml --output-format cobertura --settings coverage.config + dotnet-coverage collect "dotnet test --framework ${{ matrix.dotnet }} --no-build -l console;verbosity=normal" --output macos_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_coverage.xml --output-format cobertura --settings coverage.config env: snowflake_cloud_env: ${{ matrix.cloud_env }} net_version: ${{ matrix.dotnet }} + SF_TARGET_FRAMEWORK: ${{ matrix.target_framework }} - name: Upload Code Coverage Report uses: actions/upload-artifact@v4 with: - name: code-coverage-report_macos_${{ matrix.dotnet }}_${{ matrix.cloud_env }} - path: Snowflake.Data.Tests/macos_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_coverage.xml + name: code-coverage-report_macos_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }} + path: Snowflake.Data.Tests/macos_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_coverage.xml - name: Upload Test Performance Report uses: actions/upload-artifact@v4 with: - name: tests-performance_macos_${{ matrix.dotnet }}_${{ matrix.cloud_env }} - path: Snowflake.Data.Tests/macos_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_performance.csv + name: tests-performance_macos_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }} + path: Snowflake.Data.Tests/macos_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_performance.csv - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 with: # without the token code cov may fail because of Github limits https://github.com/codecov/codecov-action/issues/557 token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }} fail_ci_if_error: true - files: Snowflake.Data.Tests/macos_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_coverage.xml + files: Snowflake.Data.Tests/macos_${{ matrix.dotnet }}_${{ matrix.cloud_env }}_${{ matrix.target_framework }}_coverage.xml diff --git a/Snowflake.Data.Tests/Snowflake.Data.Tests.csproj b/Snowflake.Data.Tests/Snowflake.Data.Tests.csproj index 89d295439..105e5b2a1 100644 --- a/Snowflake.Data.Tests/Snowflake.Data.Tests.csproj +++ b/Snowflake.Data.Tests/Snowflake.Data.Tests.csproj @@ -20,8 +20,18 @@ - + + + + TargetFramework=$(SF_TARGET_FRAMEWORK) + + + + + + + diff --git a/Snowflake.Data.Tests/UnitTests/GcmEncryptionProviderTest.cs b/Snowflake.Data.Tests/UnitTests/GcmEncryptionProviderTest.cs index 53bc7c27e..e0a31455c 100644 --- a/Snowflake.Data.Tests/UnitTests/GcmEncryptionProviderTest.cs +++ b/Snowflake.Data.Tests/UnitTests/GcmEncryptionProviderTest.cs @@ -1,8 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; using System.Text; using NUnit.Framework; -using Org.BouncyCastle.Crypto; using Snowflake.Data.Core; using Snowflake.Data.Core.FileTransfer; using Snowflake.Data.Tests.Util; @@ -10,7 +10,7 @@ namespace Snowflake.Data.Tests.UnitTests { [TestFixture] - public class GcmEncryptionProviderTest + internal class GcmEncryptionProviderTest { private const string PlainText = "there is no rose without thorns"; private static readonly byte[] s_plainTextBytes = Encoding.UTF8.GetBytes(PlainText); @@ -36,24 +36,26 @@ public class GcmEncryptionProviderTest queryId = s_queryId, smkId = SmkId }; - private static readonly FileTransferConfiguration s_fileTransferConfiguration = new FileTransferConfiguration + internal static readonly FileTransferConfiguration s_fileTransferConfiguration = new FileTransferConfiguration { TempDir = Path.GetTempPath(), MaxBytesInMemory = FileTransferConfiguration.DefaultMaxBytesInMemory }; [Test] - public void TestEncryptAndDecryptWithoutAad() + [TestCaseSource(nameof(EncryptionTestCases))] + public void TestEncryptAndDecryptWithoutAad( + IEncryptionProvider encryptionProvider, + IDecryptionProvider decryptionProvider) { // arrange SFEncryptionMetadata encryptionMetadata = new SFEncryptionMetadata(); // act - using (var encryptedStream = GcmEncryptionProvider.Encrypt( + using (var encryptedStream = encryptionProvider.Encrypt( s_encryptionMaterial, encryptionMetadata, // this is output parameter - s_fileTransferConfiguration, - new MemoryStream(s_plainTextBytes), + s_plainTextBytes, null, null)) { @@ -67,7 +69,7 @@ public void TestEncryptAndDecryptWithoutAad() Assert.IsNull(encryptionMetadata.aad); // act - using (var decryptedStream = GcmEncryptionProvider.Decrypt(new MemoryStream(encryptedContent), s_encryptionMaterial, encryptionMetadata, s_fileTransferConfiguration)) + using (var decryptedStream = decryptionProvider.Decrypt(encryptedContent, s_encryptionMaterial, encryptionMetadata)) { // assert var decryptedText = ExtractContent(decryptedStream); @@ -77,17 +79,19 @@ public void TestEncryptAndDecryptWithoutAad() } [Test] - public void TestEncryptAndDecryptWithEmptyAad() + [TestCaseSource(nameof(EncryptionTestCases))] + public void TestEncryptAndDecryptWithEmptyAad( + IEncryptionProvider encryptionProvider, + IDecryptionProvider decryptionProvider) { // arrange SFEncryptionMetadata encryptionMetadata = new SFEncryptionMetadata(); // act - using (var encryptedStream = GcmEncryptionProvider.Encrypt( + using (var encryptedStream = encryptionProvider.Encrypt( s_encryptionMaterial, encryptionMetadata, // this is output parameter - s_fileTransferConfiguration, - new MemoryStream(s_plainTextBytes), + s_plainTextBytes, s_emptyAadBytes, s_emptyAadBytes)) { @@ -101,7 +105,7 @@ public void TestEncryptAndDecryptWithEmptyAad() Assert.AreEqual(s_emptyAadBase64, encryptionMetadata.aad); // act - using (var decryptedStream = GcmEncryptionProvider.Decrypt(new MemoryStream(encryptedContent), s_encryptionMaterial, encryptionMetadata, s_fileTransferConfiguration)) + using (var decryptedStream = decryptionProvider.Decrypt(encryptedContent, s_encryptionMaterial, encryptionMetadata)) { // assert var decryptedText = ExtractContent(decryptedStream); @@ -111,17 +115,19 @@ public void TestEncryptAndDecryptWithEmptyAad() } [Test] - public void TestEncryptAndDecryptWithAad() + [TestCaseSource(nameof(EncryptionTestCases))] + public void TestEncryptAndDecryptWithAad( + IEncryptionProvider encryptionProvider, + IDecryptionProvider decryptionProvider) { // arrange SFEncryptionMetadata encryptionMetadata = new SFEncryptionMetadata(); // act - using (var encryptedStream = GcmEncryptionProvider.Encrypt( + using (var encryptedStream = encryptionProvider.Encrypt( s_encryptionMaterial, encryptionMetadata, // this is output parameter - s_fileTransferConfiguration, - new MemoryStream(s_plainTextBytes), + s_plainTextBytes, s_contentAadBytes, s_keyAadBytes)) { @@ -135,7 +141,7 @@ public void TestEncryptAndDecryptWithAad() CollectionAssert.AreEqual(s_contentAadBase64, encryptionMetadata.aad); // act - using (var decryptedStream = GcmEncryptionProvider.Decrypt(new MemoryStream(encryptedContent), s_encryptionMaterial, encryptionMetadata, s_fileTransferConfiguration)) + using (var decryptedStream = decryptionProvider.Decrypt(encryptedContent, s_encryptionMaterial, encryptionMetadata)) { // assert var decryptedText = ExtractContent(decryptedStream); @@ -145,15 +151,17 @@ public void TestEncryptAndDecryptWithAad() } [Test] - public void TestFailDecryptWithInvalidKeyAad() + [TestCaseSource(nameof(EncryptionTestCases))] + public void TestFailDecryptWithInvalidKeyAad( + IEncryptionProvider encryptionProvider, + IDecryptionProvider decryptionProvider) { // arrange SFEncryptionMetadata encryptionMetadata = new SFEncryptionMetadata(); - using (var encryptedStream = GcmEncryptionProvider.Encrypt( + using (var encryptedStream = encryptionProvider.Encrypt( s_encryptionMaterial, encryptionMetadata, // this is output parameter - s_fileTransferConfiguration, - new MemoryStream(s_plainTextBytes), + s_plainTextBytes, null, s_keyAadBytes)) { @@ -166,24 +174,27 @@ public void TestFailDecryptWithInvalidKeyAad() encryptionMetadata.keyAad = s_invalidAadBase64; // act - var thrown = Assert.Throws(() => - GcmEncryptionProvider.Decrypt(new MemoryStream(encryptedContent), s_encryptionMaterial, encryptionMetadata, s_fileTransferConfiguration)); + var thrown = Assert.Catch(() => + decryptionProvider.Decrypt(encryptedContent, s_encryptionMaterial, encryptionMetadata)); // assert - Assert.AreEqual("mac check in GCM failed", thrown.Message); + Assert.NotNull(thrown); + Assert.AreEqual(decryptionProvider.ExpectedExceptionMessage(), thrown.Message); } } [Test] - public void TestFailDecryptWithInvalidContentAad() + [TestCaseSource(nameof(EncryptionTestCases))] + public void TestFailDecryptWithInvalidContentAad( + IEncryptionProvider encryptionProvider, + IDecryptionProvider decryptionProvider) { // arrange SFEncryptionMetadata encryptionMetadata = new SFEncryptionMetadata(); - using (var encryptedStream = GcmEncryptionProvider.Encrypt( + using (var encryptedStream = encryptionProvider.Encrypt( s_encryptionMaterial, encryptionMetadata, // this is output parameter - s_fileTransferConfiguration, - new MemoryStream(s_plainTextBytes), + s_plainTextBytes, s_contentAadBytes, null)) { @@ -196,24 +207,27 @@ public void TestFailDecryptWithInvalidContentAad() encryptionMetadata.aad = s_invalidAadBase64; // act - var thrown = Assert.Throws(() => - GcmEncryptionProvider.Decrypt(new MemoryStream(encryptedContent), s_encryptionMaterial, encryptionMetadata, s_fileTransferConfiguration)); + var thrown = Assert.Catch(() => + decryptionProvider.Decrypt(encryptedContent, s_encryptionMaterial, encryptionMetadata)); // assert - Assert.AreEqual("mac check in GCM failed", thrown.Message); + Assert.NotNull(thrown); + Assert.AreEqual(decryptionProvider.ExpectedExceptionMessage(), thrown.Message); } } [Test] - public void TestFailDecryptWhenMissingAad() + [TestCaseSource(nameof(EncryptionTestCases))] + public void TestFailDecryptWhenMissingAad( + IEncryptionProvider encryptionProvider, + IDecryptionProvider decryptionProvider) { // arrange SFEncryptionMetadata encryptionMetadata = new SFEncryptionMetadata(); - using (var encryptedStream = GcmEncryptionProvider.Encrypt( + using (var encryptedStream = encryptionProvider.Encrypt( s_encryptionMaterial, encryptionMetadata, // this is output parameter - s_fileTransferConfiguration, - new MemoryStream(s_plainTextBytes), + s_plainTextBytes, s_contentAadBytes, s_keyAadBytes)) { @@ -227,16 +241,20 @@ public void TestFailDecryptWhenMissingAad() encryptionMetadata.aad = null; // act - var thrown = Assert.Throws(() => - GcmEncryptionProvider.Decrypt(new MemoryStream(encryptedContent), s_encryptionMaterial, encryptionMetadata,s_fileTransferConfiguration)); + var thrown = Assert.Catch(() => + decryptionProvider.Decrypt(encryptedContent, s_encryptionMaterial, encryptionMetadata)); // assert - Assert.AreEqual("mac check in GCM failed", thrown.Message); + Assert.NotNull(thrown); + Assert.AreEqual(decryptionProvider.ExpectedExceptionMessage(), thrown.Message); } } [Test] - public void TestEncryptAndDecryptFile() + [TestCaseSource(nameof(EncryptionTestCases))] + public void TestEncryptAndDecryptFile( + IEncryptionProvider encryptionProvider, + IDecryptionProvider decryptionProvider) { // arrange SFEncryptionMetadata encryptionMetadata = new SFEncryptionMetadata(); @@ -247,8 +265,8 @@ public void TestEncryptAndDecryptFile() CreateFile(plainTextFilePath, PlainText); // act - using (var encryptedStream = GcmEncryptionProvider.EncryptFile(plainTextFilePath, s_encryptionMaterial, encryptionMetadata, - s_fileTransferConfiguration, s_contentAadBytes, s_keyAadBytes)) + using (var encryptedStream = encryptionProvider.EncryptFile(plainTextFilePath, s_encryptionMaterial, encryptionMetadata, + s_contentAadBytes, s_keyAadBytes)) { CreateFile(encryptedFilePath, encryptedStream); } @@ -262,8 +280,7 @@ public void TestEncryptAndDecryptFile() // act string result; - using (var decryptedStream = GcmEncryptionProvider.DecryptFile(encryptedFilePath, s_encryptionMaterial, encryptionMetadata, - s_fileTransferConfiguration)) + using (var decryptedStream = decryptionProvider.DecryptFile(encryptedFilePath, s_encryptionMaterial, encryptionMetadata)) { decryptedStream.Position = 0; var resultBytes = new byte[decryptedStream.Length]; @@ -314,5 +331,49 @@ private byte[] ExtractContentBytes(Stream stream) stream.CopyTo(memoryStream); return memoryStream.ToArray(); } + + internal static IEnumerable EncryptionTestCases() + { + yield return new object[] + { + BouncyCastleEncryption.Instance, + BouncyCastleDecryption.Instance + }; + // tests for BouncyCastle fips + yield return new object[] + { + BouncyCastleFipsEncryption.Instance, + BouncyCastleFipsDecryption.Instance + }; + yield return new object[] + { + BouncyCastleEncryption.Instance, + BouncyCastleFipsDecryption.Instance + }; + yield return new object[] + { + BouncyCastleFipsEncryption.Instance, + BouncyCastleDecryption.Instance + }; + +#if NETSTANDARD2_1 + // tests for AesGcm + yield return new object[] + { + AesGcmEncryption.Instance, + AesGcmDecryption.Instance + }; + yield return new object[] + { + BouncyCastleEncryption.Instance, + AesGcmDecryption.Instance + }; + yield return new object[] + { + AesGcmEncryption.Instance, + BouncyCastleDecryption.Instance + }; +#endif + } } } diff --git a/Snowflake.Data.Tests/UnitTests/TargetFrameworkReporterTest.cs b/Snowflake.Data.Tests/UnitTests/TargetFrameworkReporterTest.cs new file mode 100644 index 000000000..74c87ff3a --- /dev/null +++ b/Snowflake.Data.Tests/UnitTests/TargetFrameworkReporterTest.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; +using Snowflake.Data.Core; + +namespace Snowflake.Data.Tests.UnitTests +{ + [TestFixture] + public class TargetFrameworkReporterTest + { + [Test] + public void TestTargetFramework() + { + TargetFrameworkReporter.Report(); + } + } +} diff --git a/Snowflake.Data.Tests/Util/Encryption/AesGcmDecryption.cs b/Snowflake.Data.Tests/Util/Encryption/AesGcmDecryption.cs new file mode 100644 index 000000000..e9297eb82 --- /dev/null +++ b/Snowflake.Data.Tests/Util/Encryption/AesGcmDecryption.cs @@ -0,0 +1,37 @@ +#if NETSTANDARD2_1 +using System.IO; +using Snowflake.Data.Core; +using Snowflake.Data.Core.FileTransfer; + +namespace Snowflake.Data.Tests.Util +{ + internal class AesGcmDecryption : IDecryptionProvider + { + public static readonly AesGcmDecryption Instance = new AesGcmDecryption(); + + public Stream DecryptFile( + string inFile, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata) + { + return AesGcmEncryptionProvider.DecryptFile( + inFile, + encryptionMaterial, + encryptionMetadata); + } + + public Stream Decrypt( + byte[] inputBytes, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata) + { + return AesGcmEncryptionProvider.Decrypt( + inputBytes, + encryptionMaterial, + encryptionMetadata); + } + + public string ExpectedExceptionMessage() => "The computed authentication tag did not match the input authentication tag."; + } +} +#endif diff --git a/Snowflake.Data.Tests/Util/Encryption/AesGcmEncryption.cs b/Snowflake.Data.Tests/Util/Encryption/AesGcmEncryption.cs new file mode 100644 index 000000000..30325249f --- /dev/null +++ b/Snowflake.Data.Tests/Util/Encryption/AesGcmEncryption.cs @@ -0,0 +1,44 @@ +#if NETSTANDARD2_1 +using System.IO; +using Snowflake.Data.Core; +using Snowflake.Data.Core.FileTransfer; + +namespace Snowflake.Data.Tests.Util +{ + internal class AesGcmEncryption : IEncryptionProvider + { + public static readonly AesGcmEncryption Instance = new AesGcmEncryption(); + + public Stream EncryptFile( + string inFile, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata, + byte[] contentAad, + byte[] keyAad + ) + { + return AesGcmEncryptionProvider.EncryptFile( + inFile, + encryptionMaterial, + encryptionMetadata, + contentAad, + keyAad); + } + + public Stream Encrypt( + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata, + byte[] inputBytes, + byte[] contentAad, + byte[] keyAad) + { + return AesGcmEncryptionProvider.Encrypt( + encryptionMaterial, + encryptionMetadata, + inputBytes, + contentAad, + keyAad); + } + } +} +#endif diff --git a/Snowflake.Data.Tests/Util/Encryption/BouncyCastleDecryption.cs b/Snowflake.Data.Tests/Util/Encryption/BouncyCastleDecryption.cs new file mode 100644 index 000000000..c733f6b3d --- /dev/null +++ b/Snowflake.Data.Tests/Util/Encryption/BouncyCastleDecryption.cs @@ -0,0 +1,39 @@ +using System.IO; +using Snowflake.Data.Core; +using Snowflake.Data.Core.FileTransfer; +using Snowflake.Data.Tests.UnitTests; + +namespace Snowflake.Data.Tests.Util +{ + internal class BouncyCastleDecryption : IDecryptionProvider + { + public static readonly BouncyCastleDecryption Instance = new BouncyCastleDecryption(); + + public Stream DecryptFile( + string inFile, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata) + { + return GcmEncryptionProvider.DecryptFile( + inFile, + encryptionMaterial, + encryptionMetadata, + GcmEncryptionProviderTest.s_fileTransferConfiguration); + } + + public Stream Decrypt( + byte[] inputBytes, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata) + { + return GcmEncryptionProvider.Decrypt( + new MemoryStream(inputBytes), + encryptionMaterial, + encryptionMetadata, + GcmEncryptionProviderTest.s_fileTransferConfiguration); + } + + public string ExpectedExceptionMessage() => + "mac check in GCM failed"; + } +} diff --git a/Snowflake.Data.Tests/Util/Encryption/BouncyCastleEncryption.cs b/Snowflake.Data.Tests/Util/Encryption/BouncyCastleEncryption.cs new file mode 100644 index 000000000..9a1404ab9 --- /dev/null +++ b/Snowflake.Data.Tests/Util/Encryption/BouncyCastleEncryption.cs @@ -0,0 +1,46 @@ +using System.IO; +using Snowflake.Data.Core; +using Snowflake.Data.Core.FileTransfer; +using Snowflake.Data.Tests.UnitTests; + +namespace Snowflake.Data.Tests.Util +{ + internal class BouncyCastleEncryption: IEncryptionProvider + { + public static readonly BouncyCastleEncryption Instance = new BouncyCastleEncryption(); + + public Stream EncryptFile( + string inFile, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata, + byte[] contentAad, + byte[] keyAad + ) + { + return GcmEncryptionProvider.EncryptFile( + inFile, + encryptionMaterial, + encryptionMetadata, + GcmEncryptionProviderTest.s_fileTransferConfiguration, + contentAad, + keyAad); + } + + public Stream Encrypt( + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata, + byte[] inputBytes, + byte[] contentAad, + byte[] keyAad) + { + return GcmEncryptionProvider.Encrypt( + encryptionMaterial, + encryptionMetadata, + GcmEncryptionProviderTest.s_fileTransferConfiguration, + new MemoryStream(inputBytes), + contentAad, + keyAad); + } + } + +} diff --git a/Snowflake.Data.Tests/Util/Encryption/BouncyCastleFipsDecryption.cs b/Snowflake.Data.Tests/Util/Encryption/BouncyCastleFipsDecryption.cs new file mode 100644 index 000000000..f26d53cda --- /dev/null +++ b/Snowflake.Data.Tests/Util/Encryption/BouncyCastleFipsDecryption.cs @@ -0,0 +1,39 @@ +using System.IO; +using Snowflake.Data.Core; +using Snowflake.Data.Core.FileTransfer; +using Snowflake.Data.Tests.UnitTests; + +namespace Snowflake.Data.Tests.Util +{ + internal class BouncyCastleFipsDecryption : IDecryptionProvider + { + public static readonly BouncyCastleFipsDecryption Instance = new BouncyCastleFipsDecryption(); + + public Stream DecryptFile( + string inFile, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata) + { + return GcmFipsEncryptionProvider.DecryptFile( + inFile, + encryptionMaterial, + encryptionMetadata, + GcmEncryptionProviderTest.s_fileTransferConfiguration); + } + + public Stream Decrypt( + byte[] inputBytes, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata) + { + return GcmFipsEncryptionProvider.Decrypt( + new MemoryStream(inputBytes), + encryptionMaterial, + encryptionMetadata, + GcmEncryptionProviderTest.s_fileTransferConfiguration); + } + + public string ExpectedExceptionMessage() => + "mac check in GCM failed"; + } +} diff --git a/Snowflake.Data.Tests/Util/Encryption/BouncyCastleFipsEncryption.cs b/Snowflake.Data.Tests/Util/Encryption/BouncyCastleFipsEncryption.cs new file mode 100644 index 000000000..79870f3c3 --- /dev/null +++ b/Snowflake.Data.Tests/Util/Encryption/BouncyCastleFipsEncryption.cs @@ -0,0 +1,46 @@ +using System.IO; +using Snowflake.Data.Core; +using Snowflake.Data.Core.FileTransfer; +using Snowflake.Data.Tests.UnitTests; + +namespace Snowflake.Data.Tests.Util +{ + internal class BouncyCastleFipsEncryption: IEncryptionProvider + { + public static readonly BouncyCastleFipsEncryption Instance = new BouncyCastleFipsEncryption(); + + public Stream EncryptFile( + string inFile, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata, + byte[] contentAad, + byte[] keyAad + ) + { + return GcmFipsEncryptionProvider.EncryptFile( + inFile, + encryptionMaterial, + encryptionMetadata, + GcmEncryptionProviderTest.s_fileTransferConfiguration, + contentAad, + keyAad); + } + + public Stream Encrypt( + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata, + byte[] inputBytes, + byte[] contentAad, + byte[] keyAad) + { + return GcmFipsEncryptionProvider.Encrypt( + encryptionMaterial, + encryptionMetadata, + GcmEncryptionProviderTest.s_fileTransferConfiguration, + new MemoryStream(inputBytes), + contentAad, + keyAad); + } + } + +} diff --git a/Snowflake.Data.Tests/Util/Encryption/IDecryptionProvider.cs b/Snowflake.Data.Tests/Util/Encryption/IDecryptionProvider.cs new file mode 100644 index 000000000..a1b809d29 --- /dev/null +++ b/Snowflake.Data.Tests/Util/Encryption/IDecryptionProvider.cs @@ -0,0 +1,21 @@ +using System.IO; +using Snowflake.Data.Core; +using Snowflake.Data.Core.FileTransfer; + +namespace Snowflake.Data.Tests.Util +{ + internal interface IDecryptionProvider + { + Stream DecryptFile( + string inFile, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata); + + Stream Decrypt( + byte[] inputBytes, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata); + + string ExpectedExceptionMessage(); + } +} diff --git a/Snowflake.Data.Tests/Util/Encryption/IEncryptionProvider.cs b/Snowflake.Data.Tests/Util/Encryption/IEncryptionProvider.cs new file mode 100644 index 000000000..f835ccbd9 --- /dev/null +++ b/Snowflake.Data.Tests/Util/Encryption/IEncryptionProvider.cs @@ -0,0 +1,23 @@ +using System.IO; +using Snowflake.Data.Core; +using Snowflake.Data.Core.FileTransfer; + +namespace Snowflake.Data.Tests.Util +{ + internal interface IEncryptionProvider + { + Stream EncryptFile( + string inFile, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata, + byte[] contentAad, + byte[] keyAad); + + Stream Encrypt( + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata, + byte[] inputBytes, + byte[] contentAad, + byte[] keyAad); + } +} diff --git a/Snowflake.Data/Core/Authenticator/KeyPairAuthenticator.cs b/Snowflake.Data/Core/Authenticator/KeyPairAuthenticator.cs index 7d86d02c9..2ec227d98 100644 --- a/Snowflake.Data/Core/Authenticator/KeyPairAuthenticator.cs +++ b/Snowflake.Data/Core/Authenticator/KeyPairAuthenticator.cs @@ -1,10 +1,8 @@ /* * Copyright (c) 2012-2021 Snowflake Computing Inc. All rights reserved. */ - +extern alias BouncyCastle; using System; -using System.Collections.Generic; -using System.Text; using System.Threading; using System.Threading.Tasks; using Snowflake.Data.Log; @@ -12,13 +10,14 @@ using Snowflake.Data.Client; using System.Security.Cryptography; using System.IO; -using Org.BouncyCastle.OpenSsl; -using Org.BouncyCastle.Security; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.X509; +using BouncyCastle::Org.BouncyCastle.OpenSsl; +using BouncyCastle::Org.BouncyCastle.Security; +using BouncyCastle::Org.BouncyCastle.Crypto; +using BouncyCastle::Org.BouncyCastle.Crypto.Parameters; +using BouncyCastle::Org.BouncyCastle.X509; using System.Security.Claims; using Microsoft.IdentityModel.Tokens; + namespace Snowflake.Data.Core.Authenticator { /// diff --git a/Snowflake.Data/Core/FileTransfer/AesGcmEncryptionProvider.cs b/Snowflake.Data/Core/FileTransfer/AesGcmEncryptionProvider.cs new file mode 100644 index 000000000..74e1fb313 --- /dev/null +++ b/Snowflake.Data/Core/FileTransfer/AesGcmEncryptionProvider.cs @@ -0,0 +1,118 @@ +#if NETSTANDARD2_1 +using System; +using System.IO; +using System.Security.Cryptography; +using Snowflake.Data.Log; + +namespace Snowflake.Data.Core.FileTransfer +{ + internal class AesGcmEncryptionProvider + { + private const int AesTagBits = 128; + internal const int TagSizeInBytes = AesTagBits / 8; + private const int InitVectorSizeInBytes = 12; + + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + private static readonly Random s_random = new Random(); + + public static Stream EncryptFile( + string inFile, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata, + byte[] contentAad, + byte[] keyAad + ) + { + var fileBytes = File.ReadAllBytes(inFile); + return Encrypt(encryptionMaterial, encryptionMetadata, fileBytes, contentAad, keyAad); + } + + public static Stream DecryptFile( + string inFile, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata) + { + var fileBytes = File.ReadAllBytes(inFile); + return Decrypt(fileBytes, encryptionMaterial, encryptionMetadata); + } + + public static Stream Encrypt( + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata, // this is output parameter + byte[] inputBytes, + byte[] contentAad, + byte[] keyAad) + { + byte[] decodedMasterKey = Convert.FromBase64String(encryptionMaterial.queryStageMasterKey); + int masterKeySize = decodedMasterKey.Length; + s_logger.Debug($"Master key size : {masterKeySize}"); + + var contentIV = new byte[InitVectorSizeInBytes]; + var keyIV = new byte[InitVectorSizeInBytes]; + var fileKeyBytes = new byte[masterKeySize]; // we choose a random fileKey to encrypt it with qsmk key with GCM + s_random.NextBytes(contentIV); + s_random.NextBytes(keyIV); + s_random.NextBytes(fileKeyBytes); + + var encryptedKey = EncryptBytes(fileKeyBytes, decodedMasterKey, keyIV, keyAad); + var result = EncryptBytes(inputBytes, fileKeyBytes, contentIV, contentAad); + + MaterialDescriptor matDesc = new MaterialDescriptor + { + smkId = encryptionMaterial.smkId.ToString(), + queryId = encryptionMaterial.queryId, + keySize = (masterKeySize * 8).ToString() + }; + + encryptionMetadata.key = Convert.ToBase64String(encryptedKey); + encryptionMetadata.iv = Convert.ToBase64String(contentIV); + encryptionMetadata.keyIV = Convert.ToBase64String(keyIV); + encryptionMetadata.keyAad = keyAad == null ? null : Convert.ToBase64String(keyAad); + encryptionMetadata.aad = contentAad == null ? null : Convert.ToBase64String(contentAad); + encryptionMetadata.matDesc = Newtonsoft.Json.JsonConvert.SerializeObject(matDesc); + + return new MemoryStream(result); + } + + public static Stream Decrypt( + byte[] inputBytes, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata) + { + var decodedMasterKey = Convert.FromBase64String(encryptionMaterial.queryStageMasterKey); + var keyBytes = Convert.FromBase64String(encryptionMetadata.key); + var keyIVBytes = Convert.FromBase64String(encryptionMetadata.keyIV); + var keyAad = encryptionMetadata.keyAad == null ? null : Convert.FromBase64String(encryptionMetadata.keyAad); + var ivBytes = Convert.FromBase64String(encryptionMetadata.iv); + var contentAad = encryptionMetadata.aad == null ? null : Convert.FromBase64String(encryptionMetadata.aad); + var decryptedFileKey = DecryptBytes(keyBytes, decodedMasterKey, keyIVBytes, keyAad); + var decryptedContent = DecryptBytes(inputBytes, decryptedFileKey, ivBytes, contentAad); + return new MemoryStream(decryptedContent); + } + + private static byte[] EncryptBytes(byte[] inputBytes, byte[] keyBytes, byte[] iv, byte[] aad) + { + using (var aesGcm = new AesGcm(keyBytes)) + { + var encodedBytes = new byte[inputBytes.Length + TagSizeInBytes]; + var cipherBytes = encodedBytes.AsSpan(0, inputBytes.Length); + var authenticationTag = encodedBytes.AsSpan(inputBytes.Length, TagSizeInBytes); + aesGcm.Encrypt(iv, inputBytes, cipherBytes, authenticationTag, aad); + return encodedBytes; + } + } + + private static byte[] DecryptBytes(byte[] inputBytes, byte[] keyBytes, byte[] iv, byte[] aad) + { + using (var aesGcm = new AesGcm(keyBytes)) + { + var cipherBytes = inputBytes.AsSpan(0, inputBytes.Length - TagSizeInBytes); + var decryptedBytes = new byte[cipherBytes.Length]; + var authenticationTag = inputBytes.AsSpan(inputBytes.Length - TagSizeInBytes, TagSizeInBytes); + aesGcm.Decrypt(iv, cipherBytes, authenticationTag, decryptedBytes, aad); + return decryptedBytes; + } + } + } +} +#endif diff --git a/Snowflake.Data/Core/FileTransfer/GcmEncryptionProvider.cs b/Snowflake.Data/Core/FileTransfer/GcmEncryptionProvider.cs index b7ad2cda0..5602f9edd 100644 --- a/Snowflake.Data/Core/FileTransfer/GcmEncryptionProvider.cs +++ b/Snowflake.Data/Core/FileTransfer/GcmEncryptionProvider.cs @@ -1,9 +1,10 @@ +extern alias BouncyCastle; using System; using System.IO; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.IO; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Security; +using BouncyCastle::Org.BouncyCastle.Crypto; +using BouncyCastle::Org.BouncyCastle.Crypto.IO; +using BouncyCastle::Org.BouncyCastle.Crypto.Parameters; +using BouncyCastle::Org.BouncyCastle.Security; using Snowflake.Data.Log; namespace Snowflake.Data.Core.FileTransfer diff --git a/Snowflake.Data/Core/FileTransfer/GcmFipsEncryptionProvider.cs b/Snowflake.Data/Core/FileTransfer/GcmFipsEncryptionProvider.cs new file mode 100644 index 000000000..4812b61db --- /dev/null +++ b/Snowflake.Data/Core/FileTransfer/GcmFipsEncryptionProvider.cs @@ -0,0 +1,184 @@ +extern alias BouncyCastleFips; +using System; +using System.IO; +using BouncyCastleFips::Org.BouncyCastle.Security; +using BouncyCastleFips::Org.BouncyCastle.Crypto.Fips; +using BouncyCastleFips::Org.BouncyCastle.Crypto; +using BouncyCastleFips::Org.BouncyCastle.Utilities.IO; +using Snowflake.Data.Log; + +namespace Snowflake.Data.Core.FileTransfer +{ + internal class GcmFipsEncryptionProvider + { + private const int TagSizeInBits = 128; + internal const int TagSizeInBytes = TagSizeInBits / 8; + private const int InitVectorSizeInBytes = 12; + + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + + private static readonly SecureRandom s_random = SecureRandom.GetInstance("SHA1PRNG"); + + public static Stream EncryptFile( + string inFile, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata, + FileTransferConfiguration transferConfiguration, + byte[] contentAad, + byte[] keyAad + ) + { + using (var fileStream = File.OpenRead(inFile)) + { + return Encrypt(encryptionMaterial, encryptionMetadata, transferConfiguration, fileStream, contentAad, keyAad); + } + } + + public static Stream DecryptFile( + string inFile, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata, + FileTransferConfiguration transferConfiguration) + { + using (var fileStream = File.OpenRead(inFile)) + { + return Decrypt(fileStream, encryptionMaterial, encryptionMetadata, transferConfiguration); + } + } + + public static Stream Encrypt( + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata, // this is output parameter + FileTransferConfiguration fileTransferConfiguration, + Stream inputStream, + byte[] contentAad, + byte[] keyAad) + { + byte[] decodedMasterKey = Convert.FromBase64String(encryptionMaterial.queryStageMasterKey); + int masterKeySize = decodedMasterKey.Length; + s_logger.Debug($"Master key size : {masterKeySize}"); + + var contentIV = new byte[InitVectorSizeInBytes]; + var keyIV = new byte[InitVectorSizeInBytes]; + var fileKeyBytes = new byte[masterKeySize]; // we choose a random fileKey to encrypt it with qsmk key with GCM + s_random.NextBytes(contentIV); + s_random.NextBytes(keyIV); + s_random.NextBytes(fileKeyBytes); + + var encryptedKey = EncryptKey(fileKeyBytes, decodedMasterKey, keyIV, keyAad); + var result = EncryptContent(inputStream, fileKeyBytes, contentIV, contentAad, fileTransferConfiguration); + + MaterialDescriptor matDesc = new MaterialDescriptor + { + smkId = encryptionMaterial.smkId.ToString(), + queryId = encryptionMaterial.queryId, + keySize = (masterKeySize * 8).ToString() + }; + + encryptionMetadata.key = Convert.ToBase64String(encryptedKey); + encryptionMetadata.iv = Convert.ToBase64String(contentIV); + encryptionMetadata.keyIV = Convert.ToBase64String(keyIV); + encryptionMetadata.keyAad = keyAad == null ? null : Convert.ToBase64String(keyAad); + encryptionMetadata.aad = contentAad == null ? null : Convert.ToBase64String(contentAad); + encryptionMetadata.matDesc = Newtonsoft.Json.JsonConvert.SerializeObject(matDesc); + + return new MemoryStream(result); + } + + public static Stream Decrypt( + Stream inputStream, + PutGetEncryptionMaterial encryptionMaterial, + SFEncryptionMetadata encryptionMetadata, + FileTransferConfiguration fileTransferConfiguration) + { + var decodedMasterKey = Convert.FromBase64String(encryptionMaterial.queryStageMasterKey); + var keyBytes = Convert.FromBase64String(encryptionMetadata.key); + var keyIVBytes = Convert.FromBase64String(encryptionMetadata.keyIV); + var keyAad = encryptionMetadata.keyAad == null ? null : Convert.FromBase64String(encryptionMetadata.keyAad); + var ivBytes = Convert.FromBase64String(encryptionMetadata.iv); + var contentAad = encryptionMetadata.aad == null ? null : Convert.FromBase64String(encryptionMetadata.aad); + var decryptedFileKey = DecryptKey(keyBytes, decodedMasterKey, keyIVBytes, keyAad); + var decryptedContent = DecryptContent(inputStream, decryptedFileKey, ivBytes, contentAad, fileTransferConfiguration); + return new MemoryStream(decryptedContent); + } + + private static byte[] EncryptKey(byte[] fileKeyBytes, byte[] qsmk, byte[] keyIV, byte[] keyAad) + { + var outputStream = new MemoryOutputStream(); + var keyCipher = BuildEncryptor(true, outputStream, qsmk, keyIV, keyAad); + using (Stream encryptionStream = keyCipher.Stream) + { + encryptionStream.Write(fileKeyBytes, 0, fileKeyBytes.Length); + encryptionStream.Flush(); + } + var outputBytes = outputStream.ToArray(); + return outputBytes; + } + + private static byte[] DecryptKey(byte[] fileKeyBytes, byte[] qsmk, byte[] keyIV, byte[] keyAad) + { + var outputStream = new MemoryOutputStream(); + var keyCipher = BuildEncryptor(false, outputStream, qsmk, keyIV, keyAad); + using (Stream encryptionStream = keyCipher.Stream) + { + encryptionStream.Write(fileKeyBytes, 0, fileKeyBytes.Length); + encryptionStream.Flush(); + } + var outputBytes = outputStream.ToArray(); + return outputBytes; + } + + private static byte[] EncryptContent(Stream inputStream, byte[] fileKeyBytes, byte[] contentIV, byte[] contentAad, + FileTransferConfiguration transferConfiguration) + { + var targetStream = new MemoryOutputStream(); + var contentCipher = BuildEncryptor(true, targetStream, fileKeyBytes, contentIV, contentAad); + using (var encryptionStream = contentCipher.Stream) + { + byte[] buffer = new byte[transferConfiguration.MaxBytesInMemory]; + int bytesRead; + while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) > 0) + { + encryptionStream.Write(buffer, 0, bytesRead); + } + encryptionStream.Flush(); + } + var outputBytes = targetStream.ToArray(); + return outputBytes; + } + + private static byte[] DecryptContent(Stream inputStream, byte[] fileKeyBytes, byte[] contentIV, byte[] contentAad, + FileTransferConfiguration transferConfiguration) + { + var targetStream = new MemoryOutputStream(); + var contentCipher = BuildEncryptor(false, targetStream, fileKeyBytes, contentIV, contentAad); + using (var encryptionStream = contentCipher.Stream) + { + byte[] buffer = new byte[transferConfiguration.MaxBytesInMemory]; + int bytesRead; + while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) > 0) + { + encryptionStream.Write(buffer, 0, bytesRead); + } + encryptionStream.Flush(); + } + var outputBytes = targetStream.ToArray(); + return outputBytes; + } + + private static IAeadCipher BuildEncryptor(bool forEncryption, Stream outputStream, byte[] keyBytes, byte[] initialisationVector, byte[] aadData) + { + var serviceProvider = CryptoServicesRegistrar.CreateService(new FipsAes.Key(keyBytes)); + var algorithmDetails = FipsAes.Gcm.WithIV(initialisationVector).WithMacSize(TagSizeInBits); + var encryptorBuilder = forEncryption + ? serviceProvider.CreateAeadEncryptorBuilder(algorithmDetails) + : serviceProvider.CreateAeadDecryptorBuilder(algorithmDetails); + var encryptor = (IAeadCipher) encryptorBuilder.BuildCipher(outputStream); + if (aadData != null) + { + encryptor.AadStream.Write(aadData, 0, aadData.Length); + } + return encryptor; + } + } +} diff --git a/Snowflake.Data/Core/TargetFrameworkReporter.cs b/Snowflake.Data/Core/TargetFrameworkReporter.cs new file mode 100644 index 000000000..c7da20b27 --- /dev/null +++ b/Snowflake.Data/Core/TargetFrameworkReporter.cs @@ -0,0 +1,19 @@ +using Snowflake.Data.Log; + +namespace Snowflake.Data.Core +{ + internal class TargetFrameworkReporter + { + private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); + + public static void Report() + { +#if NETSTANDARD2_1 + s_logger.Warn($"Using library targeted to netstandard 2.1"); +#endif +#if NETSTANDARD2_0 + s_logger.Warn($"Using library targeted to netstandard 2.0"); +#endif + } + } +} diff --git a/Snowflake.Data/Snowflake.Data.csproj b/Snowflake.Data/Snowflake.Data.csproj index f17124419..232234cb2 100644 --- a/Snowflake.Data/Snowflake.Data.csproj +++ b/Snowflake.Data/Snowflake.Data.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.0;netstandard2.1 Snowflake.Data Snowflake.Data https://github.com/snowflakedb/snowflake-connector-net/blob/master/LICENSE @@ -25,12 +25,19 @@ - + + + + ..\dependencies\bc-fips-1.0.2.dll + BouncyCastleFips + + + diff --git a/dependencies/DependenciesNotes.md b/dependencies/DependenciesNotes.md new file mode 100644 index 000000000..8453c8f2b --- /dev/null +++ b/dependencies/DependenciesNotes.md @@ -0,0 +1,17 @@ +# External dependencies notes + +1. Library `bc-fips-1.0.2.dll` has been downloaded from https://downloads.bouncycastle.org/fips-csharp/bc-fips-1.0.2.dll + +The checksums from this dll are: +- SHA1: eb3383b02549db0e927c9959531bf0f2d0d5ee5b +- SHA256: 8911c42df2fc915c3c63aace0defaf2b1063fea5f0e335e330c1b46ff7392a82 + +The library is under following license https://www.bouncycastle.org/about/license/#License which should be read as MIT license: + +> Copyright (c) 2000 - 2023 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) +> +> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/dependencies/bc-fips-1.0.2.dll b/dependencies/bc-fips-1.0.2.dll new file mode 100644 index 000000000..ff1c7f3b3 Binary files /dev/null and b/dependencies/bc-fips-1.0.2.dll differ