From 6e3a50fc6b339b7e23fb32745cae80931cb28d0f Mon Sep 17 00:00:00 2001 From: Nick Guerrera Date: Tue, 26 Nov 2024 16:46:46 -0600 Subject: [PATCH 1/3] Pre-format before file-scoped namespaces --- .../CompareHashedSignatureBenchmarks.cs | 4 ++-- .../Cask.Benchmarks/IsCaskBenchmarks.cs | 2 +- src/CSharp/Cask.Benchmarks/Program.cs | 2 +- src/CSharp/Cask.Cli/CloudInstance.cs | 2 +- src/CSharp/Cask.Cli/GenerateCommand.cs | 2 +- src/CSharp/Cask.Cli/GenerateOptions.cs | 2 +- src/CSharp/Cask.Cli/Program.cs | 2 +- src/CSharp/Cask.Cli/ValidateCommand.cs | 4 ++-- src/CSharp/Cask.Cli/ValidateOptions.cs | 2 +- .../CommonAnnotatedSecurityKeys/Cask.cs | 8 ++++---- .../CaskExtensionMethods.cs | 2 +- .../CaskUtilityApi.cs | 8 ++++---- .../CommonAnnotatedSecurityKeys/ICask.cs | 4 +--- .../ICaskUtilityApi.cs | 4 +--- .../CommonAnnotatedSecurityKeys/Notes.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- .../CSharpCaskTests.cs | 2 +- .../CaskSecretsTests.cs | 20 +++++++++---------- .../TestCaskUtilityApi.cs | 4 ++-- 19 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/CSharp/Cask.Benchmarks/CompareHashedSignatureBenchmarks.cs b/src/CSharp/Cask.Benchmarks/CompareHashedSignatureBenchmarks.cs index 77336c6..747ffaf 100644 --- a/src/CSharp/Cask.Benchmarks/CompareHashedSignatureBenchmarks.cs +++ b/src/CSharp/Cask.Benchmarks/CompareHashedSignatureBenchmarks.cs @@ -11,7 +11,7 @@ public class CompareHashedSignatureBenchmarks { private const int iterations = 10000; private const string TestProviderSignature = "TEST"; - private static readonly byte [] TestProviderSignatureBytes = Convert.FromBase64String(TestProviderSignature); + private static readonly byte[] TestProviderSignatureBytes = Convert.FromBase64String(TestProviderSignature); [Benchmark] public void UseCompareHash() @@ -64,4 +64,4 @@ public void UseHmacSha256() } } } -} +} \ No newline at end of file diff --git a/src/CSharp/Cask.Benchmarks/IsCaskBenchmarks.cs b/src/CSharp/Cask.Benchmarks/IsCaskBenchmarks.cs index 447977a..f83e047 100644 --- a/src/CSharp/Cask.Benchmarks/IsCaskBenchmarks.cs +++ b/src/CSharp/Cask.Benchmarks/IsCaskBenchmarks.cs @@ -30,4 +30,4 @@ public void UseIsCaskBytes() } } } -} +} \ No newline at end of file diff --git a/src/CSharp/Cask.Benchmarks/Program.cs b/src/CSharp/Cask.Benchmarks/Program.cs index 61bf835..eb97896 100644 --- a/src/CSharp/Cask.Benchmarks/Program.cs +++ b/src/CSharp/Cask.Benchmarks/Program.cs @@ -12,4 +12,4 @@ * dotnet run -c Release --framework net8.0 Cask.Benchmarks.csproj */ //Summary summary = BenchmarkRunner.Run(); -Summary summary = BenchmarkRunner.Run(); +Summary summary = BenchmarkRunner.Run(); \ No newline at end of file diff --git a/src/CSharp/Cask.Cli/CloudInstance.cs b/src/CSharp/Cask.Cli/CloudInstance.cs index 586e197..74ab79f 100644 --- a/src/CSharp/Cask.Cli/CloudInstance.cs +++ b/src/CSharp/Cask.Cli/CloudInstance.cs @@ -8,4 +8,4 @@ public enum CloudInstance Public, Preproduction, } -} +} \ No newline at end of file diff --git a/src/CSharp/Cask.Cli/GenerateCommand.cs b/src/CSharp/Cask.Cli/GenerateCommand.cs index 0de38d3..bbf4f86 100644 --- a/src/CSharp/Cask.Cli/GenerateCommand.cs +++ b/src/CSharp/Cask.Cli/GenerateCommand.cs @@ -57,4 +57,4 @@ public static string EncodeForIdentifiableKey(string text) return Convert.ToBase64String(hashed).ToUrlSafe().Substring(0, 5); } } -} +} \ No newline at end of file diff --git a/src/CSharp/Cask.Cli/GenerateOptions.cs b/src/CSharp/Cask.Cli/GenerateOptions.cs index 18998d0..51681c1 100644 --- a/src/CSharp/Cask.Cli/GenerateOptions.cs +++ b/src/CSharp/Cask.Cli/GenerateOptions.cs @@ -34,4 +34,4 @@ public class GenerateOptions HelpText = "The count of keys to generate.")] public uint Count { get; set; } } -} +} \ No newline at end of file diff --git a/src/CSharp/Cask.Cli/Program.cs b/src/CSharp/Cask.Cli/Program.cs index d61f26c..e77b7bf 100644 --- a/src/CSharp/Cask.Cli/Program.cs +++ b/src/CSharp/Cask.Cli/Program.cs @@ -27,4 +27,4 @@ public static int Main(string[] args) } } } -} +} \ No newline at end of file diff --git a/src/CSharp/Cask.Cli/ValidateCommand.cs b/src/CSharp/Cask.Cli/ValidateCommand.cs index f75fb66..57376ec 100644 --- a/src/CSharp/Cask.Cli/ValidateCommand.cs +++ b/src/CSharp/Cask.Cli/ValidateCommand.cs @@ -5,8 +5,8 @@ namespace CommonAnnotatedSecurityKeys.Cli public class ValidateCommand { internal int Run(ValidateOptions _) - { + { return 0; } } -} +} \ No newline at end of file diff --git a/src/CSharp/Cask.Cli/ValidateOptions.cs b/src/CSharp/Cask.Cli/ValidateOptions.cs index 83d96b0..c5e1781 100644 --- a/src/CSharp/Cask.Cli/ValidateOptions.cs +++ b/src/CSharp/Cask.Cli/ValidateOptions.cs @@ -7,4 +7,4 @@ namespace CommonAnnotatedSecurityKeys.Cli public class ValidateOptions { } -} +} \ No newline at end of file diff --git a/src/CSharp/CommonAnnotatedSecurityKeys/Cask.cs b/src/CSharp/CommonAnnotatedSecurityKeys/Cask.cs index 15d08f5..e5e19d6 100644 --- a/src/CSharp/CommonAnnotatedSecurityKeys/Cask.cs +++ b/src/CSharp/CommonAnnotatedSecurityKeys/Cask.cs @@ -69,7 +69,7 @@ public string GenerateKey(string providerSignature, string? reserved = null, int secretEntropyInBytes = 32) { - byte[] reservedBytes = reserved == null + byte[] reservedBytes = reserved == null ? Array.Empty() : Convert.FromBase64String(reserved.FromUrlSafe()); @@ -187,7 +187,7 @@ public string GenerateHash(byte[] derivationInput, byte[] secret, int secretEntr } internal byte[] GenerateHashedSignatureBytes(byte[] derivationInput, byte[] secret, int secretEntropyInBytes) - { + { byte[] allocatorAndTimeStampBytes = new byte[3]; secretEntropyInBytes = secretEntropyInBytes.RoundUpToMultipleOf(3); @@ -232,7 +232,7 @@ internal byte[] GenerateHashedSignatureBytes(byte[] derivationInput, byte[] secr byte[] crc32Bytes = new byte[4]; Utilities.ComputeCrc32Hash(toChecksum, crc32Bytes); - + int crc32HashOffset = providerSignatureBytesOffset + 3; Array.Copy(crc32Bytes, 0, hashedSignature, crc32HashOffset, 3); @@ -245,4 +245,4 @@ public bool CompareHash(byte[] candidateHash, byte[] derivationInput, byte[] sec return CryptographicOperations.FixedTimeEquals(computedHash, candidateHash); } } -} +} \ No newline at end of file diff --git a/src/CSharp/CommonAnnotatedSecurityKeys/CaskExtensionMethods.cs b/src/CSharp/CommonAnnotatedSecurityKeys/CaskExtensionMethods.cs index 3cf12e2..9245001 100644 --- a/src/CSharp/CommonAnnotatedSecurityKeys/CaskExtensionMethods.cs +++ b/src/CSharp/CommonAnnotatedSecurityKeys/CaskExtensionMethods.cs @@ -20,4 +20,4 @@ internal static int RoundUpToMultipleOf(this int value, int multiple) return (value + multiple - 1) / multiple * multiple; } } -} +} \ No newline at end of file diff --git a/src/CSharp/CommonAnnotatedSecurityKeys/CaskUtilityApi.cs b/src/CSharp/CommonAnnotatedSecurityKeys/CaskUtilityApi.cs index ea23fd2..00c56a0 100644 --- a/src/CSharp/CommonAnnotatedSecurityKeys/CaskUtilityApi.cs +++ b/src/CSharp/CommonAnnotatedSecurityKeys/CaskUtilityApi.cs @@ -41,7 +41,7 @@ public class CaskUtilityApi : ICaskUtilityApi public static ICaskUtilityApi Instance { - get + get { if (caskConstants.Value == null) { @@ -49,7 +49,7 @@ public static ICaskUtilityApi Instance } return caskConstants.Value; } - set { caskConstants = new (() => value); } + set { caskConstants = new(() => value); } } public static Crc32 Crc32 @@ -77,7 +77,7 @@ public static RandomNumberGenerator Rng 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' }; - public static ReadOnlySpan OrderedUrlSafeBase64Characters => orderedUrlSafeBase64Characters; + public static ReadOnlySpan OrderedUrlSafeBase64Characters => orderedUrlSafeBase64Characters; public static readonly ISet UrlSafeBase64Characters = orderedUrlSafeBase64Characters.ToImmutableHashSet(); @@ -93,4 +93,4 @@ public virtual void ComputeCrc32Hash(ReadOnlySpan toChecksum, Span d Crc32.GetHashAndReset(destination); } } -} +} \ No newline at end of file diff --git a/src/CSharp/CommonAnnotatedSecurityKeys/ICask.cs b/src/CSharp/CommonAnnotatedSecurityKeys/ICask.cs index eef5c13..c56ca43 100644 --- a/src/CSharp/CommonAnnotatedSecurityKeys/ICask.cs +++ b/src/CSharp/CommonAnnotatedSecurityKeys/ICask.cs @@ -25,6 +25,4 @@ bool CompareHash(byte[] candidateHash, byte[] secret, int secretEntropyInBytes = 32); } -} - - +} \ No newline at end of file diff --git a/src/CSharp/CommonAnnotatedSecurityKeys/ICaskUtilityApi.cs b/src/CSharp/CommonAnnotatedSecurityKeys/ICaskUtilityApi.cs index 12af31c..0b4fb66 100644 --- a/src/CSharp/CommonAnnotatedSecurityKeys/ICaskUtilityApi.cs +++ b/src/CSharp/CommonAnnotatedSecurityKeys/ICaskUtilityApi.cs @@ -9,6 +9,4 @@ public interface ICaskUtilityApi void ComputeCrc32Hash(ReadOnlySpan toChecksum, Span destination); } -} - - +} \ No newline at end of file diff --git a/src/CSharp/CommonAnnotatedSecurityKeys/Notes.cs b/src/CSharp/CommonAnnotatedSecurityKeys/Notes.cs index bd1b87b..88a7d71 100644 --- a/src/CSharp/CommonAnnotatedSecurityKeys/Notes.cs +++ b/src/CSharp/CommonAnnotatedSecurityKeys/Notes.cs @@ -31,4 +31,4 @@ public string GenerateKey(string providerSignature, string allocatorCode, string } } } -*/ +*/ \ No newline at end of file diff --git a/src/CSharp/CommonAnnotatedSecurityKeys/Properties/AssemblyInfo.cs b/src/CSharp/CommonAnnotatedSecurityKeys/Properties/AssemblyInfo.cs index cbca6cb..16defb4 100644 --- a/src/CSharp/CommonAnnotatedSecurityKeys/Properties/AssemblyInfo.cs +++ b/src/CSharp/CommonAnnotatedSecurityKeys/Properties/AssemblyInfo.cs @@ -2,4 +2,4 @@ [assembly: InternalsVisibleTo("Tests.CommonAnnotatedSecurityKeys, PublicKey=0024000004800000940000000602000000240000525341310004000001000100433fbf156abe9718142bdbd48a440e779a1b708fd21486ee0ae536f4c548edf8a7185c1e3ac89ceef76c15b8cc2497906798779a59402f9b9e27281fb15e7111566cdc9a9f8326301d45320623c5222089cf4d0013f365ae729fb0a9c9d15138042825cd511a0f3d4887a7b92f4c2749f81b410813d297b73244cf64995effb1")] [assembly: InternalsVisibleTo("Cask.Cli, PublicKey=0024000004800000940000000602000000240000525341310004000001000100433fbf156abe9718142bdbd48a440e779a1b708fd21486ee0ae536f4c548edf8a7185c1e3ac89ceef76c15b8cc2497906798779a59402f9b9e27281fb15e7111566cdc9a9f8326301d45320623c5222089cf4d0013f365ae729fb0a9c9d15138042825cd511a0f3d4887a7b92f4c2749f81b410813d297b73244cf64995effb1")] -[assembly: InternalsVisibleTo("Cask.Benchmarks, PublicKey=0024000004800000940000000602000000240000525341310004000001000100433fbf156abe9718142bdbd48a440e779a1b708fd21486ee0ae536f4c548edf8a7185c1e3ac89ceef76c15b8cc2497906798779a59402f9b9e27281fb15e7111566cdc9a9f8326301d45320623c5222089cf4d0013f365ae729fb0a9c9d15138042825cd511a0f3d4887a7b92f4c2749f81b410813d297b73244cf64995effb1")] +[assembly: InternalsVisibleTo("Cask.Benchmarks, PublicKey=0024000004800000940000000602000000240000525341310004000001000100433fbf156abe9718142bdbd48a440e779a1b708fd21486ee0ae536f4c548edf8a7185c1e3ac89ceef76c15b8cc2497906798779a59402f9b9e27281fb15e7111566cdc9a9f8326301d45320623c5222089cf4d0013f365ae729fb0a9c9d15138042825cd511a0f3d4887a7b92f4c2749f81b410813d297b73244cf64995effb1")] \ No newline at end of file diff --git a/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CSharpCaskTests.cs b/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CSharpCaskTests.cs index 8810ebc..7395870 100644 --- a/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CSharpCaskTests.cs +++ b/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CSharpCaskTests.cs @@ -10,4 +10,4 @@ public CSharpCaskTests() : base(new Cask()) { } } -} +} \ No newline at end of file diff --git a/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CaskSecretsTests.cs b/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CaskSecretsTests.cs index 89dc2dd..8c1e22e 100644 --- a/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CaskSecretsTests.cs +++ b/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CaskSecretsTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; - using CommonAnnotatedSecurityKeys; +using System; + using Xunit; namespace Tests.CommonAnnotatedSecurityKeys @@ -33,16 +33,16 @@ public void CaskSecrets_IsCask(int secretEntropyInBytes) public void CaskSecrets_GenerateKey_Basic(int secretEntropyInBytes) { - string key = Cask.GenerateKey(providerSignature: "TEST", - allocatorCode: "88", - reserved: "ABCD", - secretEntropyInBytes); + string key = Cask.GenerateKey(providerSignature: "TEST", + allocatorCode: "88", + reserved: "ABCD", + secretEntropyInBytes); - byte[] keyBytes = Convert.FromBase64String(key.FromUrlSafe()); - Assert.True(keyBytes.Length % 3 == 0, "'GenerateKey' output wasn't aligned on a 3-byte boundary."); + byte[] keyBytes = Convert.FromBase64String(key.FromUrlSafe()); + Assert.True(keyBytes.Length % 3 == 0, "'GenerateKey' output wasn't aligned on a 3-byte boundary."); + + IsCaskValidate(Cask, key); - IsCaskValidate(Cask, key); - } [Theory] diff --git a/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/TestCaskUtilityApi.cs b/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/TestCaskUtilityApi.cs index 7534101..cf6c542 100644 --- a/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/TestCaskUtilityApi.cs +++ b/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/TestCaskUtilityApi.cs @@ -26,8 +26,8 @@ public override void ComputeCrc32Hash(ReadOnlySpan toChecksum, Span ComputeCrc32HashAction(toChecksum.ToArray(), destination.ToArray()); return; } - + base.ComputeCrc32Hash(toChecksum, destination); } } -} +} \ No newline at end of file From bb15dbf7610d8d91d17bbfc21fd1bba9ae0163df Mon Sep 17 00:00:00 2001 From: Nick Guerrera Date: Tue, 26 Nov 2024 16:51:25 -0600 Subject: [PATCH 2/3] Use file-scoped namespaces --- src/CSharp/.editorconfig | 2 +- .../CompareHashedSignatureBenchmarks.cs | 77 ++-- .../Cask.Benchmarks/IsCaskBenchmarks.cs | 37 +- src/CSharp/Cask.Cli/CloudInstance.cs | 17 +- src/CSharp/Cask.Cli/GenerateCommand.cs | 77 ++-- src/CSharp/Cask.Cli/GenerateOptions.cs | 55 ++- src/CSharp/Cask.Cli/Program.cs | 39 +- src/CSharp/Cask.Cli/ValidateCommand.cs | 11 +- src/CSharp/Cask.Cli/ValidateOptions.cs | 9 +- .../CommonAnnotatedSecurityKeys/Cask.cs | 359 +++++++++--------- .../CaskExtensionMethods.cs | 27 +- .../CaskUtilityApi.cs | 125 +++--- .../CommonAnnotatedSecurityKeys/ICask.cs | 35 +- .../ICaskUtilityApi.cs | 11 +- .../Polyfill/CryptographicOperations.cs | 83 ++-- .../CSharpCaskTests.cs | 9 +- .../CaskSecretsTests.cs | 229 ++++++----- .../TestCaskUtilityApi.cs | 35 +- 18 files changed, 610 insertions(+), 627 deletions(-) diff --git a/src/CSharp/.editorconfig b/src/CSharp/.editorconfig index f237782..fd8e091 100644 --- a/src/CSharp/.editorconfig +++ b/src/CSharp/.editorconfig @@ -112,7 +112,7 @@ csharp_style_prefer_readonly_struct_member = true # Code-block preferences csharp_prefer_braces = true csharp_prefer_simple_using_statement = true -csharp_style_namespace_declarations = block_scoped +csharp_style_namespace_declarations = file_scoped:warning csharp_style_prefer_method_group_conversion = true csharp_style_prefer_primary_constructors = true csharp_style_prefer_top_level_statements = true diff --git a/src/CSharp/Cask.Benchmarks/CompareHashedSignatureBenchmarks.cs b/src/CSharp/Cask.Benchmarks/CompareHashedSignatureBenchmarks.cs index 747ffaf..0f6c266 100644 --- a/src/CSharp/Cask.Benchmarks/CompareHashedSignatureBenchmarks.cs +++ b/src/CSharp/Cask.Benchmarks/CompareHashedSignatureBenchmarks.cs @@ -5,61 +5,60 @@ using System.Security.Cryptography; -namespace CommonAnnotatedSecurityKeys.Benchmarks +namespace CommonAnnotatedSecurityKeys.Benchmarks; + +public class CompareHashedSignatureBenchmarks { - public class CompareHashedSignatureBenchmarks + private const int iterations = 10000; + private const string TestProviderSignature = "TEST"; + private static readonly byte[] TestProviderSignatureBytes = Convert.FromBase64String(TestProviderSignature); + + [Benchmark] + public void UseCompareHash() { - private const int iterations = 10000; - private const string TestProviderSignature = "TEST"; - private static readonly byte[] TestProviderSignatureBytes = Convert.FromBase64String(TestProviderSignature); + string key = Cask.Instance.GenerateKey(TestProviderSignature, "99"); + byte[] keyBytes = Convert.FromBase64String(key.FromUrlSafe()); + string hash = Cask.Instance.GenerateHash(keyBytes, keyBytes, 32); + byte[] hashBytes = Convert.FromBase64String(hash.FromUrlSafe()); - [Benchmark] - public void UseCompareHash() + for (int i = 0; i < iterations; i++) { - string key = Cask.Instance.GenerateKey(TestProviderSignature, "99"); - byte[] keyBytes = Convert.FromBase64String(key.FromUrlSafe()); - string hash = Cask.Instance.GenerateHash(keyBytes, keyBytes, 32); - byte[] hashBytes = Convert.FromBase64String(hash.FromUrlSafe()); - - for (int i = 0; i < iterations; i++) + if (!Cask.Instance.CompareHash(hashBytes, keyBytes, keyBytes)) { - if (!Cask.Instance.CompareHash(hashBytes, keyBytes, keyBytes)) - { - throw new InvalidOperationException(); - } + throw new InvalidOperationException(); } } + } - [Benchmark] - public void UseHmacSha256() + [Benchmark] + public void UseHmacSha256() + { + string key = Cask.Instance.GenerateKey(TestProviderSignature, "99"); + byte[] keyBytes = Convert.FromBase64String(key.FromUrlSafe()); + string hash = Cask.Instance.GenerateHash(TestProviderSignatureBytes, keyBytes, 32); + var hmac = new HMACSHA256(keyBytes); + byte[] hashBytes = hmac.ComputeHash(TestProviderSignatureBytes); + + for (int i = 0; i < iterations; i++) { - string key = Cask.Instance.GenerateKey(TestProviderSignature, "99"); - byte[] keyBytes = Convert.FromBase64String(key.FromUrlSafe()); - string hash = Cask.Instance.GenerateHash(TestProviderSignatureBytes, keyBytes, 32); - var hmac = new HMACSHA256(keyBytes); - byte[] hashBytes = hmac.ComputeHash(TestProviderSignatureBytes); + hmac = new HMACSHA256(keyBytes); + byte[] computedHashBytes = hmac.ComputeHash(TestProviderSignatureBytes); - for (int i = 0; i < iterations; i++) + for (int j = 0; j < hashBytes.Length; j++) { - hmac = new HMACSHA256(keyBytes); - byte[] computedHashBytes = hmac.ComputeHash(TestProviderSignatureBytes); - - for (int j = 0; j < hashBytes.Length; j++) + if (hashBytes[j] != computedHashBytes[j]) { - if (hashBytes[j] != computedHashBytes[j]) - { - throw new InvalidOperationException(); - } + throw new InvalidOperationException(); } + } - computedHashBytes = hmac.ComputeHash(TestProviderSignatureBytes); + computedHashBytes = hmac.ComputeHash(TestProviderSignatureBytes); - for (int j = 0; j < hashBytes.Length; j++) + for (int j = 0; j < hashBytes.Length; j++) + { + if (hashBytes[j] != computedHashBytes[j]) { - if (hashBytes[j] != computedHashBytes[j]) - { - throw new InvalidOperationException(); - } + throw new InvalidOperationException(); } } } diff --git a/src/CSharp/Cask.Benchmarks/IsCaskBenchmarks.cs b/src/CSharp/Cask.Benchmarks/IsCaskBenchmarks.cs index f83e047..19f5087 100644 --- a/src/CSharp/Cask.Benchmarks/IsCaskBenchmarks.cs +++ b/src/CSharp/Cask.Benchmarks/IsCaskBenchmarks.cs @@ -3,31 +3,30 @@ using BenchmarkDotNet.Attributes; -namespace CommonAnnotatedSecurityKeys.Benchmarks +namespace CommonAnnotatedSecurityKeys.Benchmarks; + +public class IsCaskBenchmarks { - public class IsCaskBenchmarks - { - private const int iterations = 100000; - private const string TestProviderSignature = "TEST"; - private static readonly string key = Cask.Instance.GenerateKey(TestProviderSignature, "99"); - private static readonly byte[] keyBytes = Convert.FromBase64String(key); + private const int iterations = 100000; + private const string TestProviderSignature = "TEST"; + private static readonly string key = Cask.Instance.GenerateKey(TestProviderSignature, "99"); + private static readonly byte[] keyBytes = Convert.FromBase64String(key); - [Benchmark] - public void UseIsCaskString() + [Benchmark] + public void UseIsCaskString() + { + for (int i = 0; i < iterations; i++) { - for (int i = 0; i < iterations; i++) - { - Cask.Instance.IsCask(key); - } + Cask.Instance.IsCask(key); } + } - [Benchmark] - public void UseIsCaskBytes() + [Benchmark] + public void UseIsCaskBytes() + { + for (int i = 0; i < iterations; i++) { - for (int i = 0; i < iterations; i++) - { - Cask.Instance.IsCask(keyBytes); - } + Cask.Instance.IsCask(keyBytes); } } } \ No newline at end of file diff --git a/src/CSharp/Cask.Cli/CloudInstance.cs b/src/CSharp/Cask.Cli/CloudInstance.cs index 74ab79f..016bcde 100644 --- a/src/CSharp/Cask.Cli/CloudInstance.cs +++ b/src/CSharp/Cask.Cli/CloudInstance.cs @@ -1,11 +1,10 @@ -namespace Cask.Cli +namespace Cask.Cli; + +public enum CloudInstance { - public enum CloudInstance - { - None, - NoCloudSpecified = None, - Other, - Public, - Preproduction, - } + None, + NoCloudSpecified = None, + Other, + Public, + Preproduction, } \ No newline at end of file diff --git a/src/CSharp/Cask.Cli/GenerateCommand.cs b/src/CSharp/Cask.Cli/GenerateCommand.cs index bbf4f86..d6d300d 100644 --- a/src/CSharp/Cask.Cli/GenerateCommand.cs +++ b/src/CSharp/Cask.Cli/GenerateCommand.cs @@ -2,59 +2,58 @@ using System.Text; -namespace CommonAnnotatedSecurityKeys.Cli -{ - public class GenerateCommand - { - private readonly string MicrosoftTenantId = new Guid("782ef2bb-3056-4438-946d-395022a4a19f").ToString(); +namespace CommonAnnotatedSecurityKeys.Cli; - internal int Run(GenerateOptions options) - { - var cask = new Cask(); - byte[] derivationInput = Encoding.UTF8.GetBytes(nameof(derivationInput)); +public class GenerateCommand +{ + private readonly string MicrosoftTenantId = new Guid("782ef2bb-3056-4438-946d-395022a4a19f").ToString(); - string cloudText = "AC"; - string region = EncodeForIdentifiableKey("westus"); - string tenant = EncodeForIdentifiableKey(MicrosoftTenantId); - string providerReserved = "AAAA"; - byte[] reserved = Convert.FromBase64String($"{cloudText}{region}{tenant}{providerReserved}"); + internal int Run(GenerateOptions options) + { + var cask = new Cask(); + byte[] derivationInput = Encoding.UTF8.GetBytes(nameof(derivationInput)); - byte[] providerSignature = Convert.FromBase64String(options.FixedSignature.ToUrlSafe()); + string cloudText = "AC"; + string region = EncodeForIdentifiableKey("westus"); + string tenant = EncodeForIdentifiableKey(MicrosoftTenantId); + string providerReserved = "AAAA"; + byte[] reserved = Convert.FromBase64String($"{cloudText}{region}{tenant}{providerReserved}"); - for (int i = 0; i < options.Count; i++) - { + byte[] providerSignature = Convert.FromBase64String(options.FixedSignature.ToUrlSafe()); - byte[] keyBytes = cask.GenerateKeyBytes(providerSignature, - "99", - reserved, - secretEntropyInBytes: options.SecretEntropyInBytes, - testChar: default); + for (int i = 0; i < options.Count; i++) + { + byte[] keyBytes = cask.GenerateKeyBytes(providerSignature, + "99", + reserved, + secretEntropyInBytes: options.SecretEntropyInBytes, + testChar: default); - string key = Convert.ToBase64String(keyBytes).ToUrlSafe(); - string validity = cask.IsCask(key) ? "Valid Key " : "INVALID KEY "; - Console.WriteLine($"{validity}: {key}"); + string key = Convert.ToBase64String(keyBytes).ToUrlSafe(); - keyBytes = Convert.FromBase64String(key.FromUrlSafe()); - string hash = cask.GenerateHash(derivationInput, keyBytes, options.SecretEntropyInBytes); + string validity = cask.IsCask(key) ? "Valid Key " : "INVALID KEY "; + Console.WriteLine($"{validity}: {key}"); - validity = cask.IsCask(hash) ? "Valid Hash " : "INVALID HASH"; - Console.WriteLine($"{validity}: {hash}"); - } + keyBytes = Convert.FromBase64String(key.FromUrlSafe()); + string hash = cask.GenerateHash(derivationInput, keyBytes, options.SecretEntropyInBytes); - return 0; + validity = cask.IsCask(hash) ? "Valid Hash " : "INVALID HASH"; + Console.WriteLine($"{validity}: {hash}"); } - public static string EncodeForIdentifiableKey(string text) - { - if (string.IsNullOrWhiteSpace(text)) - { - return "AAAAA"; - } + return 0; + } - byte[] hashed = CaskUtilityApi.Sha256.ComputeHash(Encoding.UTF8.GetBytes(text)); - return Convert.ToBase64String(hashed).ToUrlSafe().Substring(0, 5); + public static string EncodeForIdentifiableKey(string text) + { + if (string.IsNullOrWhiteSpace(text)) + { + return "AAAAA"; } + + byte[] hashed = CaskUtilityApi.Sha256.ComputeHash(Encoding.UTF8.GetBytes(text)); + return Convert.ToBase64String(hashed).ToUrlSafe().Substring(0, 5); } } \ No newline at end of file diff --git a/src/CSharp/Cask.Cli/GenerateOptions.cs b/src/CSharp/Cask.Cli/GenerateOptions.cs index 51681c1..5d7631a 100644 --- a/src/CSharp/Cask.Cli/GenerateOptions.cs +++ b/src/CSharp/Cask.Cli/GenerateOptions.cs @@ -2,36 +2,35 @@ #nullable disable using CommandLine; -namespace CommonAnnotatedSecurityKeys.Cli +namespace CommonAnnotatedSecurityKeys.Cli; + +[Verb("generate", HelpText = "Generate one or more common annotated security keys.")] +public class GenerateOptions { - [Verb("generate", HelpText = "Generate one or more common annotated security keys.")] - public class GenerateOptions - { - [Option( - "test", - Required = false, - HelpText = "Generate all test keys.")] - public bool Test { get; set; } + [Option( + "test", + Required = false, + HelpText = "Generate all test keys.")] + public bool Test { get; set; } - [Option( - "signature", - Required = false, - Default = "TEST", - HelpText = "A fixed signature to inject into the generated key).")] - public string FixedSignature { get; set; } + [Option( + "signature", + Required = false, + Default = "TEST", + HelpText = "A fixed signature to inject into the generated key).")] + public string FixedSignature { get; set; } - [Option( - "length", - Required = false, - Default = 32, - HelpText = "The length of the randomized component in bytes. Must be at least 16.")] - public int SecretEntropyInBytes { get; set; } + [Option( + "length", + Required = false, + Default = 32, + HelpText = "The length of the randomized component in bytes. Must be at least 16.")] + public int SecretEntropyInBytes { get; set; } - [Option( - "count", - Required = false, - Default = (uint)1, - HelpText = "The count of keys to generate.")] - public uint Count { get; set; } - } + [Option( + "count", + Required = false, + Default = (uint)1, + HelpText = "The count of keys to generate.")] + public uint Count { get; set; } } \ No newline at end of file diff --git a/src/CSharp/Cask.Cli/Program.cs b/src/CSharp/Cask.Cli/Program.cs index e77b7bf..5f67640 100644 --- a/src/CSharp/Cask.Cli/Program.cs +++ b/src/CSharp/Cask.Cli/Program.cs @@ -2,29 +2,28 @@ using CommandLine; -namespace CommonAnnotatedSecurityKeys.Cli +namespace CommonAnnotatedSecurityKeys.Cli; + +internal class Program { - internal class Program + [STAThread] + public static int Main(string[] args) { - [STAThread] - public static int Main(string[] args) + try + { + return Parser.Default.ParseArguments< + GenerateOptions, + ValidateOptions + >(args) + .MapResult( + (GenerateOptions options) => new GenerateCommand().Run(options), + (ValidateOptions options) => new ValidateCommand().Run(options), + _ => 1); + } + catch (Exception e) { - try - { - return Parser.Default.ParseArguments< - GenerateOptions, - ValidateOptions - >(args) - .MapResult( - (GenerateOptions options) => new GenerateCommand().Run(options), - (ValidateOptions options) => new ValidateCommand().Run(options), - _ => 1); - } - catch (Exception e) - { - Console.WriteLine(e); - return 1; - } + Console.WriteLine(e); + return 1; } } } \ No newline at end of file diff --git a/src/CSharp/Cask.Cli/ValidateCommand.cs b/src/CSharp/Cask.Cli/ValidateCommand.cs index 57376ec..3b84ca8 100644 --- a/src/CSharp/Cask.Cli/ValidateCommand.cs +++ b/src/CSharp/Cask.Cli/ValidateCommand.cs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. -namespace CommonAnnotatedSecurityKeys.Cli +namespace CommonAnnotatedSecurityKeys.Cli; + +public class ValidateCommand { - public class ValidateCommand + internal int Run(ValidateOptions _) { - internal int Run(ValidateOptions _) - { - return 0; - } + return 0; } } \ No newline at end of file diff --git a/src/CSharp/Cask.Cli/ValidateOptions.cs b/src/CSharp/Cask.Cli/ValidateOptions.cs index c5e1781..1aeb521 100644 --- a/src/CSharp/Cask.Cli/ValidateOptions.cs +++ b/src/CSharp/Cask.Cli/ValidateOptions.cs @@ -1,10 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. using CommandLine; -namespace CommonAnnotatedSecurityKeys.Cli +namespace CommonAnnotatedSecurityKeys.Cli; + +[Verb("validate", HelpText = "Validate one or more common annotated security keys.")] +public class ValidateOptions { - [Verb("validate", HelpText = "Validate one or more common annotated security keys.")] - public class ValidateOptions - { - } } \ No newline at end of file diff --git a/src/CSharp/CommonAnnotatedSecurityKeys/Cask.cs b/src/CSharp/CommonAnnotatedSecurityKeys/Cask.cs index e5e19d6..898bc7c 100644 --- a/src/CSharp/CommonAnnotatedSecurityKeys/Cask.cs +++ b/src/CSharp/CommonAnnotatedSecurityKeys/Cask.cs @@ -3,246 +3,245 @@ using System.Security.Cryptography; -namespace CommonAnnotatedSecurityKeys +namespace CommonAnnotatedSecurityKeys; + +public class Cask : ICask { - public class Cask : ICask - { - [ThreadStatic] - private static Lazy cask = - new(() => new Cask()); + [ThreadStatic] + private static Lazy cask = + new(() => new Cask()); - public static ICask Instance + public static ICask Instance + { + get { - get - { - return cask.Value; - } + return cask.Value; } + } - public Cask() - { - Utilities = new CaskUtilityApi(); - } + public Cask() + { + Utilities = new CaskUtilityApi(); + } - public ICaskUtilityApi Utilities { get; set; } + public ICaskUtilityApi Utilities { get; set; } - public bool IsCask(string key) + public bool IsCask(string key) + { + if (CaskUtilityApi.CaskSignature[0] != key[key.Length - 16] || + CaskUtilityApi.CaskSignature[1] != key[key.Length - 15] || + CaskUtilityApi.CaskSignature[2] != key[key.Length - 14] || + CaskUtilityApi.CaskSignature[3] != key[key.Length - 13]) { - if (CaskUtilityApi.CaskSignature[0] != key[key.Length - 16] || - CaskUtilityApi.CaskSignature[1] != key[key.Length - 15] || - CaskUtilityApi.CaskSignature[2] != key[key.Length - 14] || - CaskUtilityApi.CaskSignature[3] != key[key.Length - 13]) - { - return false; - } - byte[] keyBytes = Convert.FromBase64String(key.FromUrlSafe()); - return IsCask(keyBytes); + return false; } + byte[] keyBytes = Convert.FromBase64String(key.FromUrlSafe()); + return IsCask(keyBytes); + } - public bool IsCask(byte[] keyBytes) + public bool IsCask(byte[] keyBytes) + { + // This check ensures that 3 bytes of fixed signature are present + // where they belong. We next hash the key and ensure the first + // three bytes of the hash are present where they belong. This + // leads to a chance of collision of 1 in 2 ^ 48, or + // 1 in 281,474,976,710,656, or ~1 million times less likely than + // winning the Powerball lottery. + if (CaskUtilityApi.CaskSignatureBytes[0] != keyBytes[keyBytes.Length - 12] || + CaskUtilityApi.CaskSignatureBytes[1] != keyBytes[keyBytes.Length - 11] || + CaskUtilityApi.CaskSignatureBytes[2] != keyBytes[keyBytes.Length - 10]) { - // This check ensures that 3 bytes of fixed signature are present - // where they belong. We next hash the key and ensure the first - // three bytes of the hash are present where they belong. This - // leads to a chance of collision of 1 in 2 ^ 48, or - // 1 in 281,474,976,710,656, or ~1 million times less likely than - // winning the Powerball lottery. - if (CaskUtilityApi.CaskSignatureBytes[0] != keyBytes[keyBytes.Length - 12] || - CaskUtilityApi.CaskSignatureBytes[1] != keyBytes[keyBytes.Length - 11] || - CaskUtilityApi.CaskSignatureBytes[2] != keyBytes[keyBytes.Length - 10]) - { - return false; - } + return false; + } - Span toChecksum = new Span(keyBytes, 0, keyBytes.Length - 3); - byte[] crc32Bytes = new byte[4]; - Utilities.ComputeCrc32Hash(toChecksum, crc32Bytes); + Span toChecksum = new Span(keyBytes, 0, keyBytes.Length - 3); + byte[] crc32Bytes = new byte[4]; + Utilities.ComputeCrc32Hash(toChecksum, crc32Bytes); - return - crc32Bytes[0] == keyBytes[keyBytes.Length - 3] && - crc32Bytes[1] == keyBytes[keyBytes.Length - 2] && - crc32Bytes[2] == keyBytes[keyBytes.Length - 1]; - } + return + crc32Bytes[0] == keyBytes[keyBytes.Length - 3] && + crc32Bytes[1] == keyBytes[keyBytes.Length - 2] && + crc32Bytes[2] == keyBytes[keyBytes.Length - 1]; + } - public string GenerateKey(string providerSignature, - string allocatorCode, - string? reserved = null, - int secretEntropyInBytes = 32) - { - byte[] reservedBytes = reserved == null - ? Array.Empty() - : Convert.FromBase64String(reserved.FromUrlSafe()); + public string GenerateKey(string providerSignature, + string allocatorCode, + string? reserved = null, + int secretEntropyInBytes = 32) + { + byte[] reservedBytes = reserved == null + ? Array.Empty() + : Convert.FromBase64String(reserved.FromUrlSafe()); - byte[] providerSignatureBytes = Convert.FromBase64String(providerSignature); + byte[] providerSignatureBytes = Convert.FromBase64String(providerSignature); - byte[] keyBytes = GenerateKeyBytes(providerSignatureBytes, - allocatorCode, - reservedBytes, - secretEntropyInBytes); + byte[] keyBytes = GenerateKeyBytes(providerSignatureBytes, + allocatorCode, + reservedBytes, + secretEntropyInBytes); - return Convert.ToBase64String(keyBytes).ToUrlSafe(); - } + return Convert.ToBase64String(keyBytes).ToUrlSafe(); + } - public byte[] GenerateKeyBytes(byte[] providerSignature, - string allocatorCode, - byte[]? reserved = null, - int secretEntropyInBytes = 32, - char testChar = default) - { - // Ensure that the randomBytesCount is a multiple of 3. We keep all data - // aligned along a 3-byte boundary to ensure consistent base64 encoding - // in the key for fixed components. - secretEntropyInBytes = secretEntropyInBytes.RoundUpToMultipleOf(3); + public byte[] GenerateKeyBytes(byte[] providerSignature, + string allocatorCode, + byte[]? reserved = null, + int secretEntropyInBytes = 32, + char testChar = default) + { + // Ensure that the randomBytesCount is a multiple of 3. We keep all data + // aligned along a 3-byte boundary to ensure consistent base64 encoding + // in the key for fixed components. + secretEntropyInBytes = secretEntropyInBytes.RoundUpToMultipleOf(3); - byte[] allocatorAndTimestampBytes = GenerateAllocatorAndTimestampBytes(allocatorCode); + byte[] allocatorAndTimestampBytes = GenerateAllocatorAndTimestampBytes(allocatorCode); - int reservedLength = (reserved?.Length ?? 0); + int reservedLength = (reserved?.Length ?? 0); - int keyLength = secretEntropyInBytes + - reservedLength + - 3 + /* always 3 */ - allocatorAndTimestampBytes.Length + /* always 3 */ - providerSignature.Length + - 3; // Partial HMAC256 is 3 bytes. + int keyLength = secretEntropyInBytes + + reservedLength + + 3 + /* always 3 */ + allocatorAndTimestampBytes.Length + /* always 3 */ + providerSignature.Length + + 3; // Partial HMAC256 is 3 bytes. - // Start by filling the entire key with random bytes. - byte[] keyBytes = new byte[keyLength]; + // Start by filling the entire key with random bytes. + byte[] keyBytes = new byte[keyLength]; - if (testChar == default) - { - CaskUtilityApi.Rng.GetBytes(keyBytes); - } - else - { - string randomComponent = new string(testChar, secretEntropyInBytes.RoundUpToMultipleOf(3)); - Array.Copy(Convert.FromBase64String(randomComponent), 0, keyBytes, 0, secretEntropyInBytes); - } + if (testChar == default) + { + CaskUtilityApi.Rng.GetBytes(keyBytes); + } + else + { + string randomComponent = new string(testChar, secretEntropyInBytes.RoundUpToMultipleOf(3)); + Array.Copy(Convert.FromBase64String(randomComponent), 0, keyBytes, 0, secretEntropyInBytes); + } - int reservedOffset = secretEntropyInBytes; - int caskSignatureOffset = reservedOffset + reservedLength; - int allocatorAndTimestampOffset = caskSignatureOffset + 3; - int providerSignatureOffset = allocatorAndTimestampOffset + allocatorAndTimestampBytes.Length; - int partialHashOffset = providerSignatureOffset + providerSignature.Length; + int reservedOffset = secretEntropyInBytes; + int caskSignatureOffset = reservedOffset + reservedLength; + int allocatorAndTimestampOffset = caskSignatureOffset + 3; + int providerSignatureOffset = allocatorAndTimestampOffset + allocatorAndTimestampBytes.Length; + int partialHashOffset = providerSignatureOffset + providerSignature.Length; - // Copy optional reserved bytes, if provided. - Array.Copy(reserved ?? new byte[] { }, 0, keyBytes, reservedOffset, reserved?.Length ?? 0); + // Copy optional reserved bytes, if provided. + Array.Copy(reserved ?? new byte[] { }, 0, keyBytes, reservedOffset, reserved?.Length ?? 0); - // Copy 'JQQJ', the CASK standard fixed signature, into the key. - Array.Copy(CaskUtilityApi.CaskSignatureBytes, 0, keyBytes, caskSignatureOffset, 3); + // Copy 'JQQJ', the CASK standard fixed signature, into the key. + Array.Copy(CaskUtilityApi.CaskSignatureBytes, 0, keyBytes, caskSignatureOffset, 3); - // Copy the allocator and timestamp into the key. - Array.Copy(allocatorAndTimestampBytes, 0, keyBytes, allocatorAndTimestampOffset, allocatorAndTimestampBytes.Length); + // Copy the allocator and timestamp into the key. + Array.Copy(allocatorAndTimestampBytes, 0, keyBytes, allocatorAndTimestampOffset, allocatorAndTimestampBytes.Length); - // Copy the key provider's signature into the key. - Array.Copy(providerSignature, 0, keyBytes, providerSignatureOffset, providerSignature.Length); + // Copy the key provider's signature into the key. + Array.Copy(providerSignature, 0, keyBytes, providerSignatureOffset, providerSignature.Length); - Span toChecksum = new Span(keyBytes, 0, partialHashOffset); + Span toChecksum = new Span(keyBytes, 0, partialHashOffset); - byte[] crc32Bytes = new byte[4]; - Utilities.ComputeCrc32Hash(toChecksum, crc32Bytes); + byte[] crc32Bytes = new byte[4]; + Utilities.ComputeCrc32Hash(toChecksum, crc32Bytes); - Array.Copy(crc32Bytes, 0, keyBytes, partialHashOffset, 3); + Array.Copy(crc32Bytes, 0, keyBytes, partialHashOffset, 3); - // Done. - return keyBytes; - } + // Done. + return keyBytes; + } - internal byte[] GenerateAllocatorAndTimestampBytes(string allocatorCode) + internal byte[] GenerateAllocatorAndTimestampBytes(string allocatorCode) + { + if (!ContainsOnlyUrlSafeBase64Characters(allocatorCode)) { - if (!ContainsOnlyUrlSafeBase64Characters(allocatorCode)) - { - throw new ArgumentException($"Allocator code includes characters that are not legal URL-safe base64: '{allocatorCode}"); - } - - DateTimeOffset utcNow = Utilities.GetCurrentDateTimeUtc(); + throw new ArgumentException($"Allocator code includes characters that are not legal URL-safe base64: '{allocatorCode}"); + } - if (utcNow.Year < 2024 || utcNow.Year > 2087) - { - throw new ArgumentOutOfRangeException("CASK requires the current year to be between 2024 and 2087."); - } + DateTimeOffset utcNow = Utilities.GetCurrentDateTimeUtc(); - char yearsSince2024 = CaskUtilityApi.OrderedUrlSafeBase64Characters[utcNow.Year - 2024]; - char zeroIndexedMonth = CaskUtilityApi.OrderedUrlSafeBase64Characters[utcNow.Month - 1]; - string allocatorAndTimestamp = $"{allocatorCode}{yearsSince2024}{zeroIndexedMonth}"; - return Convert.FromBase64String(allocatorAndTimestamp.FromUrlSafe()); + if (utcNow.Year < 2024 || utcNow.Year > 2087) + { + throw new ArgumentOutOfRangeException("CASK requires the current year to be between 2024 and 2087."); } - private static bool ContainsOnlyUrlSafeBase64Characters(string allocatorCode) + char yearsSince2024 = CaskUtilityApi.OrderedUrlSafeBase64Characters[utcNow.Year - 2024]; + char zeroIndexedMonth = CaskUtilityApi.OrderedUrlSafeBase64Characters[utcNow.Month - 1]; + string allocatorAndTimestamp = $"{allocatorCode}{yearsSince2024}{zeroIndexedMonth}"; + return Convert.FromBase64String(allocatorAndTimestamp.FromUrlSafe()); + } + + private static bool ContainsOnlyUrlSafeBase64Characters(string allocatorCode) + { + foreach (char c in allocatorCode) { - foreach (char c in allocatorCode) + if (!CaskUtilityApi.UrlSafeBase64Characters.Contains(c)) { - if (!CaskUtilityApi.UrlSafeBase64Characters.Contains(c)) - { - return false; - } + return false; } - - return true; } - public string GenerateHash(byte[] derivationInput, byte[] secret, int secretEntropyInBytes) - { - byte[] hash = GenerateHashedSignatureBytes(derivationInput, secret, secretEntropyInBytes); - return Convert.ToBase64String(hash).ToUrlSafe(); - } + return true; + } - internal byte[] GenerateHashedSignatureBytes(byte[] derivationInput, byte[] secret, int secretEntropyInBytes) - { - byte[] allocatorAndTimeStampBytes = new byte[3]; + public string GenerateHash(byte[] derivationInput, byte[] secret, int secretEntropyInBytes) + { + byte[] hash = GenerateHashedSignatureBytes(derivationInput, secret, secretEntropyInBytes); + return Convert.ToBase64String(hash).ToUrlSafe(); + } - secretEntropyInBytes = secretEntropyInBytes.RoundUpToMultipleOf(3); - int reservedBytesLength = secret.Length - 12 - secretEntropyInBytes; + internal byte[] GenerateHashedSignatureBytes(byte[] derivationInput, byte[] secret, int secretEntropyInBytes) + { + byte[] allocatorAndTimeStampBytes = new byte[3]; - using var hmac = new HMACSHA256(secret); - byte[] hash = hmac.ComputeHash(derivationInput); + secretEntropyInBytes = secretEntropyInBytes.RoundUpToMultipleOf(3); + int reservedBytesLength = secret.Length - 12 - secretEntropyInBytes; - byte[] hashedSignature = new byte[33 + 12 + reservedBytesLength]; + using var hmac = new HMACSHA256(secret); + byte[] hash = hmac.ComputeHash(derivationInput); - // Move the literal hash over. - Array.Copy(hash, 0, hashedSignature, 0, 32); + byte[] hashedSignature = new byte[33 + 12 + reservedBytesLength]; - // Recapitulate other data. - int reservedOffset = 33; - Array.Copy(secret, secretEntropyInBytes, hashedSignature, reservedOffset, reservedBytesLength); + // Move the literal hash over. + Array.Copy(hash, 0, hashedSignature, 0, 32); - int standardOffset = reservedOffset + reservedBytesLength; - Array.Copy(CaskUtilityApi.CaskSignatureBytes, 0, hashedSignature, standardOffset, 3); + // Recapitulate other data. + int reservedOffset = 33; + Array.Copy(secret, secretEntropyInBytes, hashedSignature, reservedOffset, reservedBytesLength); - byte[] secretAllocatorAndTimeStampBytes = new byte[3]; - int secretAllocatorAndTimeStampBytesOffset = secretEntropyInBytes + reservedBytesLength + 3; - Array.Copy(secret, secretAllocatorAndTimeStampBytesOffset, secretAllocatorAndTimeStampBytes, 0, 3); + int standardOffset = reservedOffset + reservedBytesLength; + Array.Copy(CaskUtilityApi.CaskSignatureBytes, 0, hashedSignature, standardOffset, 3); - byte yearsSince2024 = (byte)(DateTime.UtcNow.Year - 2024); - byte zeroIndexedMonth = (byte)(DateTime.UtcNow.Month - 1); + byte[] secretAllocatorAndTimeStampBytes = new byte[3]; + int secretAllocatorAndTimeStampBytesOffset = secretEntropyInBytes + reservedBytesLength + 3; + Array.Copy(secret, secretAllocatorAndTimeStampBytesOffset, secretAllocatorAndTimeStampBytes, 0, 3); - int? metadata = (61 << 18) | (61 << 12) | (yearsSince2024 << 6) | zeroIndexedMonth; - byte[] metadataBytes = BitConverter.GetBytes(metadata.Value); + byte yearsSince2024 = (byte)(DateTime.UtcNow.Year - 2024); + byte zeroIndexedMonth = (byte)(DateTime.UtcNow.Month - 1); - int allocatorAndTimestampOffset = standardOffset + 3; + int? metadata = (61 << 18) | (61 << 12) | (yearsSince2024 << 6) | zeroIndexedMonth; + byte[] metadataBytes = BitConverter.GetBytes(metadata.Value); - hashedSignature[allocatorAndTimestampOffset] = secret[secretAllocatorAndTimeStampBytesOffset]; - hashedSignature[allocatorAndTimestampOffset + 1] = (byte)((secret[secretAllocatorAndTimeStampBytesOffset + 1] & 0xf0) | (yearsSince2024 >> 4 & 0x3)); - hashedSignature[allocatorAndTimestampOffset + 2] = (byte)(yearsSince2024 << 6 | zeroIndexedMonth); + int allocatorAndTimestampOffset = standardOffset + 3; - int secretProviderSignatureBytesOffset = secretAllocatorAndTimeStampBytesOffset + 3; - int providerSignatureBytesOffset = allocatorAndTimestampOffset + 3; - Array.Copy(secret, secretProviderSignatureBytesOffset, hashedSignature, providerSignatureBytesOffset, 3); + hashedSignature[allocatorAndTimestampOffset] = secret[secretAllocatorAndTimeStampBytesOffset]; + hashedSignature[allocatorAndTimestampOffset + 1] = (byte)((secret[secretAllocatorAndTimeStampBytesOffset + 1] & 0xf0) | (yearsSince2024 >> 4 & 0x3)); + hashedSignature[allocatorAndTimestampOffset + 2] = (byte)(yearsSince2024 << 6 | zeroIndexedMonth); - Span toChecksum = new Span(hashedSignature, 0, providerSignatureBytesOffset + 3); + int secretProviderSignatureBytesOffset = secretAllocatorAndTimeStampBytesOffset + 3; + int providerSignatureBytesOffset = allocatorAndTimestampOffset + 3; + Array.Copy(secret, secretProviderSignatureBytesOffset, hashedSignature, providerSignatureBytesOffset, 3); - byte[] crc32Bytes = new byte[4]; - Utilities.ComputeCrc32Hash(toChecksum, crc32Bytes); + Span toChecksum = new Span(hashedSignature, 0, providerSignatureBytesOffset + 3); - int crc32HashOffset = providerSignatureBytesOffset + 3; - Array.Copy(crc32Bytes, 0, hashedSignature, crc32HashOffset, 3); + byte[] crc32Bytes = new byte[4]; + Utilities.ComputeCrc32Hash(toChecksum, crc32Bytes); - return hashedSignature; - } + int crc32HashOffset = providerSignatureBytesOffset + 3; + Array.Copy(crc32Bytes, 0, hashedSignature, crc32HashOffset, 3); - public bool CompareHash(byte[] candidateHash, byte[] derivationInput, byte[] secret, int secretEntropyInBytes) - { - byte[] computedHash = GenerateHashedSignatureBytes(derivationInput, secret, secretEntropyInBytes); - return CryptographicOperations.FixedTimeEquals(computedHash, candidateHash); - } + return hashedSignature; + } + + public bool CompareHash(byte[] candidateHash, byte[] derivationInput, byte[] secret, int secretEntropyInBytes) + { + byte[] computedHash = GenerateHashedSignatureBytes(derivationInput, secret, secretEntropyInBytes); + return CryptographicOperations.FixedTimeEquals(computedHash, candidateHash); } } \ No newline at end of file diff --git a/src/CSharp/CommonAnnotatedSecurityKeys/CaskExtensionMethods.cs b/src/CSharp/CommonAnnotatedSecurityKeys/CaskExtensionMethods.cs index 9245001..407d6f2 100644 --- a/src/CSharp/CommonAnnotatedSecurityKeys/CaskExtensionMethods.cs +++ b/src/CSharp/CommonAnnotatedSecurityKeys/CaskExtensionMethods.cs @@ -1,23 +1,22 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace CommonAnnotatedSecurityKeys +namespace CommonAnnotatedSecurityKeys; + +internal static class CaskExtensionMethods { - internal static class CaskExtensionMethods + internal static string FromUrlSafe(this string base64) { - internal static string FromUrlSafe(this string base64) - { - return base64.Replace('-', '+').Replace('_', '/'); - } + return base64.Replace('-', '+').Replace('_', '/'); + } - internal static string ToUrlSafe(this string base64) - { - return base64.Replace('+', '-').Replace('/', '_'); - } + internal static string ToUrlSafe(this string base64) + { + return base64.Replace('+', '-').Replace('/', '_'); + } - internal static int RoundUpToMultipleOf(this int value, int multiple) - { - return (value + multiple - 1) / multiple * multiple; - } + internal static int RoundUpToMultipleOf(this int value, int multiple) + { + return (value + multiple - 1) / multiple * multiple; } } \ No newline at end of file diff --git a/src/CSharp/CommonAnnotatedSecurityKeys/CaskUtilityApi.cs b/src/CSharp/CommonAnnotatedSecurityKeys/CaskUtilityApi.cs index 00c56a0..6870dcc 100644 --- a/src/CSharp/CommonAnnotatedSecurityKeys/CaskUtilityApi.cs +++ b/src/CSharp/CommonAnnotatedSecurityKeys/CaskUtilityApi.cs @@ -7,90 +7,89 @@ using System.Text; using System.Text.RegularExpressions; -namespace CommonAnnotatedSecurityKeys +namespace CommonAnnotatedSecurityKeys; + +public class CaskUtilityApi : ICaskUtilityApi { - public class CaskUtilityApi : ICaskUtilityApi - { - [ThreadStatic] - private static Lazy caskConstants = - new(() => new CaskUtilityApi()); + [ThreadStatic] + private static Lazy caskConstants = + new(() => new CaskUtilityApi()); - [ThreadStatic] - private static readonly Lazy crc32 = - new(() => new Crc32()); + [ThreadStatic] + private static readonly Lazy crc32 = + new(() => new Crc32()); - [ThreadStatic] - private static readonly Lazy sha256 = - new(() => SHA256.Create()); + [ThreadStatic] + private static readonly Lazy sha256 = + new(() => SHA256.Create()); - [ThreadStatic] - private static readonly Lazy caskHmac256 = - new(() => new HMACSHA256(Encoding.UTF8.GetBytes("Cask_v1"))); + [ThreadStatic] + private static readonly Lazy caskHmac256 = + new(() => new HMACSHA256(Encoding.UTF8.GetBytes("Cask_v1"))); - [ThreadStatic] - private static readonly Lazy rng = - new(() => RandomNumberGenerator.Create()); + [ThreadStatic] + private static readonly Lazy rng = + new(() => RandomNumberGenerator.Create()); - public static string CaskSignature => "JQQJ"; + public static string CaskSignature => "JQQJ"; - public static byte[] CaskSignatureBytes => Convert.FromBase64String(CaskSignature); + public static byte[] CaskSignatureBytes => Convert.FromBase64String(CaskSignature); - public static readonly Regex CaskKeyRegex = - new Regex("(^|[^A-Za-z0-9+/-_])([A-Za-z0-9-_]{4}){6,}JQQJ[A-Za-z0-9-_]{12}($|[^A-Za-z0-9+/-_])", - RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant); + public static readonly Regex CaskKeyRegex = + new Regex("(^|[^A-Za-z0-9+/-_])([A-Za-z0-9-_]{4}){6,}JQQJ[A-Za-z0-9-_]{12}($|[^A-Za-z0-9+/-_])", + RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant); - public static ICaskUtilityApi Instance + public static ICaskUtilityApi Instance + { + get { - get + if (caskConstants.Value == null) { - if (caskConstants.Value == null) - { - caskConstants = new Lazy(() => new CaskUtilityApi()); - } - return caskConstants.Value; + caskConstants = new Lazy(() => new CaskUtilityApi()); } - set { caskConstants = new(() => value); } + return caskConstants.Value; } + set { caskConstants = new(() => value); } + } - public static Crc32 Crc32 - { - get { return crc32.Value; } - } + public static Crc32 Crc32 + { + get { return crc32.Value; } + } - public static SHA256 Sha256 - { - get { return sha256.Value; } - } + public static SHA256 Sha256 + { + get { return sha256.Value; } + } - public static HMACSHA256 Hmac256 - { - get { return caskHmac256.Value; } - } + public static HMACSHA256 Hmac256 + { + get { return caskHmac256.Value; } + } - public static RandomNumberGenerator Rng - { - get { return rng.Value; } - } + public static RandomNumberGenerator Rng + { + get { return rng.Value; } + } - private static readonly char[] orderedUrlSafeBase64Characters = new[] { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' }; + private static readonly char[] orderedUrlSafeBase64Characters = new[] { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' }; - public static ReadOnlySpan OrderedUrlSafeBase64Characters => orderedUrlSafeBase64Characters; + public static ReadOnlySpan OrderedUrlSafeBase64Characters => orderedUrlSafeBase64Characters; - public static readonly ISet UrlSafeBase64Characters = orderedUrlSafeBase64Characters.ToImmutableHashSet(); + public static readonly ISet UrlSafeBase64Characters = orderedUrlSafeBase64Characters.ToImmutableHashSet(); - public virtual DateTimeOffset GetCurrentDateTimeUtc() - { - return DateTimeOffset.UtcNow; - } + public virtual DateTimeOffset GetCurrentDateTimeUtc() + { + return DateTimeOffset.UtcNow; + } - public virtual void ComputeCrc32Hash(ReadOnlySpan toChecksum, Span destination) - { - Crc32.Reset(); - Crc32.Append(toChecksum); - Crc32.GetHashAndReset(destination); - } + public virtual void ComputeCrc32Hash(ReadOnlySpan toChecksum, Span destination) + { + Crc32.Reset(); + Crc32.Append(toChecksum); + Crc32.GetHashAndReset(destination); } } \ No newline at end of file diff --git a/src/CSharp/CommonAnnotatedSecurityKeys/ICask.cs b/src/CSharp/CommonAnnotatedSecurityKeys/ICask.cs index c56ca43..d4949e4 100644 --- a/src/CSharp/CommonAnnotatedSecurityKeys/ICask.cs +++ b/src/CSharp/CommonAnnotatedSecurityKeys/ICask.cs @@ -1,28 +1,27 @@ // Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. // See LICENSE file in the project root for full license information. -namespace CommonAnnotatedSecurityKeys +namespace CommonAnnotatedSecurityKeys; + +public interface ICask { - public interface ICask - { - ICaskUtilityApi Utilities { get; set; } + ICaskUtilityApi Utilities { get; set; } - bool IsCask(string keyOrHash); + bool IsCask(string keyOrHash); - bool IsCask(byte[] keyOrHashBytes); + bool IsCask(byte[] keyOrHashBytes); - string GenerateKey(string providerSignature, - string allocatorCode, - string? reserved = null, - int secretEntropyInBytes = 32); + string GenerateKey(string providerSignature, + string allocatorCode, + string? reserved = null, + int secretEntropyInBytes = 32); - string GenerateHash(byte[] derivationInput, - byte[] secret, - int secretEntropyInBytes = 32); + string GenerateHash(byte[] derivationInput, + byte[] secret, + int secretEntropyInBytes = 32); - bool CompareHash(byte[] candidateHash, - byte[] derivationInput, - byte[] secret, - int secretEntropyInBytes = 32); - } + bool CompareHash(byte[] candidateHash, + byte[] derivationInput, + byte[] secret, + int secretEntropyInBytes = 32); } \ No newline at end of file diff --git a/src/CSharp/CommonAnnotatedSecurityKeys/ICaskUtilityApi.cs b/src/CSharp/CommonAnnotatedSecurityKeys/ICaskUtilityApi.cs index 0b4fb66..1274eee 100644 --- a/src/CSharp/CommonAnnotatedSecurityKeys/ICaskUtilityApi.cs +++ b/src/CSharp/CommonAnnotatedSecurityKeys/ICaskUtilityApi.cs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. // See LICENSE file in the project root for full license information. -namespace CommonAnnotatedSecurityKeys +namespace CommonAnnotatedSecurityKeys; + +public interface ICaskUtilityApi { - public interface ICaskUtilityApi - { - DateTimeOffset GetCurrentDateTimeUtc(); + DateTimeOffset GetCurrentDateTimeUtc(); - void ComputeCrc32Hash(ReadOnlySpan toChecksum, Span destination); - } + void ComputeCrc32Hash(ReadOnlySpan toChecksum, Span destination); } \ No newline at end of file diff --git a/src/CSharp/CommonAnnotatedSecurityKeys/Polyfill/CryptographicOperations.cs b/src/CSharp/CommonAnnotatedSecurityKeys/Polyfill/CryptographicOperations.cs index 692812b..56ebeaf 100644 --- a/src/CSharp/CommonAnnotatedSecurityKeys/Polyfill/CryptographicOperations.cs +++ b/src/CSharp/CommonAnnotatedSecurityKeys/Polyfill/CryptographicOperations.cs @@ -7,53 +7,52 @@ #if NETFRAMEWORK using System.Runtime.CompilerServices; -namespace System.Security.Cryptography -{ - internal static class CryptographicOperations - { - /// - /// Determine the equality of two byte sequences in an amount of time which depends on - /// the length of the sequences, but not the values. - /// - /// The first buffer to compare. - /// The second buffer to compare. - /// - /// true if and have the same - /// values for and the same contents, false - /// otherwise. - /// - /// - /// This method compares two buffers' contents for equality in a manner which does not - /// leak timing information, making it ideal for use within cryptographic routines. - /// This method will short-circuit and return false only if - /// and have different lengths. - /// - /// Fixed-time behavior is guaranteed in all other cases, including if - /// and reference the same address. - /// - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) - { - // NoOptimization because we want this method to be exactly as non-short-circuiting - // as written. - // - // NoInlining because the NoOptimization would get lost if the method got inlined. +namespace System.Security.Cryptography; - if (left.Length != right.Length) - { - return false; - } +internal static class CryptographicOperations +{ + /// + /// Determine the equality of two byte sequences in an amount of time which depends on + /// the length of the sequences, but not the values. + /// + /// The first buffer to compare. + /// The second buffer to compare. + /// + /// true if and have the same + /// values for and the same contents, false + /// otherwise. + /// + /// + /// This method compares two buffers' contents for equality in a manner which does not + /// leak timing information, making it ideal for use within cryptographic routines. + /// This method will short-circuit and return false only if + /// and have different lengths. + /// + /// Fixed-time behavior is guaranteed in all other cases, including if + /// and reference the same address. + /// + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static bool FixedTimeEquals(ReadOnlySpan left, ReadOnlySpan right) + { + // NoOptimization because we want this method to be exactly as non-short-circuiting + // as written. + // + // NoInlining because the NoOptimization would get lost if the method got inlined. - int length = left.Length; - int accum = 0; + if (left.Length != right.Length) + { + return false; + } - for (int i = 0; i < length; i++) - { - accum |= left[i] - right[i]; - } + int length = left.Length; + int accum = 0; - return accum == 0; + for (int i = 0; i < length; i++) + { + accum |= left[i] - right[i]; } + + return accum == 0; } } #endif \ No newline at end of file diff --git a/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CSharpCaskTests.cs b/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CSharpCaskTests.cs index 7395870..117350b 100644 --- a/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CSharpCaskTests.cs +++ b/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CSharpCaskTests.cs @@ -2,12 +2,11 @@ using CommonAnnotatedSecurityKeys; -namespace Tests.CommonAnnotatedSecurityKeys +namespace Tests.CommonAnnotatedSecurityKeys; + +public class CSharpCaskTests : CaskTestsBase { - public class CSharpCaskTests : CaskTestsBase + public CSharpCaskTests() : base(new Cask()) { - public CSharpCaskTests() : base(new Cask()) - { - } } } \ No newline at end of file diff --git a/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CaskSecretsTests.cs b/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CaskSecretsTests.cs index 8c1e22e..d19cbe4 100644 --- a/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CaskSecretsTests.cs +++ b/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/CaskSecretsTests.cs @@ -7,161 +7,160 @@ using Xunit; -namespace Tests.CommonAnnotatedSecurityKeys +namespace Tests.CommonAnnotatedSecurityKeys; + +public abstract class CaskTestsBase { - public abstract class CaskTestsBase + protected CaskTestsBase(ICask cask) { - protected CaskTestsBase(ICask cask) - { - Cask = cask; - } + Cask = cask; + } - protected ICask Cask { get; } + protected ICask Cask { get; } - [Theory, InlineData(16), InlineData(32), InlineData(64)] - public void CaskSecrets_IsCask(int secretEntropyInBytes) - { - string key = Cask.GenerateKey(providerSignature: "TEST", - allocatorCode: "88", - reserved: null, - secretEntropyInBytes); + [Theory, InlineData(16), InlineData(32), InlineData(64)] + public void CaskSecrets_IsCask(int secretEntropyInBytes) + { + string key = Cask.GenerateKey(providerSignature: "TEST", + allocatorCode: "88", + reserved: null, + secretEntropyInBytes); - IsCaskValidate(Cask, key); - } + IsCaskValidate(Cask, key); + } - [Theory, InlineData(16), InlineData(32), InlineData(64)] - public void CaskSecrets_GenerateKey_Basic(int secretEntropyInBytes) - { + [Theory, InlineData(16), InlineData(32), InlineData(64)] + public void CaskSecrets_GenerateKey_Basic(int secretEntropyInBytes) + { - string key = Cask.GenerateKey(providerSignature: "TEST", - allocatorCode: "88", - reserved: "ABCD", - secretEntropyInBytes); + string key = Cask.GenerateKey(providerSignature: "TEST", + allocatorCode: "88", + reserved: "ABCD", + secretEntropyInBytes); - byte[] keyBytes = Convert.FromBase64String(key.FromUrlSafe()); - Assert.True(keyBytes.Length % 3 == 0, "'GenerateKey' output wasn't aligned on a 3-byte boundary."); + byte[] keyBytes = Convert.FromBase64String(key.FromUrlSafe()); + Assert.True(keyBytes.Length % 3 == 0, "'GenerateKey' output wasn't aligned on a 3-byte boundary."); - IsCaskValidate(Cask, key); + IsCaskValidate(Cask, key); - } + } - [Theory] - [InlineData(16, 2023), InlineData(16, 2088)] - [InlineData(32, 2023), InlineData(32, 2088)] - [InlineData(64, 2023), InlineData(64, 2088)] - public void CaskSecrets_GenerateKey_InvalidTimestamps(int secretEntropyInBytes, int invalidYear) + [Theory] + [InlineData(16, 2023), InlineData(16, 2088)] + [InlineData(32, 2023), InlineData(32, 2088)] + [InlineData(64, 2023), InlineData(64, 2088)] + public void CaskSecrets_GenerateKey_InvalidTimestamps(int secretEntropyInBytes, int invalidYear) + { + // The CASK standard timestamp is only valid from 2024 - 2087 + // (where the base64-encoded character 'A' indicates 2024, and + // the last valid base64 character '_' indicates 2087. + + var caskUtilities = Cask.Utilities; + var testCaskUtilityApi = new TestCaskUtilityApi(); + Cask.Utilities = testCaskUtilityApi; + try { - // The CASK standard timestamp is only valid from 2024 - 2087 - // (where the base64-encoded character 'A' indicates 2024, and - // the last valid base64 character '_' indicates 2087. - - var caskUtilities = Cask.Utilities; - var testCaskUtilityApi = new TestCaskUtilityApi(); - Cask.Utilities = testCaskUtilityApi; - try + for (int month = 0; month < 12; month++) { - for (int month = 0; month < 12; month++) - { - testCaskUtilityApi.GetCurrentDateTimeUtcFunc = - () => new DateTimeOffset(new DateOnly(invalidYear, 1 + month, 1), default, default); + testCaskUtilityApi.GetCurrentDateTimeUtcFunc = + () => new DateTimeOffset(new DateOnly(invalidYear, 1 + month, 1), default, default); - var action = () => Cask.GenerateKey(providerSignature: "TEST", - allocatorCode: "88", - reserved: "ABCD", - secretEntropyInBytes); + var action = () => Cask.GenerateKey(providerSignature: "TEST", + allocatorCode: "88", + reserved: "ABCD", + secretEntropyInBytes); - Assert.Throws(action); - } - } - finally - { - Cask.Utilities = caskUtilities; + Assert.Throws(action); } } + finally + { + Cask.Utilities = caskUtilities; + } + } + + [Theory, InlineData(16), InlineData(32), InlineData(64)] + public void CaskSecrets_GenerateKey_ValidTimestamps(int secretEntropyInBytes) + { + var testCaskUtilityApi = new TestCaskUtilityApi(); + CaskUtilityApi.Instance = testCaskUtilityApi; - [Theory, InlineData(16), InlineData(32), InlineData(64)] - public void CaskSecrets_GenerateKey_ValidTimestamps(int secretEntropyInBytes) + try { - var testCaskUtilityApi = new TestCaskUtilityApi(); - CaskUtilityApi.Instance = testCaskUtilityApi; - try + // Every year from 2024 - 2087 should produce a valid key. + // We trust that the CASK standard will be long dead by + // 2087 or perhaps simply all or most programmers will be. + for (int year = 0; year < 64; year++) { - - // Every year from 2024 - 2087 should produce a valid key. - // We trust that the CASK standard will be long dead by - // 2087 or perhaps simply all or most programmers will be. - for (int year = 0; year < 64; year++) + for (int month = 0; month < 12; month++) { - for (int month = 0; month < 12; month++) - { - testCaskUtilityApi.GetCurrentDateTimeUtcFunc = - () => new DateTimeOffset(new DateOnly(2024 + year, 1 + month, 1), default, default); - - string key = Cask.GenerateKey(providerSignature: "TEST", - allocatorCode: "88", - reserved: "ABCD", - secretEntropyInBytes); + testCaskUtilityApi.GetCurrentDateTimeUtcFunc = + () => new DateTimeOffset(new DateOnly(2024 + year, 1 + month, 1), default, default); - IsCaskValidate(Cask, key); - } + string key = Cask.GenerateKey(providerSignature: "TEST", + allocatorCode: "88", + reserved: "ABCD", + secretEntropyInBytes); + IsCaskValidate(Cask, key); } - } - finally - { - CaskUtilityApi.Instance = null!; + } } - - private void IsCaskValidate(ICask cask, string key) + finally { - // Positive test cases. - Assert.True(cask.IsCask(key), $"'GenerateKey' output failed 'IsCask(string)': {key}"); + CaskUtilityApi.Instance = null!; + } + } - byte[] keyBytes = Convert.FromBase64String(key.FromUrlSafe()); - Assert.True(cask.IsCask(keyBytes), $"'GenerateKey' output failed 'IsCask(byte[]): {key}'."); + private void IsCaskValidate(ICask cask, string key) + { + // Positive test cases. + Assert.True(cask.IsCask(key), $"'GenerateKey' output failed 'IsCask(string)': {key}"); - // Now we will modify the CASK standard fixed signature only ('JQQJ'). - // We will recompute the checksum and replace it, to ensure that it - // is the signature check, and not the checksum hash, that - // invalidates the secret. + byte[] keyBytes = Convert.FromBase64String(key.FromUrlSafe()); + Assert.True(cask.IsCask(keyBytes), $"'GenerateKey' output failed 'IsCask(byte[]): {key}'."); - int signatureIndex = key.LastIndexOf("JQQJ"); - for (int i = 0; i < 4; i++) - { - // Cycle through XQQJ, JXQJ, JQXJ, and JQQX. - string modifiedKey = $"{key.Substring(0, signatureIndex + i)}X{key.Substring(signatureIndex + i + 1)}"; + // Now we will modify the CASK standard fixed signature only ('JQQJ'). + // We will recompute the checksum and replace it, to ensure that it + // is the signature check, and not the checksum hash, that + // invalidates the secret. - Span toChecksum = new Span(keyBytes, 0, keyBytes.Length - 3); + int signatureIndex = key.LastIndexOf("JQQJ"); + for (int i = 0; i < 4; i++) + { + // Cycle through XQQJ, JXQJ, JQXJ, and JQQX. + string modifiedKey = $"{key.Substring(0, signatureIndex + i)}X{key.Substring(signatureIndex + i + 1)}"; - byte[] crc32Bytes = new byte[4]; - CaskUtilityApi.Instance.ComputeCrc32Hash(toChecksum, crc32Bytes); + Span toChecksum = new Span(keyBytes, 0, keyBytes.Length - 3); - string checksum = Convert.ToBase64String(crc32Bytes).ToUrlSafe().Substring(0, 4); - modifiedKey = $"{modifiedKey.Substring(0, modifiedKey.Length - 4)}{checksum}"; + byte[] crc32Bytes = new byte[4]; + CaskUtilityApi.Instance.ComputeCrc32Hash(toChecksum, crc32Bytes); - Assert.False(cask.IsCask(modifiedKey), $"'IsCask(string)' unexpectedly succeeded with modified 'JQQJ' signature: {modifiedKey}"); + string checksum = Convert.ToBase64String(crc32Bytes).ToUrlSafe().Substring(0, 4); + modifiedKey = $"{modifiedKey.Substring(0, modifiedKey.Length - 4)}{checksum}"; - keyBytes = Convert.FromBase64String(modifiedKey.FromUrlSafe()); - Assert.False(cask.IsCask(keyBytes), $"'IsCask(byte[])' unexpectedly succeeded with modified 'JQQJ' signature: {modifiedKey}"); - } + Assert.False(cask.IsCask(modifiedKey), $"'IsCask(string)' unexpectedly succeeded with modified 'JQQJ' signature: {modifiedKey}"); - // Having established that the key is a CASK secret, we now will modify - // every character in the key, which should invalidate the checksum. + keyBytes = Convert.FromBase64String(modifiedKey.FromUrlSafe()); + Assert.False(cask.IsCask(keyBytes), $"'IsCask(byte[])' unexpectedly succeeded with modified 'JQQJ' signature: {modifiedKey}"); + } - for (int i = 0; i < key.Length; i++) - { - char replacement = key[i] == '-' ? '_' : '-'; - string modifiedKey = $"{key.Substring(0, i)}{replacement}{key.Substring(i + 1)}"; + // Having established that the key is a CASK secret, we now will modify + // every character in the key, which should invalidate the checksum. - bool result = cask.IsCask(modifiedKey); - Assert.False(result, $"'IsCask(string)' unexpectedly succeeded after invalidating checksum: {modifiedKey}. Original key was: {key}"); + for (int i = 0; i < key.Length; i++) + { + char replacement = key[i] == '-' ? '_' : '-'; + string modifiedKey = $"{key.Substring(0, i)}{replacement}{key.Substring(i + 1)}"; - keyBytes = Convert.FromBase64String(modifiedKey.FromUrlSafe()); - result = cask.IsCask(keyBytes); - Assert.False(result, $"'IsCask(byte[])' unexpectedly succeeded after invalidating checksum: {modifiedKey}. Original key was: {key}"); - } + bool result = cask.IsCask(modifiedKey); + Assert.False(result, $"'IsCask(string)' unexpectedly succeeded after invalidating checksum: {modifiedKey}. Original key was: {key}"); + + keyBytes = Convert.FromBase64String(modifiedKey.FromUrlSafe()); + result = cask.IsCask(keyBytes); + Assert.False(result, $"'IsCask(byte[])' unexpectedly succeeded after invalidating checksum: {modifiedKey}. Original key was: {key}"); } } } \ No newline at end of file diff --git a/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/TestCaskUtilityApi.cs b/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/TestCaskUtilityApi.cs index cf6c542..9c056c6 100644 --- a/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/TestCaskUtilityApi.cs +++ b/src/CSharp/Test/Tests.CommonAnnotatedSecurityKeys/TestCaskUtilityApi.cs @@ -5,29 +5,28 @@ using System; -namespace Tests.CommonAnnotatedSecurityKeys +namespace Tests.CommonAnnotatedSecurityKeys; + +internal class TestCaskUtilityApi : CaskUtilityApi { - internal class TestCaskUtilityApi : CaskUtilityApi + public Func? GetCurrentDateTimeUtcFunc; + public Action? ComputeCrc32HashAction = null; // Not currently assigned anywhere else, redundant null assignment to silence compiler warning. + + public override DateTimeOffset GetCurrentDateTimeUtc() { - public Func? GetCurrentDateTimeUtcFunc; - public Action? ComputeCrc32HashAction = null; // Not currently assigned anywhere else, redundant null assignment to silence compiler warning. + return GetCurrentDateTimeUtcFunc == null + ? base.GetCurrentDateTimeUtc() + : GetCurrentDateTimeUtcFunc(); + } - public override DateTimeOffset GetCurrentDateTimeUtc() + public override void ComputeCrc32Hash(ReadOnlySpan toChecksum, Span destination) + { + if (ComputeCrc32HashAction != null) { - return GetCurrentDateTimeUtcFunc == null - ? base.GetCurrentDateTimeUtc() - : GetCurrentDateTimeUtcFunc(); + ComputeCrc32HashAction(toChecksum.ToArray(), destination.ToArray()); + return; } - public override void ComputeCrc32Hash(ReadOnlySpan toChecksum, Span destination) - { - if (ComputeCrc32HashAction != null) - { - ComputeCrc32HashAction(toChecksum.ToArray(), destination.ToArray()); - return; - } - - base.ComputeCrc32Hash(toChecksum, destination); - } + base.ComputeCrc32Hash(toChecksum, destination); } } \ No newline at end of file From 05d8cce66eb7b1e9ac4bde0df9701f23747df747 Mon Sep 17 00:00:00 2001 From: Nick Guerrera Date: Tue, 26 Nov 2024 16:51:46 -0600 Subject: [PATCH 3/3] Delete commented out file --- .../CommonAnnotatedSecurityKeys/Notes.cs | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 src/CSharp/CommonAnnotatedSecurityKeys/Notes.cs diff --git a/src/CSharp/CommonAnnotatedSecurityKeys/Notes.cs b/src/CSharp/CommonAnnotatedSecurityKeys/Notes.cs deleted file mode 100644 index 88a7d71..0000000 --- a/src/CSharp/CommonAnnotatedSecurityKeys/Notes.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -/* -namespace CommonAnnotatedSecurityKeys -{ - internal class Notes - { - public string GenerateKey(string providerSignature, string allocatorCode, string reserved = null, int randomBytesCount = 32) - { - ValidateArguments(providerSignature, allocatorCode, reserved, randomBytesCount); - - // Ensure that the randomBytesCount is a multiple of 3. We keep all data - // aligned along a 3-byte boundary to ensure consistent base64 encoding - // in the key for fixed components. - randomBytesCount = ((randomBytesCount + 2) / 3) * 3; - byte[] randomBytes = new byte[randomBytesCount]; - s_rng.GetBytes(randomBytes); - string randomComponent = Convert.ToBase64String(randomBytes).Replace('+', '-').Replace('/', '_'); - - char yearsSince2024 = (char)('A' + DateTime.UtcNow.Year - 2024); - char zeroIndexedMonth = (char)('A' + DateTime.UtcNow.Month - 1); - - string key = $"{randomComponent}{reserved}JQQJ{allocatorCode}{yearsSince2024}{zeroIndexedMonth}{providerSignature}"; - - key = key.Replace('-', '+').Replace('_', '/'); - byte[] hash = hmac.ComputeHash(Convert.FromBase64String(key)); - - return $"{key}{Convert.ToBase64String(hash, 0, 3)}"; - - } - } -} -*/ \ No newline at end of file