From 2eccd4167e90e21d38c5ba8019f5adace0f16351 Mon Sep 17 00:00:00 2001 From: idimov-keeper <78815270+idimov-keeper@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:08:55 -0500 Subject: [PATCH] Release .Net SDK v16.6.6 (#606) * KSM-360 - GHA to build and release strong named assemblies * KSM-490 - Switch some internal classes to public - for use in plugins * KSM-515 - Update to Bouncy Castle 2.4.0 * KSM-536 - Update to System.Text.Json 8.0.4 * KSM-517 - Add support for netstandard2.0 target * KSM-542 - Fix PowerShell module to allow dot in title --- .../workflows/publish.nuget.strong.named.yml | 72 +++++++++++ .github/workflows/publish.nuget.yml | 114 +++++++++++++++++- .../dotNet/HelloSecret/HelloSecret.csproj | 2 +- sdk/dotNet/.editorconfig | 10 ++ sdk/dotNet/README.md | 9 ++ .../SecretManagement.Keeper.Extension.psd1 | 6 +- .../SecretManagement.Keeper.Extension.psm1 | 7 +- .../SecretManagement.Keeper.csproj | 6 +- .../SecretManagement.Keeper.psd1 | 6 +- sdk/dotNet/SecretManagement.Keeper/build.ps1 | 18 ++- .../src/SecretManagement.cs | 53 +++++++- .../CryptoUtils.Test.cs | 72 +++++------ .../JsonUtils.Test.cs | 31 ++--- .../SecretsManager.Test.Core/Notation.Test.cs | 60 ++++----- .../SecretsManager.Test.Core.csproj | 16 ++- .../SecretsManagerClient.Test.cs | 28 ++--- sdk/dotNet/SecretsManager.sln | 5 + sdk/dotNet/SecretsManager/CryptoUtils.cs | 9 +- sdk/dotNet/SecretsManager/JsonUtils.cs | 2 +- .../SecretsManager/LocalConfigStorage.cs | 2 +- sdk/dotNet/SecretsManager/Notation.cs | 4 +- sdk/dotNet/SecretsManager/RecordData.cs | 4 +- .../SecretsManager/SecretsManager.csproj | 19 +-- .../SecretsManager/SecretsManagerClient.cs | 5 +- 24 files changed, 415 insertions(+), 145 deletions(-) create mode 100644 .github/workflows/publish.nuget.strong.named.yml create mode 100644 sdk/dotNet/.editorconfig diff --git a/.github/workflows/publish.nuget.strong.named.yml b/.github/workflows/publish.nuget.strong.named.yml new file mode 100644 index 00000000..9f1d3f5e --- /dev/null +++ b/.github/workflows/publish.nuget.strong.named.yml @@ -0,0 +1,72 @@ +name: Publish strong-named assemblies to NuGet + +on: + workflow_dispatch: + inputs: + publish: + description: 'Publish to NuGet (uncheck to build only)' + required: false + default: 'true' + type: boolean + + +jobs: + publish-nuget: + environment: prod + runs-on: ubuntu-latest + + defaults: + run: + shell: bash + working-directory: ./sdk/dotNet + + steps: + - name: Get the source code + uses: actions/checkout@v4 + + - name: Setup .NET 6 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 6.0.x + + - name: Retrieve secrets from KSM + id: ksmsecrets + uses: Keeper-Security/ksm-action@master + with: + keeper-secret-config: ${{ secrets.KSM_KSM_CONFIG }} + secrets: | + Sq4nnb5HXXNp1l6KryXynw/field/password > NUGET_AUTH_TOKEN + Sq4nnb5HXXNp1l6KryXynw/file/sgKSM.snk > file:${{ github.workspace }}/sdk/dotNet/SecretsManager/sgKSM.snk + + - name: Install dependencies + run: dotnet restore + + - name: "Preparing package for strong naming" + working-directory: ./SecretsManager + run: | + pwd + ls -lah + cp -f SecretsManager.csproj SecretsManager.StrongName.csproj + ls -lah + sed -i 's/Keeper.SecretsManager<\/PackageId>/Keeper.SecretsManager.StrongName<\/PackageId>/g' SecretsManager.StrongName.csproj + cat SecretsManager.StrongName.csproj + + - name: Build + working-directory: ./SecretsManager + run: | + pwd + ls -lah + dotnet build SecretsManager.StrongName.csproj --configuration Release --no-restore -p:SignKSM=True + + - name: Cleanup temp files + working-directory: ./SecretsManager + run: | + ls -lah + rm -f sgKSM.snk + + - name: Publish package + if: ${{ github.event.inputs.publish == 'true' }} + working-directory: ./SecretsManager + run: | + ls -lah ./bin/Release/ + dotnet nuget push bin/Release/*.nupkg --api-key ${{steps.ksmsecrets.outputs.NUGET_AUTH_TOKEN}} --source https://api.nuget.org/v3/index.json diff --git a/.github/workflows/publish.nuget.yml b/.github/workflows/publish.nuget.yml index 00e8c482..d4f0450a 100644 --- a/.github/workflows/publish.nuget.yml +++ b/.github/workflows/publish.nuget.yml @@ -1,6 +1,13 @@ name: Publish to NuGet + on: workflow_dispatch: + inputs: + publish: + description: 'Publish to NuGet (uncheck to build only)' + required: false + default: 'true' + type: boolean jobs: publish-nuget: @@ -9,23 +16,62 @@ jobs: defaults: run: + working-directory: ./sdk/dotNet steps: - name: Get the source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup .NET 6 - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: dotnet-version: 6.0.x + - name: Retrieve secrets from KSM + id: ksmsecrets + uses: Keeper-Security/ksm-action@master + with: + keeper-secret-config: ${{ secrets.KSM_KSM_CONFIG }} + secrets: | + Sq4nnb5HXXNp1l6KryXynw/field/password > NUGET_AUTH_TOKEN + - name: Install dependencies run: dotnet restore - name: Build run: dotnet build --configuration Release --no-restore + - name: Publish package + if: ${{ github.event.inputs.publish == 'true' }} + run: dotnet nuget push ./SecretsManager/bin/Release/*.nupkg --api-key ${{steps.ksmsecrets.outputs.NUGET_AUTH_TOKEN}} --source https://api.nuget.org/v3/index.json + + - name: Upload non-strong-named binaries + if: ${{ github.event.inputs.publish == 'false' }} + uses: actions/upload-artifact@v4 + with: + name: non-strong-named-binaries-${{ github.run_number }} + path: | + ${{ github.workspace }}/sdk/dotNet/SecretsManager/bin/Release/*.nupkg + + publish-nuget-strongname: + environment: prod + runs-on: windows-latest + + defaults: + run: + shell: powershell + working-directory: .\sdk\dotNet + + steps: + - name: Get the source code + uses: actions/checkout@v4 + + - name: Setup .NET 6 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 6.0.x + - name: Retrieve secrets from KSM id: ksmsecrets uses: Keeper-Security/ksm-action@master @@ -33,6 +79,68 @@ jobs: keeper-secret-config: ${{ secrets.KSM_KSM_CONFIG }} secrets: | Sq4nnb5HXXNp1l6KryXynw/field/password > NUGET_AUTH_TOKEN + Sq4nnb5HXXNp1l6KryXynw/file/sgKSM.snk > file:${{ github.workspace }}\sdk\dotNet\SecretsManager\sgKSM.snk + + - name: Extract and Update Public Key in SecretsManagerClient.cs + run: | + $snPath = "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\sn.exe" + $snkPath = "${{ github.workspace }}\sdk\dotNet\SecretsManager\sgKSM.snk" + $publicKeyPath = "${{ github.workspace }}\sdk\dotNet\SecretsManager\sgKSM.pub" + & $snPath -p $snkPath $publicKeyPath + $publicKeyInfo = & $snPath -tp $publicKeyPath + # Filter and join the lines of the public key + $publicKeyLines = $publicKeyInfo -split "`n" | Where-Object { $_ -match "^[a-f0-9\s]+$" } + $publicKey = $publicKeyLines -join "" -replace "\s", "" + + if (-not $publicKey) { + Write-Error "Failed to extract the full public key." + exit 1 + } + + Write-Output "Extracted Public Key: $publicKey" + $filePath = "${{ github.workspace }}\sdk\dotNet\SecretsManager\SecretsManagerClient.cs" + (Get-Content $filePath) -replace '\[assembly: InternalsVisibleTo\("SecretsManager.Test.Core"\)\]', "[assembly: InternalsVisibleTo(`"SecretsManager.Test.Core, PublicKey=$publicKey`")]" | Set-Content $filePath + Write-Output "First 20 lines of the modified SecretsManagerClient.cs:" + Get-Content $filePath -Head 20 + + - name: Install dependencies + run: dotnet restore + + - name: "Preparing package for strong naming" + working-directory: ${{ github.workspace }}\sdk\dotNet\SecretsManager\ + run: | + pwd + Get-ChildItem + Copy-Item -Path "SecretsManager.csproj" -Destination "SecretsManager.StrongName.csproj" + (Get-Content -Path "SecretsManager.StrongName.csproj") -replace 'Keeper.SecretsManager', 'Keeper.SecretsManager.StrongName' | Set-Content -Path "SecretsManager.StrongName.csproj" + Get-Content "SecretsManager.StrongName.csproj" + Copy-Item -Path "${{ github.workspace }}\sdk\dotNet\SecretsManager\sgKSM.snk" -Destination "${{ github.workspace }}\sdk\dotNet\SecretsManager.Test.Core\sgKSM.snk" + Get-ChildItem "${{ github.workspace }}\sdk\dotNet\SecretsManager.Test.Core\" + + - name: Build + working-directory: ${{ github.workspace }}\sdk\dotNet\SecretsManager\ + run: | + pwd + Get-ChildItem + dotnet build "SecretsManager.StrongName.csproj" --configuration Release --no-restore -p:SignKSM=True + + - name: Cleanup secret files + working-directory: ${{ github.workspace }}\sdk\dotNet\SecretsManager\ + run: | + Remove-Item -Path "${{ github.workspace }}\sdk\dotNet\SecretsManager\sgKSM.snk" + Remove-Item -Path "${{ github.workspace }}\sdk\dotNet\SecretsManager.Test.Core\sgKSM.snk" - name: Publish package - run: dotnet nuget push ./SecretsManager/bin/Release/*.nupkg --api-key ${{steps.ksmsecrets.outputs.NUGET_AUTH_TOKEN}} --source https://api.nuget.org/v3/index.json + if: ${{ github.event.inputs.publish == 'true' }} + working-directory: ${{ github.workspace }}\sdk\dotNet\SecretsManager\ + run: | + Get-ChildItem ".\bin\Release\" + dotnet nuget push ".\bin\Release\*.nupkg" --api-key ${{steps.ksmsecrets.outputs.NUGET_AUTH_TOKEN}} --source https://api.nuget.org/v3/index.json + + - name: Upload strong-named binaries + if: ${{ github.event.inputs.publish == 'false' }} + uses: actions/upload-artifact@v4 + with: + name: strong-named-binaries-${{ github.run_number }} + path: | + ${{ github.workspace }}\sdk\dotNet\SecretsManager\bin\Release\*.nupkg \ No newline at end of file diff --git a/examples/dotNet/HelloSecret/HelloSecret.csproj b/examples/dotNet/HelloSecret/HelloSecret.csproj index 51758ca3..7c938c57 100644 --- a/examples/dotNet/HelloSecret/HelloSecret.csproj +++ b/examples/dotNet/HelloSecret/HelloSecret.csproj @@ -6,7 +6,7 @@ - + diff --git a/sdk/dotNet/.editorconfig b/sdk/dotNet/.editorconfig new file mode 100644 index 00000000..692b3332 --- /dev/null +++ b/sdk/dotNet/.editorconfig @@ -0,0 +1,10 @@ +[*] + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = lf +insert_final_newline = true diff --git a/sdk/dotNet/README.md b/sdk/dotNet/README.md index 20220ace..8a77aa2b 100644 --- a/sdk/dotNet/README.md +++ b/sdk/dotNet/README.md @@ -3,6 +3,15 @@ # Change Log +## 16.6.6 + +* KSM-360 - GHA to build and release strong named assemblies +* KSM-490 - Switch some internal classes to public - for use in plugins +* KSM-515 - Update to Bouncy Castle 2.4.0 +* KSM-536 - Update to System.Text.Json 8.0.4 +* KSM-517 - Add support for netstandard2.0 target +* KSM-542 - Fix PowerShell module to allow dot in title + ## 16.6.5 * KSM-476 - fix public key parsing diff --git a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psd1 b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psd1 index 46074a60..ea4ea3c4 100644 --- a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psd1 +++ b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psd1 @@ -1,14 +1,14 @@ @{ - ModuleVersion = '16.6.4' + ModuleVersion = '16.6.6' RootModule = 'SecretManagement.Keeper.Extension.psm1' RequiredAssemblies = '../SecretManagement.Keeper.dll' CompatiblePSEditions = @('Core') GUID = '7ad471fa-c303-4e0f-8da7-4b4b6da380f9' Author = 'Sergey Aldoukhov' CompanyName = 'Keeper Security' - Copyright = '(c) 2023 Keeper Security, Inc.' + Copyright = '(c) 2024 Keeper Security, Inc.' FunctionsToExport = 'Set-Secret', 'Get-Secret', 'Remove-Secret', 'Get-SecretInfo', 'Test-SecretVault', 'Set-KeeperVault', 'Get-Notation' CmdletsToExport = @() VariablesToExport = @() AliasesToExport = @() -} \ No newline at end of file +} diff --git a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psm1 b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psm1 index e76368a3..54dd4d56 100644 --- a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psm1 +++ b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.Extension/SecretManagement.Keeper.Extension.psm1 @@ -3,14 +3,15 @@ function Get-Config { [string] $LocalVaultName ) $vaults = Microsoft.Powershell.SecretManagement\Get-SecretVault - $localVault = $vaults.Where( { $_.Name -eq $LocalVaultName } ) + $localVault = $vaults.Where( { $_.Name -eq $LocalVaultName } ) # SecretStore/LocalStore if (!$localVault) { return $null } + $moduleInstance = Import-Module -Name $localVault.ModuleName -PassThru - $configSecretName = 'KeeperVault.' + $VaultName + $configSecretName = 'KeeperVault.' + $VaultName # passed by SecretStore while enumerating registered vaults $config = & $moduleInstance Get-Secret -Name $configSecretName -VaultName $localVault.Name - if ($config -isnot [Hashtable]) { + if ($config -isnot [Hashtable]) { $config = $config[0] # SecretStore returns a List } return $config diff --git a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.csproj b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.csproj index b1d1dcee..090d492d 100644 --- a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.csproj +++ b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.csproj @@ -1,11 +1,11 @@  - netstandard2.1 + netstandard2.0 SecretManagement.Keeper true - 16.6.4 - 16.6.4 + 16.6.6 + 16.6.6 diff --git a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.psd1 b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.psd1 index 15685c93..2b4d59ca 100644 --- a/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.psd1 +++ b/sdk/dotNet/SecretManagement.Keeper/SecretManagement.Keeper.psd1 @@ -1,10 +1,10 @@ @{ - ModuleVersion = '16.6.4' + ModuleVersion = '16.6.6' CompatiblePSEditions = @('Core') GUID = '20ab89cb-f0dd-4e8e-b276-f3a7708c1eb2' Author = 'Sergey Aldoukhov' CompanyName = 'Keeper Security' - Copyright = '(c) 2023 Keeper Security, Inc.' + Copyright = '(c) 2024 Keeper Security, Inc.' Description = 'SecretManagement extension vault for Keeper' RootModule = './SecretManagement.Keeper.psm1' NestedModules = @('./SecretManagement.Keeper.Extension') @@ -22,4 +22,4 @@ ReleaseNotes = 'Bug fixes and improvements' } } -} \ No newline at end of file +} diff --git a/sdk/dotNet/SecretManagement.Keeper/build.ps1 b/sdk/dotNet/SecretManagement.Keeper/build.ps1 index f8b94d3e..b986b263 100644 --- a/sdk/dotNet/SecretManagement.Keeper/build.ps1 +++ b/sdk/dotNet/SecretManagement.Keeper/build.ps1 @@ -28,9 +28,19 @@ if ($Package) { } @( - './bin/Release/netstandard2.1/SecretManagement.Keeper.dll' - './bin/Release/netstandard2.1/SecretsManager.dll' - './bin/Release/netstandard2.1/BouncyCastle.Cryptography.dll' + './bin/Release/netstandard2.0/SecretsManager.dll' + './bin/Release/netstandard2.0/SecretManagement.Keeper.dll' + './bin/Release/netstandard2.0/SecretManagement.Keeper.deps.json' + './bin/Release/netstandard2.0/BouncyCastle.Cryptography.dll' + './bin/Release/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.dll' + './bin/Release/netstandard2.0/System.Buffers.dll' + './bin/Release/netstandard2.0/System.Management.Automation.dll' + './bin/Release/netstandard2.0/System.Memory.dll' + './bin/Release/netstandard2.0/System.Numerics.Vectors.dll' + './bin/Release/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll' + './bin/Release/netstandard2.0/System.Text.Encodings.Web.dll' + './bin/Release/netstandard2.0/System.Text.Json.dll' + './bin/Release/netstandard2.0/System.Threading.Tasks.Extensions.dll' ) | ForEach-Object { Copy-Item -Path $_ -Destination $outDir -Force } @@ -45,4 +55,4 @@ if ($Publish) { Publish-Module -Path ./out/SecretManagement.Keeper -NuGetApiKey $APIKey -Verbose } -Pop-Location \ No newline at end of file +Pop-Location diff --git a/sdk/dotNet/SecretManagement.Keeper/src/SecretManagement.cs b/sdk/dotNet/SecretManagement.Keeper/src/SecretManagement.cs index 345f5970..73780dbf 100644 --- a/sdk/dotNet/SecretManagement.Keeper/src/SecretManagement.cs +++ b/sdk/dotNet/SecretManagement.Keeper/src/SecretManagement.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO; using System.Linq; using System.Management.Automation; +using System.Text; using System.Text.Json; using System.Threading.Tasks; @@ -19,7 +21,7 @@ public static async Task GetVaultConfigFromToken(string oneTimeTok SecretsManagerClient.InitializeStorage(storage, oneTimeToken); try { - await SecretsManagerClient.GetSecrets(new SecretsManagerOptions(storage)); + await SecretsManagerClient.GetSecrets(new SecretsManagerOptions(storage), new string[] { new string('A', 22) }); } catch (Exception e) { @@ -34,7 +36,7 @@ public static async Task GetVaultConfigFromConfigString(string con var storage = new InMemoryStorage(config); try { - await SecretsManagerClient.GetSecrets(new SecretsManagerOptions(storage)); + await SecretsManagerClient.GetSecrets(new SecretsManagerOptions(storage), new string[] { new string('A', 22) }); } catch (Exception e) { @@ -75,9 +77,52 @@ public static KeeperResult Error(string errorMsg) } } + private const char EscapeChar = '\\'; + private static readonly char[] EscapedChars = { '\\', '.' }; + private static string[] ParseQuery(string query) + { + // escape char to be used only in title and only when title has dot(s) + if (string.IsNullOrEmpty(query)) return new string[] { "" }; + if (!query.Contains('.') || !query.Contains(EscapeChar)) return query.Split(new[] { '.' }, 2); + + int pos = 0; + bool inEscSequence = query[pos].Equals(EscapeChar); + StringBuilder title = inEscSequence ? new StringBuilder() : new StringBuilder(query[pos].ToString()); + while (++pos <= query.Length) + { + if (pos >= query.Length) + { + // unfinished escape sequence - treat last EscapeChar as regular character + if (inEscSequence) title.Append(EscapeChar); + break; + } + if (inEscSequence) + { + // \N (where N not in EscapedChars) is bad esc sequence but treat \ as single non esc char + if (!EscapedChars.Contains(query[pos])) + title.Append(EscapeChar); + title.Append(query[pos]); + inEscSequence = false; + } + else + { + inEscSequence = query[pos].Equals(EscapeChar); + if (!inEscSequence) + { + if ('.'.Equals(query[pos])) break; + title.Append(query[pos]); + } + } + } + + // return cleaned up title with escape chars removed + if (pos >= query.Length) return new string[] { title.ToString() }; + return new string[] { title.ToString(), query.Substring(pos + 1) }; + } + public static async Task GetSecret(string name, Hashtable config) { - var parts = name.Split(new[] { '.' }, 2); + var parts = ParseQuery(name); var (records, _) = await GetKeeperSecrets(config); var found = records.FirstOrDefault(x => x.RecordUid == parts[0] || x.Data.title == parts[0]); if (found == null) @@ -159,7 +204,7 @@ public static async Task GetSecretsInfo(string filter, Hashtable confi public static async Task SetSecret(string name, object secret, Hashtable config) { - var parts = name.Split('.'); + var parts = ParseQuery(name); if (parts.Length == 1) { return KeeperResult.Error("Set-Secret can be used only on a single field"); diff --git a/sdk/dotNet/SecretsManager.Test.Core/CryptoUtils.Test.cs b/sdk/dotNet/SecretsManager.Test.Core/CryptoUtils.Test.cs index 63c08e5c..da61fdd0 100644 --- a/sdk/dotNet/SecretsManager.Test.Core/CryptoUtils.Test.cs +++ b/sdk/dotNet/SecretsManager.Test.Core/CryptoUtils.Test.cs @@ -12,33 +12,33 @@ public void PrivateKeyIsCompatible() const string fakeExternalPrivateKey64String = "MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg34GXYbMpXKaHcHZW4dIMO3WYU8zTjB6t+41SRsY1rwqgCgYIKoZIzj0DAQehRANCAAQGH/4ZwpGR9B7AlMjVY7ekpjOcaD3rhuO25CmOZxI3wqRDdhXQIdDnuWvQPCZ3ymtjL3C8JrVIcloklwYI9T7+"; var fakeExternalPrivateKey64Bytes = CryptoUtils.Base64ToBytes(fakeExternalPrivateKey64String); var fakeExportedPublicKey = CryptoUtils.ExportPublicKey(fakeExternalPrivateKey64Bytes); - Assert.AreEqual("BAYf/hnCkZH0HsCUyNVjt6SmM5xoPeuG47bkKY5nEjfCpEN2FdAh0Oe5a9A8JnfKa2MvcLwmtUhyWiSXBgj1Pv4=", CryptoUtils.BytesToBase64(fakeExportedPublicKey)); + Assert.That("BAYf/hnCkZH0HsCUyNVjt6SmM5xoPeuG47bkKY5nEjfCpEN2FdAh0Oe5a9A8JnfKa2MvcLwmtUhyWiSXBgj1Pv4=", Is.EqualTo(CryptoUtils.BytesToBase64(fakeExportedPublicKey))); } [Test] public void PrivateKeySizeIs150() { var privateKey = CryptoUtils.GenerateKeyPair(); - Assert.AreEqual(150, privateKey.Length); + Assert.That(150, Is.EqualTo(privateKey.Length)); } [Test] public void TestPasswordGeneration() { var password = CryptoUtils.GeneratePassword(); - Assert.AreEqual(32, password?.Length); + Assert.That(32, Is.EqualTo(password?.Length)); password = CryptoUtils.GeneratePassword(32, 32); - Assert.IsTrue(Regex.IsMatch(password, @"^[a-z]{32}$")); + Assert.That(Regex.IsMatch(password, @"^[a-z]{32}$")); password = CryptoUtils.GeneratePassword(32, 0, 32); - Assert.IsTrue(Regex.IsMatch(password, @"^[A-Z]{32}$")); + Assert.That(Regex.IsMatch(password, @"^[A-Z]{32}$")); password = CryptoUtils.GeneratePassword(32, 0, 0, 32); - Assert.IsTrue(Regex.IsMatch(password, @"^[0-9]{32}$")); + Assert.That(Regex.IsMatch(password, @"^[0-9]{32}$")); password = CryptoUtils.GeneratePassword(32, 0, 0, 0, 32); - Assert.IsTrue(Regex.IsMatch(password, @"^[""!@#$%()+;<>=?[\\\]{}^.,]{32}$")); + Assert.That(Regex.IsMatch(password, @"^[""!@#$%()+;<>=?[\\\]{}^.,]{32}$")); password = CryptoUtils.GeneratePassword(64, 16, 16, 16, 16); var chars = password.ToCharArray(); @@ -50,7 +50,7 @@ public void TestPasswordGeneration() return groupCompare == 0 ? x.CompareTo(y) : groupCompare; }); password = new string(chars); - Assert.IsTrue(Regex.IsMatch(password, @"^[a-z]{16}[A-Z]{16}[0-9]{16}[""!@#$%()+;<>=?[\\\]{}^.,]{16}$")); + Assert.That(Regex.IsMatch(password, @"^[a-z]{16}[A-Z]{16}[0-9]{16}[""!@#$%()+;<>=?[\\\]{}^.,]{16}$")); } [Test] @@ -59,7 +59,7 @@ public void TestTotpDefaultAlgorithm() // {Algorithm: "", Period: 30, UnixTime: 20000000000, Secret: "12345678901234567890", Digits: 8}, Output: "65353130"} const string url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=&digits=8&period=30"; var totp = CryptoUtils.GetTotpCode(url, 20000000000); - Assert.AreEqual("65353130", totp?.Code); // using default algorithm SHA1 + Assert.That("65353130", Is.EqualTo(totp?.Code)); // using default algorithm SHA1 } [Test] @@ -68,7 +68,7 @@ public void TestTotpDefaultDigits() // { Algorithm: "SHA1", Period: 30, UnixTime: 20000000000, Secret: "12345678901234567890", Digits: 0}, Output: "353130"} const string url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=0&period=30"; var totp = CryptoUtils.GetTotpCode(url, 20000000000); - Assert.AreEqual("353130", totp?.Code); // using default digits = 6 + Assert.That("353130", Is.EqualTo(totp?.Code)); // using default digits = 6 } [Test] @@ -77,7 +77,7 @@ public void TestTotpDefaultPeriod() // {Algorithm: "SHA1", Period: 0, UnixTime: 20000000000, Secret: "12345678901234567890", Digits: 8}, Output: "65353130"} const string url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=8&period=0"; var totp = CryptoUtils.GetTotpCode(url, 20000000000); - Assert.AreEqual("65353130", totp?.Code); // using default period = 30 + Assert.That("65353130", Is.EqualTo(totp?.Code)); // using default period = 30 } [Test] @@ -86,7 +86,7 @@ public void TestTotpEmptySecret() // {Algorithm: "SHA1", Period: 30, UnixTime: 0, Secret: "", Digits: 8}, Output: "no secret key provided"} const string url = "otpauth://totp/ACME:john.doe@email.com?secret=&issuer=ACME&algorithm=SHA1&digits=8&period=30"; var totp = CryptoUtils.GetTotpCode(url); - Assert.Null(totp, "Empty secret shouldn't produce valid TOTP"); + Assert.That(totp, Is.Null, "Empty secret shouldn't produce valid TOTP"); } [Test] @@ -95,7 +95,7 @@ public void TestTotpInvalidAlgorithm() // { Algorithm: "SHA1024", Period: 30, UnixTime: 0, Secret: "12345678901234567890", Digits: 8}, Output: "invalid algorithm - use one of SHA1/SHA256/SHA512"} const string url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1024&digits=8&period=30"; var totp = CryptoUtils.GetTotpCode(url); - Assert.Null(totp, "SHA1024 is unsupported algorithm for TOTP"); + Assert.That(totp, Is.Null, "SHA1024 is unsupported algorithm for TOTP"); } [Test] @@ -104,7 +104,7 @@ public void TestTotpInvalidSecret() // { Algorithm: "SHA1", Period: 30, UnixTime: 0, Secret: "1NVAL1D", Digits: 8}, Output: "bad secret key"} const string url = "otpauth://totp/ACME:john.doe@email.com?secret=1NVAL1D&issuer=ACME&algorithm=SHA1&digits=8&period=30"; var totp = CryptoUtils.GetTotpCode(url); - Assert.Null(totp, "Invalid secret shouldn't produce valid TOTP"); + Assert.That(totp, Is.Null, "Invalid secret shouldn't produce valid TOTP"); } [Test] @@ -114,86 +114,86 @@ public void TestTotp() // {Algorithm: "SHA1", Period: 30, UnixTime: 59, Secret: "12345678901234567890", Digits: 8}, Output: "94287082"} string url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=8&period=30"; var totp = CryptoUtils.GetTotpCode(url, 59); - Assert.AreEqual("94287082", totp?.Code); - Assert.AreEqual(1, totp?.TimeLeft); + Assert.That("94287082", Is.EqualTo(totp?.Code)); + Assert.That(1, Is.EqualTo(totp?.TimeLeft)); // {Algorithm: "SHA256", Period: 30, UnixTime: 59, Secret: "12345678901234567890123456789012", Digits: 8}, Output: "46119246"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA&issuer=ACME&algorithm=SHA256&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 59); - Assert.AreEqual("46119246", totp?.Code); - Assert.AreEqual(1, totp?.TimeLeft); + Assert.That("46119246", Is.EqualTo(totp?.Code)); + Assert.That(1, Is.EqualTo(totp?.TimeLeft)); // {Algorithm: "SHA512", Period: 30, UnixTime: 59, Secret: "1234567890123456789012345678901234567890123456789012345678901234", Digits: 8}, Output: "90693936"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA=&issuer=ACME&algorithm=SHA512&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 59); - Assert.AreEqual("90693936", totp?.Code); - Assert.AreEqual(1, totp?.TimeLeft); + Assert.That("90693936", Is.EqualTo(totp?.Code)); + Assert.That(1, Is.EqualTo(totp?.TimeLeft)); // Check different periods - 1 sec. before split // {Algorithm: "SHA1", Period: 30, UnixTime: 1111111109, Secret: "12345678901234567890", Digits: 8}, Output: "07081804"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 1111111109); - Assert.AreEqual("07081804", totp?.Code); + Assert.That("07081804", Is.EqualTo(totp?.Code)); // {Algorithm: "SHA256", Period: 30, UnixTime: 1111111109, Secret: "12345678901234567890123456789012", Digits: 8}, Output: "68084774"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA&issuer=ACME&algorithm=SHA256&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 1111111109); - Assert.AreEqual("68084774", totp?.Code); + Assert.That("68084774", Is.EqualTo(totp?.Code)); // {Algorithm: "SHA512", Period: 30, UnixTime: 1111111109, Secret: "1234567890123456789012345678901234567890123456789012345678901234", Digits: 8}, Output: "25091201"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA=&issuer=ACME&algorithm=SHA512&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 1111111109); - Assert.AreEqual("25091201", totp?.Code); + Assert.That("25091201", Is.EqualTo(totp?.Code)); // Check different periods - 1 sec. after split // {Algorithm: "SHA1", Period: 30, UnixTime: 1111111111, Secret: "12345678901234567890", Digits: 8}, Output: "14050471"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 1111111111); - Assert.AreEqual("14050471", totp?.Code); + Assert.That("14050471", Is.EqualTo(totp?.Code)); // {Algorithm: "SHA256", Period: 30, UnixTime: 1111111111, Secret: "12345678901234567890123456789012", Digits: 8}, Output: "67062674"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA&issuer=ACME&algorithm=SHA256&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 1111111111); - Assert.AreEqual("67062674", totp?.Code); + Assert.That("67062674", Is.EqualTo(totp?.Code)); // {Algorithm: "SHA512", Period: 30, UnixTime: 1111111111, Secret: "1234567890123456789012345678901234567890123456789012345678901234", Digits: 8}, Output: "99943326"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA=&issuer=ACME&algorithm=SHA512&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 1111111111); - Assert.AreEqual("99943326", totp?.Code); + Assert.That("99943326", Is.EqualTo(totp?.Code)); // Check different time periods // {Algorithm: "SHA1", Period: 30, UnixTime: 1234567890, Secret: "12345678901234567890", Digits: 8}, Output: "89005924"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 1234567890); - Assert.AreEqual("89005924", totp?.Code); + Assert.That("89005924", Is.EqualTo(totp?.Code)); // {Algorithm: "SHA256", Period: 30, UnixTime: 1234567890, Secret: "12345678901234567890123456789012", Digits: 8}, Output: "91819424"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA&issuer=ACME&algorithm=SHA256&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 1234567890); - Assert.AreEqual("91819424", totp?.Code); + Assert.That("91819424", Is.EqualTo(totp?.Code)); // {Algorithm: "SHA512", Period: 30, UnixTime: 1234567890, Secret: "1234567890123456789012345678901234567890123456789012345678901234", Digits: 8}, Output: "93441116"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA=&issuer=ACME&algorithm=SHA512&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 1234567890); - Assert.AreEqual("93441116", totp?.Code); + Assert.That("93441116", Is.EqualTo(totp?.Code)); // {Algorithm: "SHA1", Period: 30, UnixTime: 2000000000, Secret: "12345678901234567890", Digits: 8}, Output: "69279037"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 2000000000); - Assert.AreEqual("69279037", totp?.Code); + Assert.That("69279037", Is.EqualTo(totp?.Code)); // {Algorithm: "SHA256", Period: 30, UnixTime: 2000000000, Secret: "12345678901234567890123456789012", Digits: 8}, Output: "90698825"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA&issuer=ACME&algorithm=SHA256&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 2000000000); - Assert.AreEqual("90698825", totp?.Code); + Assert.That("90698825", Is.EqualTo(totp?.Code)); // {Algorithm: "SHA512", Period: 30, UnixTime: 2000000000, Secret: "1234567890123456789012345678901234567890123456789012345678901234", Digits: 8}, Output: "38618901"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA=&issuer=ACME&algorithm=SHA512&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 2000000000); - Assert.AreEqual("38618901", totp?.Code); + Assert.That("38618901", Is.EqualTo(totp?.Code)); // {Algorithm: "SHA1", Period: 30, UnixTime: 20000000000, Secret: "12345678901234567890", Digits: 8}, Output: "65353130"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=ACME&algorithm=SHA1&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 20000000000); - Assert.AreEqual("65353130", totp?.Code); + Assert.That("65353130", Is.EqualTo(totp?.Code)); // {Algorithm: "SHA256", Period: 30, UnixTime: 20000000000, Secret: "12345678901234567890123456789012", Digits: 8}, Output: "77737706"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA&issuer=ACME&algorithm=SHA256&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 20000000000); - Assert.AreEqual("77737706", totp?.Code); + Assert.That("77737706", Is.EqualTo(totp?.Code)); // {Algorithm: "SHA512", Period: 30, UnixTime: 20000000000, Secret: "1234567890123456789012345678901234567890123456789012345678901234", Digits: 8}, Output: "47863826"} url = "otpauth://totp/ACME:john.doe@email.com?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA=&issuer=ACME&algorithm=SHA512&digits=8&period=30"; totp = CryptoUtils.GetTotpCode(url, 20000000000); - Assert.AreEqual("47863826", totp?.Code); + Assert.That("47863826", Is.EqualTo(totp?.Code)); } } -} \ No newline at end of file +} diff --git a/sdk/dotNet/SecretsManager.Test.Core/JsonUtils.Test.cs b/sdk/dotNet/SecretsManager.Test.Core/JsonUtils.Test.cs index 94567399..7ac408fd 100644 --- a/sdk/dotNet/SecretsManager.Test.Core/JsonUtils.Test.cs +++ b/sdk/dotNet/SecretsManager.Test.Core/JsonUtils.Test.cs @@ -7,26 +7,27 @@ public class JsonUtilsTests [Test] public void ParseAndSerializeShouldNotChangeTheData() { - var rec = new KeeperRecordData { - type = "Login2", + var rec = new KeeperRecordData + { + type = "Login2", title = "MyHomeLogin", notes = "MyNotes", - fields = new KeeperRecordField[] { - new KeeperRecordField { type = "login", label = "Login", value = new string[] { "Login 1" }, required=true, privacyScreen=true }, - new KeeperRecordField { type = "password", label = "Password", value = new string[] { "3[OJ%sc7n].wX6+k5GY)6" }, required=true, privacyScreen=true, enforceGeneration=true, complexity=new PasswordComplexity{ length=21, caps=5, lowercase=5, digits=5, special=5} }, - new KeeperRecordField { type = "url", label = "URL", value = new string[] { "https://asdfjkasdfkdsa.com" }, required=true, privacyScreen=true }, - new KeeperRecordField { type = "securityQuestion", label = "Security Question & Answer", value = new SecurityQuestion[] { new SecurityQuestion { question= "asdf", answer= "asdf" } }, required=true, privacyScreen=true }, - new KeeperRecordField { type = "fileRef", label = "File or Photo", value = new object[] { } }, - new KeeperRecordField { type = "oneTimeCode", label = "Two-Factor Code", value = new object[] { } }, - }, - custom = new KeeperRecordField[] { } + fields = new KeeperRecordField[] { + new KeeperRecordField { type = "login", label = "Login", value = new string[] { "Login 1" }, required=true, privacyScreen=true }, + new KeeperRecordField { type = "password", label = "Password", value = new string[] { "3[OJ%sc7n].wX6+k5GY)6" }, required=true, privacyScreen=true, enforceGeneration=true, complexity=new PasswordComplexity{ length=21, caps=5, lowercase=5, digits=5, special=5} }, + new KeeperRecordField { type = "url", label = "URL", value = new string[] { "https://asdfjkasdfkdsa.com" }, required=true, privacyScreen=true }, + new KeeperRecordField { type = "securityQuestion", label = "Security Question & Answer", value = new SecurityQuestion[] { new SecurityQuestion { question= "asdf", answer= "asdf" } }, required=true, privacyScreen=true }, + new KeeperRecordField { type = "fileRef", label = "File or Photo", value = new object[] { } }, + new KeeperRecordField { type = "oneTimeCode", label = "Two-Factor Code", value = new object[] { } }, + }, + custom = new KeeperRecordField[] { } }; var jsonIn = CryptoUtils.BytesToString(JsonUtils.SerializeJson(rec)); //const string jsonIn = "{\"title\":\"MyHomeLogin\",\"type\":\"Login2\",\"fields\":[{\"type\":\"login\",\"label\":\"Login\",\"value\":[\"Login 1\"],\"required\":true,\"privacyScreen\":true},{\"type\":\"password\",\"label\":\"Password\",\"value\":[\"3[OJ%sc7n].wX6+k5GY)6\"],\"required\":true,\"privacyScreen\":true,\"enforceGeneration\":true,\"complexity\":{\"length\":21,\"caps\":5,\"lowercase\":5,\"digits\":5,\"special\":5}},{\"type\":\"url\",\"label\":\"URL\",\"value\":[\"https://asdfjkasdfkdsa.com\"],\"required\":true,\"privacyScreen\":true},{\"type\":\"securityQuestion\",\"label\":\"Security Question & Answer\",\"value\":[{\"question\":\"asdf\",\"answer\":\"asdf\"}],\"required\":true,\"privacyScreen\":true},{\"type\":\"fileRef\",\"label\":\"File or Photo\",\"value\":[]},{\"type\":\"oneTimeCode\",\"label\":\"Two-Factor Code\",\"value\":[]}],\"custom\":[],\"notes\":\"MyNotes\"}"; var recordData = JsonUtils.ParseJson(CryptoUtils.StringToBytes(jsonIn)); var jsonOut = CryptoUtils.BytesToString(JsonUtils.SerializeJson(recordData)); - Assert.AreEqual(jsonIn, jsonOut); - Assert.AreEqual(recordData.fields[1].value[0].ToString(), rec.fields[1].value[0]); + Assert.That(jsonIn, Is.EqualTo(jsonOut)); + Assert.That(recordData.fields[1].value[0].ToString(), Is.EqualTo(rec.fields[1].value[0])); } [Test] public void ParseAndSerializeShouldPreserveDiacritics() @@ -34,7 +35,7 @@ public void ParseAndSerializeShouldPreserveDiacritics() string recordTitle = "MySpéciàlHomèL°gin"; var krdin = new KeeperRecordData { title = recordTitle, type = "login" }; var krdout = JsonUtils.ParseJson(JsonUtils.SerializeJson(krdin)); - Assert.AreEqual(krdin.title, krdout.title); + Assert.That(krdin.title, Is.EqualTo(krdout.title)); } } -} \ No newline at end of file +} diff --git a/sdk/dotNet/SecretsManager.Test.Core/Notation.Test.cs b/sdk/dotNet/SecretsManager.Test.Core/Notation.Test.cs index 50e8ecca..3f016ba2 100644 --- a/sdk/dotNet/SecretsManager.Test.Core/Notation.Test.cs +++ b/sdk/dotNet/SecretsManager.Test.Core/Notation.Test.cs @@ -19,13 +19,13 @@ public void NotationsWork() }); var value = Notation.GetValue(secrets, $"keeper://{RecordUid}/field/login"); - Assert.AreEqual("My Login 1", value); + Assert.That("My Login 1", Is.EqualTo(value)); value = Notation.GetValue(secrets, $"{RecordUid}/field/login"); - Assert.AreEqual("My Login 1", value); + Assert.That("My Login 1", Is.EqualTo(value)); value = Notation.GetValue(secrets, $"keeper://{RecordUid}/field/login[0]"); - Assert.AreEqual("My Login 1", value); + Assert.That("My Login 1", Is.EqualTo(value)); try { @@ -38,34 +38,34 @@ public void NotationsWork() } value = Notation.GetValue(secrets, $"keeper://{RecordUid}/field/login[]"); - Assert.AreEqual("[\"My Login 1\"]", value); + Assert.That("[\"My Login 1\"]", Is.EqualTo(value)); value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/My Custom 1"); - Assert.AreEqual("custom1", value); + Assert.That("custom1", Is.EqualTo(value)); value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/My Custom 2"); - Assert.AreEqual("one", value); + Assert.That("one", Is.EqualTo(value)); value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/My Custom 2[1]"); - Assert.AreEqual("two", value); + Assert.That("two", Is.EqualTo(value)); value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/My Custom 2[]"); - Assert.AreEqual("[\"one\",\"two\",\"three\"]", value); + Assert.That("[\"one\",\"two\",\"three\"]", Is.EqualTo(value)); value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/phone[0][number]"); - Assert.AreEqual("555-5555555", value); + Assert.That("555-5555555", Is.EqualTo(value)); value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/phone[1][number]"); - Assert.AreEqual("777-7777777", value); + Assert.That("777-7777777", Is.EqualTo(value)); value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/phone[2]"); - Assert.AreEqual("{\"number\":\"888-8888888\",\"ext\":\"\",\"type\":\"Home\"}", value); + Assert.That("{\"number\":\"888-8888888\",\"ext\":\"\",\"type\":\"Home\"}", Is.EqualTo(value)); value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/name[first]"); - Assert.AreEqual("Jenny", value); + Assert.That("Jenny", Is.EqualTo(value)); value = Notation.GetValue(secrets, $"keeper://{RecordUid}/custom_field/name[last]"); - Assert.AreEqual("Smith", value); + Assert.That("Smith", Is.EqualTo(value)); } [Test] @@ -86,35 +86,35 @@ public void NotationParserWork() catch (Exception) { } var res = Notation.ParseNotation("/type"); - Assert.AreEqual("type", res[2].Text.Item1); + Assert.That("type", Is.EqualTo(res[2].Text.Item1)); res = Notation.ParseNotation("/title"); - Assert.AreEqual("title", res[2].Text.Item1); + Assert.That("title", Is.EqualTo(res[2].Text.Item1)); res = Notation.ParseNotation("/notes"); - Assert.AreEqual("notes", res[2].Text.Item1); + Assert.That("notes", Is.EqualTo(res[2].Text.Item1)); res = Notation.ParseNotation("/file/filename.ext"); - Assert.AreEqual("file", res[2].Text.Item1); - Assert.AreEqual("filename.ext", res[2].Parameter.Item1); + Assert.That("file", Is.EqualTo(res[2].Text.Item1)); + Assert.That("filename.ext", Is.EqualTo(res[2].Parameter.Item1)); res = Notation.ParseNotation("/field/text"); - Assert.AreEqual("field", res[2].Text.Item1); - Assert.AreEqual("text", res[2].Parameter.Item1); + Assert.That("field", Is.EqualTo(res[2].Text.Item1)); + Assert.That("text", Is.EqualTo(res[2].Parameter.Item1)); res = Notation.ParseNotation(@"/custom_field/label with \[[0][middle]"); - Assert.AreEqual("", res[1].Text.Item1); // empty title - Assert.AreEqual("custom_field", res[2].Text.Item1); - Assert.AreEqual(@"label with [", res[2].Parameter.Item1); - Assert.AreEqual("0", res[2].Index1.Item1); - Assert.AreEqual("middle", res[2].Index2.Item1); + Assert.That("", Is.EqualTo(res[1].Text.Item1)); // empty title + Assert.That("custom_field", Is.EqualTo(res[2].Text.Item1)); + Assert.That(@"label with [", Is.EqualTo(res[2].Parameter.Item1)); + Assert.That("0", Is.EqualTo(res[2].Index1.Item1)); + Assert.That("middle", Is.EqualTo(res[2].Index2.Item1)); res = Notation.ParseNotation(@"title with \[\]\//custom_field/label with \[[0][middle]"); - Assert.AreEqual(@"title with []/", res[1].Text.Item1); - Assert.AreEqual("custom_field", res[2].Text.Item1); - Assert.AreEqual(@"label with [", res[2].Parameter.Item1); - Assert.AreEqual("0", res[2].Index1.Item1); - Assert.AreEqual("middle", res[2].Index2.Item1); + Assert.That(@"title with []/", Is.EqualTo(res[1].Text.Item1)); + Assert.That("custom_field", Is.EqualTo(res[2].Text.Item1)); + Assert.That(@"label with [", Is.EqualTo(res[2].Parameter.Item1)); + Assert.That("0", Is.EqualTo(res[2].Index1.Item1)); + Assert.That("middle", Is.EqualTo(res[2].Index2.Item1)); } } } diff --git a/sdk/dotNet/SecretsManager.Test.Core/SecretsManager.Test.Core.csproj b/sdk/dotNet/SecretsManager.Test.Core/SecretsManager.Test.Core.csproj index 598ad363..20aa10c6 100644 --- a/sdk/dotNet/SecretsManager.Test.Core/SecretsManager.Test.Core.csproj +++ b/sdk/dotNet/SecretsManager.Test.Core/SecretsManager.Test.Core.csproj @@ -9,14 +9,22 @@ - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + True + sgKSM.snk + + diff --git a/sdk/dotNet/SecretsManager.Test.Core/SecretsManagerClient.Test.cs b/sdk/dotNet/SecretsManager.Test.Core/SecretsManagerClient.Test.cs index c8c6cbc4..b16d43fa 100644 --- a/sdk/dotNet/SecretsManager.Test.Core/SecretsManagerClient.Test.cs +++ b/sdk/dotNet/SecretsManager.Test.Core/SecretsManagerClient.Test.cs @@ -36,14 +36,14 @@ Task TestPostFunction(string s, TransmissionKey transmission var storage = new LocalConfigStorage(); var fakeOneTimeCode = "YyIhK5wXFHj36wGBAOmBsxI3v5rIruINrC8KXjyM58c"; - + SecretsManagerClient.InitializeStorage(storage, fakeOneTimeCode, "fake.keepersecurity.com"); - + var options = new SecretsManagerOptions(storage, TestPostFunction); var secrets = await SecretsManagerClient.GetSecrets(options); var password = secrets.Records[1].FieldValue("password").ToString(); // ReSharper disable once StringLiteralTypo - Assert.AreEqual("Lex1S++Wx6g^,LC.(Vp<", password); + Assert.That("Lex1S++Wx6g^,LC.(Vp<", Is.EqualTo(password)); } [Test] @@ -51,35 +51,35 @@ public void TestStoragePrefixes() { var storage = new InMemoryStorage(); SecretsManagerClient.InitializeStorage(storage, "US:ONE_TIME_TOKEN"); - Assert.AreEqual("keepersecurity.com", storage.GetString("hostname")); + Assert.That("keepersecurity.com", Is.EqualTo(storage.GetString("hostname"))); storage = new InMemoryStorage(); SecretsManagerClient.InitializeStorage(storage, "EU:ONE_TIME_TOKEN"); - Assert.AreEqual("keepersecurity.eu", storage.GetString("hostname")); + Assert.That("keepersecurity.eu", Is.EqualTo(storage.GetString("hostname"))); storage = new InMemoryStorage(); SecretsManagerClient.InitializeStorage(storage, "AU:ONE_TIME_TOKEN"); - Assert.AreEqual("keepersecurity.com.au", storage.GetString("hostname")); + Assert.That("keepersecurity.com.au", Is.EqualTo(storage.GetString("hostname"))); storage = new InMemoryStorage(); SecretsManagerClient.InitializeStorage(storage, "GOV:ONE_TIME_TOKEN"); - Assert.AreEqual("govcloud.keepersecurity.us", storage.GetString("hostname")); + Assert.That("govcloud.keepersecurity.us", Is.EqualTo(storage.GetString("hostname"))); storage = new InMemoryStorage(); SecretsManagerClient.InitializeStorage(storage, "JP:ONE_TIME_TOKEN"); - Assert.AreEqual("keepersecurity.jp", storage.GetString("hostname")); + Assert.That("keepersecurity.jp", Is.EqualTo(storage.GetString("hostname"))); storage = new InMemoryStorage(); SecretsManagerClient.InitializeStorage(storage, "CA:ONE_TIME_TOKEN"); - Assert.AreEqual("keepersecurity.ca", storage.GetString("hostname")); + Assert.That("keepersecurity.ca", Is.EqualTo(storage.GetString("hostname"))); storage = new InMemoryStorage(); SecretsManagerClient.InitializeStorage(storage, "eu:ONE_TIME_TOKEN"); - Assert.AreEqual("keepersecurity.eu", storage.GetString("hostname")); + Assert.That("keepersecurity.eu", Is.EqualTo(storage.GetString("hostname"))); storage = new InMemoryStorage(); SecretsManagerClient.InitializeStorage(storage, "fake.keepersecurity.com:ONE_TIME_TOKEN"); - Assert.AreEqual("fake.keepersecurity.com", storage.GetString("hostname")); + Assert.That("fake.keepersecurity.com", Is.EqualTo(storage.GetString("hostname"))); } [Test] @@ -88,9 +88,9 @@ public void TestStorageBase64Config() string fakeBase64Config = "eyJhcHBLZXkiOiAiRkFLRV9BUFBfS0VZIiwgICAgICJjbGllbnRJZCI6ICJGQUtFX0NMSUVOVF9LRV" + "kiLCAgICAgImhvc3RuYW1lIjogImZha2Uua2VlcGVyc2VjdXJpdHkuY29tIiwgICAgICJwcml2YXRl" + "S2V5IjogIkZBS0VfUFJJVkFURV9LRVkiLCAgICAKInNlcnZlclB1YmxpY0tleUlkIjogIjEwIiB9"; - + var storage = new InMemoryStorage(fakeBase64Config); - Assert.AreEqual("fake.keepersecurity.com", storage.GetString("hostname")); + Assert.That("fake.keepersecurity.com", Is.EqualTo(storage.GetString("hostname"))); } } -} \ No newline at end of file +} diff --git a/sdk/dotNet/SecretsManager.sln b/sdk/dotNet/SecretsManager.sln index e39c3e33..8839449f 100644 --- a/sdk/dotNet/SecretsManager.sln +++ b/sdk/dotNet/SecretsManager.sln @@ -11,6 +11,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecretsManager.Test.Core", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecretManagement.Keeper", "SecretManagement.Keeper\SecretManagement.Keeper.csproj", "{2BFFD8C3-29BD-4349-A183-7C01F9C34ED0}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{07124028-B5D7-4920-B2F3-76DF03ED030B}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/sdk/dotNet/SecretsManager/CryptoUtils.cs b/sdk/dotNet/SecretsManager/CryptoUtils.cs index fbf5784c..84f73137 100644 --- a/sdk/dotNet/SecretsManager/CryptoUtils.cs +++ b/sdk/dotNet/SecretsManager/CryptoUtils.cs @@ -5,7 +5,6 @@ using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Modes; using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.Security; using Org.BouncyCastle.Crypto.Paddings; @@ -107,10 +106,10 @@ private static ECPublicKeyParameters ImportPublicKey(byte[] key) } private static ECPrivateKeyParameters ImportPrivateKey(byte[] privateKeyDer) - { - var privateKeyInfo = PrivateKeyInfo.GetInstance(privateKeyDer); - var privateKeyStructure = ECPrivateKeyStructure.GetInstance(privateKeyInfo.ParsePrivateKey()); - var privateKeyValue = privateKeyStructure.GetKey(); + { + var privateKeyInfo = PrivateKeyInfo.GetInstance(privateKeyDer); + var privateKeyStructure = ECPrivateKeyStructure.GetInstance(privateKeyInfo.ParsePrivateKey()); + var privateKeyValue = privateKeyStructure.GetKey(); return new ECPrivateKeyParameters(privateKeyValue, ECParameters); } diff --git a/sdk/dotNet/SecretsManager/JsonUtils.cs b/sdk/dotNet/SecretsManager/JsonUtils.cs index 30c874a8..e70a5ab7 100644 --- a/sdk/dotNet/SecretsManager/JsonUtils.cs +++ b/sdk/dotNet/SecretsManager/JsonUtils.cs @@ -22,4 +22,4 @@ public static byte[] SerializeJson(T obj) return CryptoUtils.StringToBytes(JsonSerializer.Serialize(obj, Options)); } } -} \ No newline at end of file +} diff --git a/sdk/dotNet/SecretsManager/LocalConfigStorage.cs b/sdk/dotNet/SecretsManager/LocalConfigStorage.cs index 871fef50..e93e290a 100644 --- a/sdk/dotNet/SecretsManager/LocalConfigStorage.cs +++ b/sdk/dotNet/SecretsManager/LocalConfigStorage.cs @@ -195,4 +195,4 @@ public static byte[] GetCachedValue() return File.ReadAllBytes("cache.dat"); } } -} \ No newline at end of file +} diff --git a/sdk/dotNet/SecretsManager/Notation.cs b/sdk/dotNet/SecretsManager/Notation.cs index 5dd84098..07f1b98f 100644 --- a/sdk/dotNet/SecretsManager/Notation.cs +++ b/sdk/dotNet/SecretsManager/Notation.cs @@ -108,7 +108,7 @@ KeeperRecordField FindField(string fieldName) } // data class to represent parsed notation section - internal class NotationSection + public class NotationSection { public string Section = null; // section name - ex. prefix public bool IsPresent = false; // presence flag @@ -281,7 +281,7 @@ private static NotationSection ParseSection(string notation, string section, int return result; } - internal static List ParseNotation(string notation, bool legacyMode = false) + public static List ParseNotation(string notation, bool legacyMode = false) { if (string.IsNullOrEmpty(notation)) throw new Exception("Keeper notation is missing or invalid."); diff --git a/sdk/dotNet/SecretsManager/RecordData.cs b/sdk/dotNet/SecretsManager/RecordData.cs index ad8482cc..2c60db46 100644 --- a/sdk/dotNet/SecretsManager/RecordData.cs +++ b/sdk/dotNet/SecretsManager/RecordData.cs @@ -567,8 +567,8 @@ public class Script public string fileRef { get; set; } public string command { get; set; } public List recordRef { get; set; } - } - + } + [SuppressMessage("ReSharper", "InconsistentNaming")] [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] diff --git a/sdk/dotNet/SecretsManager/SecretsManager.csproj b/sdk/dotNet/SecretsManager/SecretsManager.csproj index cc475f05..0a662ddc 100644 --- a/sdk/dotNet/SecretsManager/SecretsManager.csproj +++ b/sdk/dotNet/SecretsManager/SecretsManager.csproj @@ -1,13 +1,13 @@ - net47;net48;netstandard2.1 + net47;net48;netstandard2.0;netstandard2.1 9 Keeper Security Inc. SecretsManager .Net SDK - 16.6.5 - 16.6.5 - 16.6.5 + 16.6.6 + 16.6.6 + 16.6.6 en-US Keeper.SecretsManager Sergey Aldoukhov @@ -16,14 +16,19 @@ https://github.com/Keeper-Security/secrets-manager GitHub keeper secrets manager passwords - © 2023 Keeper Security, Inc. + © 2024 Keeper Security, Inc. https://raw.githubusercontent.com/Keeper-Security/secrets-manager/master/LICENSE?token=AACNMRVMD5L3PYT3C5MTNF3BEAFZY true - - + + + + + True + sgKSM.snk + diff --git a/sdk/dotNet/SecretsManager/SecretsManagerClient.cs b/sdk/dotNet/SecretsManager/SecretsManagerClient.cs index 44202c7a..bcb6eec6 100644 --- a/sdk/dotNet/SecretsManager/SecretsManagerClient.cs +++ b/sdk/dotNet/SecretsManager/SecretsManagerClient.cs @@ -5,14 +5,11 @@ using System.Linq; using System.Net; using System.Reflection; -using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; -[assembly: InternalsVisibleTo("SecretsManager.Test.Core")] - namespace SecretsManager { using GetRandomBytesFunction = Func; @@ -1412,7 +1409,7 @@ private static TransmissionKey GenerateTransmissionKey(IKeyValueStorage storage) [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] - internal static GetRandomBytesFunction TransmissionKeyStub { get; set; } + public static GetRandomBytesFunction TransmissionKeyStub { get; set; } private static EncryptedPayload EncryptAndSignPayload(IKeyValueStorage storage, TransmissionKey transmissionKey, T payload)