From b849125833810f08fc2d126f38b6c2b6c55b8978 Mon Sep 17 00:00:00 2001 From: Dallas Delaney Date: Thu, 8 Jan 2026 10:59:01 -0800 Subject: [PATCH 1/4] feat: add PKCS#1 v1.5 signing scheme support Add RSA PKCS#1 v1.5 signature algorithm support (RS256, RS384, RS512) alongside the existing PSS algorithms. This is required for dm-verity kernel signature verification which expects PKCS#1 v1.5 signatures. Changes: - Add PKCS1 key spec variants to Protocol/KeySpec.cs - Extend KeySpecExtension to map PKCS1 specs to Azure Key Vault algorithms - Update GenerateSignature to select the correct signing scheme - Add unit tests for new key specs and algorithm mappings Signed-off-by: Dallas Delaney --- .../KeyVault/KeySpecExtensionTests.cs | 30 ++++++++- .../Protocol/KeySpecTests.cs | 28 ++++++++ .../Command/GenerateSignature.cs | 24 ++++++- .../KeyVault/KeySpecExtension.cs | 44 +++++++++++- .../Protocol/KeySpec.cs | 67 ++++++++++++++++++- 5 files changed, 186 insertions(+), 7 deletions(-) diff --git a/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs b/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs index 9912ef4b..19ebeff2 100644 --- a/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs +++ b/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs @@ -35,5 +35,33 @@ public void ToSignatureAlgorithm_InvalidKeySpecs_ThrowsArgumentException(KeyType // Act & Assert Assert.Throws(() => keySpec.ToKeyVaultSignatureAlgorithm()); } + + [Theory] + [InlineData(KeyType.RSA, 2048, null, "PS256")] // Default: PSS + [InlineData(KeyType.RSA, 2048, "rsassa-pkcs1-v1_5", "RS256")] // PKCS1 routing + [InlineData(KeyType.EC, 256, "rsassa-pkcs1-v1_5", "ES256")] // EC unaffected + public void ToSignatureAlgorithm_WithScheme_ReturnsCorrectAlgorithm(KeyType keyType, int keySize, string? scheme, string expectedAlgorithm) + { + // Arrange + var keySpec = new KeySpec(keyType, keySize); + + // Act + var signatureAlgorithm = keySpec.ToKeyVaultSignatureAlgorithm(scheme); + + // Assert + Assert.Equal(expectedAlgorithm, signatureAlgorithm); + } + + [Fact] + public void ToSignatureAlgorithm_WithInvalidScheme_ThrowsArgumentException() + { + // Arrange + var keySpec = new KeySpec(KeyType.RSA, 2048); + + // Act & Assert + var ex = Assert.Throws(() => keySpec.ToKeyVaultSignatureAlgorithm("invalid-scheme")); + Assert.Contains("rsassa-pss", ex.Message); + Assert.Contains("rsassa-pkcs1-v1_5", ex.Message); + } } -} \ No newline at end of file +} diff --git a/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs index 6af88699..5b6d9706 100644 --- a/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs +++ b/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs @@ -40,5 +40,33 @@ public void KeySpec_EncodeKeySpecAndToSigningAlgorithm_ThrowsArgumentExceptionFo Assert.Throws(() => keySpec.EncodeKeySpec()); Assert.Throws(() => keySpec.ToSigningAlgorithm()); } + + [Theory] + [InlineData(KeyType.RSA, 2048, null, "RSASSA-PSS-SHA-256")] // Default: PSS + [InlineData(KeyType.RSA, 2048, "rsassa-pkcs1-v1_5", "RSASSA-PKCS1-v1_5-SHA-256")] // PKCS1 routing + [InlineData(KeyType.EC, 256, "rsassa-pkcs1-v1_5", "ECDSA-SHA-256")] // EC unaffected + public void KeySpec_ToSigningAlgorithmWithScheme_ReturnsCorrectValues(KeyType keyType, int size, string? scheme, string expectedSigningAlgorithm) + { + // Arrange + KeySpec keySpec = new KeySpec(keyType, size); + + // Act + string signingAlgorithm = keySpec.ToSigningAlgorithm(scheme); + + // Assert + Assert.Equal(expectedSigningAlgorithm, signingAlgorithm); + } + + [Fact] + public void KeySpec_ToSigningAlgorithmWithInvalidScheme_ThrowsArgumentException() + { + // Arrange + KeySpec keySpec = new KeySpec(KeyType.RSA, 2048); + + // Act & Assert + var ex = Assert.Throws(() => keySpec.ToSigningAlgorithm("invalid-scheme")); + Assert.Contains("rsassa-pss", ex.Message); + Assert.Contains("rsassa-pkcs1-v1_5", ex.Message); + } } } diff --git a/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs b/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs index bbb0a8fb..101a4f48 100644 --- a/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs +++ b/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs @@ -12,6 +12,12 @@ namespace Notation.Plugin.AzureKeyVault.Command /// public class GenerateSignature : IPluginCommand { + /// + /// Plugin config key for specifying the signing scheme. + /// Supported values: "rsassa-pss" (default), "rsassa-pkcs1-v1_5" + /// + public const string SigningSchemeConfigKey = "signing_scheme"; + private GenerateSignatureRequest _request; private IKeyVaultClient _keyVaultClient; @@ -104,13 +110,25 @@ public async Task RunAsync() // Extract KeySpec from the certificate var keySpec = leafCert.KeySpec(); - // Sign - var signature = await _keyVaultClient.SignAsync(keySpec.ToKeyVaultSignatureAlgorithm(), _request.Payload); + // Get the signing scheme from plugin config (default: rsassa-pss) + // Supported values: + // - "rsassa-pss" (default): RSASSA-PSS padding for JWS/COSE signatures + // - "rsassa-pkcs1-v1_5": RSASSA-PKCS1-v1_5 padding for PKCS#7/dm-verity signatures + string? signingScheme = _request.PluginConfig?.GetValueOrDefault(SigningSchemeConfigKey); + + // Determine the Azure Key Vault signature algorithm based on scheme + var akvAlgorithm = keySpec.ToKeyVaultSignatureAlgorithm(signingScheme); + + // Sign using the selected algorithm + var signature = await _keyVaultClient.SignAsync(akvAlgorithm, _request.Payload); + + // Determine the notation signing algorithm string based on scheme + var signingAlgorithm = keySpec.ToSigningAlgorithm(signingScheme); return new GenerateSignatureResponse( keyId: _request.KeyId, signature: signature, - signingAlgorithm: keySpec.ToSigningAlgorithm(), + signingAlgorithm: signingAlgorithm, certificateChain: certChain.Select(x => x.RawData).ToList()); } } diff --git a/Notation.Plugin.AzureKeyVault/KeyVault/KeySpecExtension.cs b/Notation.Plugin.AzureKeyVault/KeyVault/KeySpecExtension.cs index a92caa6f..4d7ba376 100644 --- a/Notation.Plugin.AzureKeyVault/KeyVault/KeySpecExtension.cs +++ b/Notation.Plugin.AzureKeyVault/KeyVault/KeySpecExtension.cs @@ -8,9 +8,9 @@ namespace Notation.Plugin.AzureKeyVault.Client /// public static class KeySpecExtension { - /// /// Get SignatureAlgorithm from KeySpec for Azure Key Vault signing. + /// Uses RSASSA-PSS (PS256/PS384/PS512) for RSA keys (default for JWS/COSE). /// public static SignatureAlgorithm ToKeyVaultSignatureAlgorithm(this KeySpec keySpec) => keySpec.Type switch { @@ -30,5 +30,45 @@ public static class KeySpecExtension }, _ => throw new ArgumentException($"Invalid KeySpec with type {keySpec.Type}") }; + + /// + /// Get SignatureAlgorithm from KeySpec for Azure Key Vault signing using RSASSA-PKCS1-v1_5. + /// Uses RS256/RS384/RS512 for RSA keys (required for PKCS#7/dm-verity). + /// For EC keys, returns the standard ECDSA algorithm (no padding change). + /// + public static SignatureAlgorithm ToKeyVaultSignatureAlgorithmPKCS1(this KeySpec keySpec) => keySpec.Type switch + { + KeyType.RSA => keySpec.Size switch + { + 2048 => SignatureAlgorithm.RS256, + 3072 => SignatureAlgorithm.RS384, + 4096 => SignatureAlgorithm.RS512, + _ => throw new ArgumentException($"Invalid KeySpec for RSA with size {keySpec.Size}") + }, + KeyType.EC => keySpec.Size switch + { + // ECDSA doesn't have padding variants - use the same algorithm + 256 => SignatureAlgorithm.ES256, + 384 => SignatureAlgorithm.ES384, + 521 => SignatureAlgorithm.ES512, + _ => throw new ArgumentException($"Invalid KeySpec for EC with size {keySpec.Size}") + }, + _ => throw new ArgumentException($"Invalid KeySpec with type {keySpec.Type}") + }; + + /// + /// Get SignatureAlgorithm from KeySpec for Azure Key Vault signing based on the specified signing scheme. + /// + /// The key specification + /// The signing scheme (rsassa-pss or rsassa-pkcs1-v1_5). Default is rsassa-pss. + /// The Azure Key Vault SignatureAlgorithm + public static SignatureAlgorithm ToKeyVaultSignatureAlgorithm(this KeySpec keySpec, string? scheme) => scheme?.ToLowerInvariant() switch + { + SigningScheme.RSASSA_PKCS1_V1_5 => keySpec.ToKeyVaultSignatureAlgorithmPKCS1(), + SigningScheme.RSASSA_PSS => keySpec.ToKeyVaultSignatureAlgorithm(), + null => keySpec.ToKeyVaultSignatureAlgorithm(), + "" => keySpec.ToKeyVaultSignatureAlgorithm(), + _ => throw new ArgumentException($"Invalid signing scheme: {scheme}. Supported values are '{SigningScheme.RSASSA_PSS}' and '{SigningScheme.RSASSA_PKCS1_V1_5}'") + }; } -} \ No newline at end of file +} diff --git a/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs b/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs index a1a5f79c..5142910d 100644 --- a/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs +++ b/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs @@ -24,14 +24,40 @@ public static class KeySpecConstants /// public static class SigningAlgorithms { + // RSASSA-PSS (default for JWS/COSE) public const string RSASSA_PSS_SHA_256 = "RSASSA-PSS-SHA-256"; public const string RSASSA_PSS_SHA_384 = "RSASSA-PSS-SHA-384"; public const string RSASSA_PSS_SHA_512 = "RSASSA-PSS-SHA-512"; + + // RSASSA-PKCS1-v1_5 (required for PKCS#7/dm-verity) + public const string RSASSA_PKCS1_V1_5_SHA_256 = "RSASSA-PKCS1-v1_5-SHA-256"; + public const string RSASSA_PKCS1_V1_5_SHA_384 = "RSASSA-PKCS1-v1_5-SHA-384"; + public const string RSASSA_PKCS1_V1_5_SHA_512 = "RSASSA-PKCS1-v1_5-SHA-512"; + + // ECDSA public const string ECDSA_SHA_256 = "ECDSA-SHA-256"; public const string ECDSA_SHA_384 = "ECDSA-SHA-384"; public const string ECDSA_SHA_512 = "ECDSA-SHA-512"; } + /// + /// Defines the supported signing schemes (padding modes for RSA). + /// + public static class SigningScheme + { + /// + /// RSASSA-PSS padding (default for JWS/COSE notation signatures). + /// + public const string RSASSA_PSS = "rsassa-pss"; + + /// + /// RSASSA-PKCS1-v1_5 padding (required for PKCS#7/dm-verity signatures). + /// This scheme is needed for Linux kernel dm-verity verification which + /// only supports PKCS#1 v1.5 signatures. + /// + public const string RSASSA_PKCS1_V1_5 = "rsassa-pkcs1-v1_5"; + } + /// /// KeyType class. /// @@ -87,7 +113,7 @@ public KeySpec(KeyType type, int size) }; /// - /// Convert KeySpec to be SigningAlgorithm string. + /// Convert KeySpec to be SigningAlgorithm string (RSASSA-PSS for RSA, default). /// Supported key types are RSA with key size 2048, 3072, 4096 /// and ECDSA with key size 256, 384, 521. /// @@ -109,5 +135,44 @@ public KeySpec(KeyType type, int size) }, _ => throw new ArgumentException($"Invalid KeySpec Type: {Type}") }; + + /// + /// Convert KeySpec to be SigningAlgorithm string using RSASSA-PKCS1-v1_5. + /// This is required for PKCS#7 signatures used in dm-verity. + /// For EC keys, this returns the standard ECDSA algorithm (no padding change). + /// + public string ToSigningAlgorithmPKCS1() => Type switch + { + KeyType.RSA => Size switch + { + 2048 => SigningAlgorithms.RSASSA_PKCS1_V1_5_SHA_256, + 3072 => SigningAlgorithms.RSASSA_PKCS1_V1_5_SHA_384, + 4096 => SigningAlgorithms.RSASSA_PKCS1_V1_5_SHA_512, + _ => throw new ArgumentException($"Invalid RSA KeySpec size {Size}") + }, + KeyType.EC => Size switch + { + // ECDSA doesn't have padding variants - use the same algorithm + 256 => SigningAlgorithms.ECDSA_SHA_256, + 384 => SigningAlgorithms.ECDSA_SHA_384, + 521 => SigningAlgorithms.ECDSA_SHA_512, + _ => throw new ArgumentException($"Invalid EC KeySpec size {Size}") + }, + _ => throw new ArgumentException($"Invalid KeySpec Type: {Type}") + }; + + /// + /// Convert KeySpec to SigningAlgorithm string based on the specified signing scheme. + /// + /// The signing scheme to use (rsassa-pss or rsassa-pkcs1-v1_5) + /// The appropriate signing algorithm string + public string ToSigningAlgorithm(string? scheme) => scheme?.ToLowerInvariant() switch + { + SigningScheme.RSASSA_PKCS1_V1_5 => ToSigningAlgorithmPKCS1(), + SigningScheme.RSASSA_PSS => ToSigningAlgorithm(), + null => ToSigningAlgorithm(), + "" => ToSigningAlgorithm(), + _ => throw new ArgumentException($"Invalid signing scheme: {scheme}. Supported values are '{SigningScheme.RSASSA_PSS}' and '{SigningScheme.RSASSA_PKCS1_V1_5}'") + }; } } From d3821351a10e7800f06e0749e696d107164fa489 Mon Sep 17 00:00:00 2001 From: Dallas Delaney Date: Mon, 16 Mar 2026 16:20:09 -0700 Subject: [PATCH 2/4] test: expand coverage for PKCS1 signing paths Improve CI patch coverage for tests to get over 80% Add tests for all RSA key sizes (2048/3072/4096) and EC key sizes (256/384/521) through the PKCS1 code paths. Add tests for empty string and rsassa-pss scheme routing. Add direct tests for ToKeyVaultSignatureAlgorithmPKCS1 and ToSigningAlgorithmPKCS1. Fix trailing whitespace in KeySpec.cs. Signed-off-by: Dallas Delaney --- .../KeyVault/KeySpecExtensionTests.cs | 39 ++++++++++++++++- .../Protocol/KeySpecTests.cs | 43 +++++++++++++++++-- .../Protocol/KeySpec.cs | 6 +-- 3 files changed, 81 insertions(+), 7 deletions(-) diff --git a/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs b/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs index 19ebeff2..a2957430 100644 --- a/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs +++ b/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs @@ -36,15 +36,52 @@ public void ToSignatureAlgorithm_InvalidKeySpecs_ThrowsArgumentException(KeyType Assert.Throws(() => keySpec.ToKeyVaultSignatureAlgorithm()); } + [Theory] + [InlineData(KeyType.RSA, 2048, "RS256")] + [InlineData(KeyType.RSA, 3072, "RS384")] + [InlineData(KeyType.RSA, 4096, "RS512")] + [InlineData(KeyType.EC, 256, "ES256")] + [InlineData(KeyType.EC, 384, "ES384")] + [InlineData(KeyType.EC, 521, "ES512")] + public void ToKeyVaultSignatureAlgorithmPKCS1_ValidKeySpecs_ReturnsCorrectAlgorithm(KeyType keyType, int keySize, string expectedAlgorithm) + { + // Arrange + var keySpec = new KeySpec(keyType, keySize); + + // Act + var signatureAlgorithm = keySpec.ToKeyVaultSignatureAlgorithmPKCS1(); + + // Assert + Assert.Equal(expectedAlgorithm, signatureAlgorithm); + } + + [Theory] + [InlineData(KeyType.RSA, 1024)] + [InlineData(KeyType.EC, 128)] + public void ToKeyVaultSignatureAlgorithmPKCS1_InvalidKeySpecs_ThrowsArgumentException(KeyType keyType, int keySize) + { + // Arrange + var keySpec = new KeySpec(keyType, keySize); + + // Act & Assert + Assert.Throws(() => keySpec.ToKeyVaultSignatureAlgorithmPKCS1()); + } + [Theory] [InlineData(KeyType.RSA, 2048, null, "PS256")] // Default: PSS + [InlineData(KeyType.RSA, 2048, "", "PS256")] // Empty: PSS + [InlineData(KeyType.RSA, 2048, "rsassa-pss", "PS256")] // Explicit PSS [InlineData(KeyType.RSA, 2048, "rsassa-pkcs1-v1_5", "RS256")] // PKCS1 routing + [InlineData(KeyType.RSA, 3072, "rsassa-pkcs1-v1_5", "RS384")] // PKCS1 3072 + [InlineData(KeyType.RSA, 4096, "rsassa-pkcs1-v1_5", "RS512")] // PKCS1 4096 [InlineData(KeyType.EC, 256, "rsassa-pkcs1-v1_5", "ES256")] // EC unaffected + [InlineData(KeyType.EC, 384, "rsassa-pkcs1-v1_5", "ES384")] // EC 384 + [InlineData(KeyType.EC, 521, "rsassa-pkcs1-v1_5", "ES512")] // EC 521 public void ToSignatureAlgorithm_WithScheme_ReturnsCorrectAlgorithm(KeyType keyType, int keySize, string? scheme, string expectedAlgorithm) { // Arrange var keySpec = new KeySpec(keyType, keySize); - + // Act var signatureAlgorithm = keySpec.ToKeyVaultSignatureAlgorithm(scheme); diff --git a/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs index 5b6d9706..90c50dfd 100644 --- a/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs +++ b/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs @@ -42,9 +42,46 @@ public void KeySpec_EncodeKeySpecAndToSigningAlgorithm_ThrowsArgumentExceptionFo } [Theory] - [InlineData(KeyType.RSA, 2048, null, "RSASSA-PSS-SHA-256")] // Default: PSS - [InlineData(KeyType.RSA, 2048, "rsassa-pkcs1-v1_5", "RSASSA-PKCS1-v1_5-SHA-256")] // PKCS1 routing - [InlineData(KeyType.EC, 256, "rsassa-pkcs1-v1_5", "ECDSA-SHA-256")] // EC unaffected + [InlineData(KeyType.RSA, 2048, "RSASSA-PKCS1-v1_5-SHA-256")] + [InlineData(KeyType.RSA, 3072, "RSASSA-PKCS1-v1_5-SHA-384")] + [InlineData(KeyType.RSA, 4096, "RSASSA-PKCS1-v1_5-SHA-512")] + [InlineData(KeyType.EC, 256, "ECDSA-SHA-256")] + [InlineData(KeyType.EC, 384, "ECDSA-SHA-384")] + [InlineData(KeyType.EC, 521, "ECDSA-SHA-512")] + public void KeySpec_ToSigningAlgorithmPKCS1_ReturnsCorrectValues(KeyType keyType, int size, string expectedSigningAlgorithm) + { + // Arrange + KeySpec keySpec = new KeySpec(keyType, size); + + // Act + string signingAlgorithm = keySpec.ToSigningAlgorithmPKCS1(); + + // Assert + Assert.Equal(expectedSigningAlgorithm, signingAlgorithm); + } + + [Theory] + [InlineData(KeyType.RSA, 1024)] + [InlineData(KeyType.EC, 128)] + public void KeySpec_ToSigningAlgorithmPKCS1_ThrowsArgumentExceptionForInvalidSizes(KeyType keyType, int size) + { + // Arrange + KeySpec keySpec = new KeySpec(keyType, size); + + // Act & Assert + Assert.Throws(() => keySpec.ToSigningAlgorithmPKCS1()); + } + + [Theory] + [InlineData(KeyType.RSA, 2048, null, "RSASSA-PSS-SHA-256")] // Default: PSS + [InlineData(KeyType.RSA, 2048, "", "RSASSA-PSS-SHA-256")] // Empty: PSS + [InlineData(KeyType.RSA, 2048, "rsassa-pss", "RSASSA-PSS-SHA-256")] // Explicit PSS + [InlineData(KeyType.RSA, 2048, "rsassa-pkcs1-v1_5", "RSASSA-PKCS1-v1_5-SHA-256")] // PKCS1 routing + [InlineData(KeyType.RSA, 3072, "rsassa-pkcs1-v1_5", "RSASSA-PKCS1-v1_5-SHA-384")] // PKCS1 3072 + [InlineData(KeyType.RSA, 4096, "rsassa-pkcs1-v1_5", "RSASSA-PKCS1-v1_5-SHA-512")] // PKCS1 4096 + [InlineData(KeyType.EC, 256, "rsassa-pkcs1-v1_5", "ECDSA-SHA-256")] // EC unaffected + [InlineData(KeyType.EC, 384, "rsassa-pkcs1-v1_5", "ECDSA-SHA-384")] // EC 384 + [InlineData(KeyType.EC, 521, "rsassa-pkcs1-v1_5", "ECDSA-SHA-512")] // EC 521 public void KeySpec_ToSigningAlgorithmWithScheme_ReturnsCorrectValues(KeyType keyType, int size, string? scheme, string expectedSigningAlgorithm) { // Arrange diff --git a/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs b/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs index 5142910d..40531894 100644 --- a/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs +++ b/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs @@ -28,12 +28,12 @@ public static class SigningAlgorithms public const string RSASSA_PSS_SHA_256 = "RSASSA-PSS-SHA-256"; public const string RSASSA_PSS_SHA_384 = "RSASSA-PSS-SHA-384"; public const string RSASSA_PSS_SHA_512 = "RSASSA-PSS-SHA-512"; - + // RSASSA-PKCS1-v1_5 (required for PKCS#7/dm-verity) public const string RSASSA_PKCS1_V1_5_SHA_256 = "RSASSA-PKCS1-v1_5-SHA-256"; public const string RSASSA_PKCS1_V1_5_SHA_384 = "RSASSA-PKCS1-v1_5-SHA-384"; public const string RSASSA_PKCS1_V1_5_SHA_512 = "RSASSA-PKCS1-v1_5-SHA-512"; - + // ECDSA public const string ECDSA_SHA_256 = "ECDSA-SHA-256"; public const string ECDSA_SHA_384 = "ECDSA-SHA-384"; @@ -49,7 +49,7 @@ public static class SigningScheme /// RSASSA-PSS padding (default for JWS/COSE notation signatures). /// public const string RSASSA_PSS = "rsassa-pss"; - + /// /// RSASSA-PKCS1-v1_5 padding (required for PKCS#7/dm-verity signatures). /// This scheme is needed for Linux kernel dm-verity verification which From c5101296b74eb033618ae6fee456877578bbfe33 Mon Sep 17 00:00:00 2001 From: Dallas Delaney Date: Tue, 19 May 2026 11:45:35 -0700 Subject: [PATCH 3/4] fix: address review feedback on PKCS#1 v1.5 signing scheme - expand SigningSchemeConfigKey XML doc to clarify supported values - reject rsassa-pkcs1-v1_5 + EC key in RunAsync (new theory test) - mark PKCS1 helpers internal to hide EC fallback - collapse null / "" switch arms to 'null or ""' - rename ToSignatureAlgorithm_WithScheme_* tests to ToKeyVaultSignatureAlgorithm_WithScheme_* - assert offending scheme value in ArgumentException Signed-off-by: Dallas Delaney --- .../Command/GenerateSignatureTests.cs | 36 +++++++++++++++++++ .../KeyVault/KeySpecExtensionTests.cs | 5 +-- .../Protocol/KeySpecTests.cs | 1 + .../Command/GenerateSignature.cs | 14 ++++++-- .../KeyVault/KeySpecExtension.cs | 12 ++++--- .../Protocol/KeySpec.cs | 9 +++-- 6 files changed, 64 insertions(+), 13 deletions(-) diff --git a/Notation.Plugin.AzureKeyVault.Tests/Command/GenerateSignatureTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Command/GenerateSignatureTests.cs index 8cae7bbd..43bac6d0 100644 --- a/Notation.Plugin.AzureKeyVault.Tests/Command/GenerateSignatureTests.cs +++ b/Notation.Plugin.AzureKeyVault.Tests/Command/GenerateSignatureTests.cs @@ -248,5 +248,41 @@ public async Task RunAsync_SelfSignedWithCaCerts() await Assert.ThrowsAsync(async () => await generateSignatureCommand.RunAsync()); } + + [Theory] + [InlineData("ec_256.crt")] + [InlineData("ec_384.crt")] + [InlineData("ec_521.crt")] + public async Task RunAsync_PKCS1SchemeWithECKey_ThrowsValidationException(string ecCertFile) + { + // Arrange + var keyId = "https://testvault.vault.azure.net/keys/testkey/123"; + var ecCert = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "TestData", ecCertFile)); + var mockKeyVaultClient = new Mock(); + mockKeyVaultClient.Setup(client => client.GetCertificateAsync()) + .ReturnsAsync(ecCert); + + var request = new GenerateSignatureRequest( + contractVersion: "1.0", + keyId: keyId, + pluginConfig: new Dictionary() + { + ["self_signed"] = "true", + [GenerateSignature.SigningSchemeConfigKey] = SigningScheme.RSASSA_PKCS1_V1_5 + }, + keySpec: "EC-256", + hashAlgorithm: "SHA-256", + payload: Encoding.UTF8.GetBytes("Cg==")); + + var generateSignatureCommand = new GenerateSignature(request, mockKeyVaultClient.Object); + + // Act & Assert: must reject before any AKV SignAsync call. + var ex = await Assert.ThrowsAsync( + async () => await generateSignatureCommand.RunAsync()); + Assert.Contains(SigningScheme.RSASSA_PKCS1_V1_5, ex.Message); + mockKeyVaultClient.Verify( + c => c.SignAsync(It.IsAny(), It.IsAny()), + Times.Never); + } } } diff --git a/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs b/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs index a2957430..4be3f4a6 100644 --- a/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs +++ b/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs @@ -77,7 +77,7 @@ public void ToKeyVaultSignatureAlgorithmPKCS1_InvalidKeySpecs_ThrowsArgumentExce [InlineData(KeyType.EC, 256, "rsassa-pkcs1-v1_5", "ES256")] // EC unaffected [InlineData(KeyType.EC, 384, "rsassa-pkcs1-v1_5", "ES384")] // EC 384 [InlineData(KeyType.EC, 521, "rsassa-pkcs1-v1_5", "ES512")] // EC 521 - public void ToSignatureAlgorithm_WithScheme_ReturnsCorrectAlgorithm(KeyType keyType, int keySize, string? scheme, string expectedAlgorithm) + public void ToKeyVaultSignatureAlgorithm_WithScheme_ReturnsCorrectAlgorithm(KeyType keyType, int keySize, string? scheme, string expectedAlgorithm) { // Arrange var keySpec = new KeySpec(keyType, keySize); @@ -90,13 +90,14 @@ public void ToSignatureAlgorithm_WithScheme_ReturnsCorrectAlgorithm(KeyType keyT } [Fact] - public void ToSignatureAlgorithm_WithInvalidScheme_ThrowsArgumentException() + public void ToKeyVaultSignatureAlgorithm_WithInvalidScheme_ThrowsArgumentException() { // Arrange var keySpec = new KeySpec(KeyType.RSA, 2048); // Act & Assert var ex = Assert.Throws(() => keySpec.ToKeyVaultSignatureAlgorithm("invalid-scheme")); + Assert.Contains("invalid-scheme", ex.Message); Assert.Contains("rsassa-pss", ex.Message); Assert.Contains("rsassa-pkcs1-v1_5", ex.Message); } diff --git a/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs index 90c50dfd..4b5b6cc1 100644 --- a/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs +++ b/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs @@ -102,6 +102,7 @@ public void KeySpec_ToSigningAlgorithmWithInvalidScheme_ThrowsArgumentException( // Act & Assert var ex = Assert.Throws(() => keySpec.ToSigningAlgorithm("invalid-scheme")); + Assert.Contains("invalid-scheme", ex.Message); Assert.Contains("rsassa-pss", ex.Message); Assert.Contains("rsassa-pkcs1-v1_5", ex.Message); } diff --git a/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs b/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs index 101a4f48..d18ac187 100644 --- a/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs +++ b/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs @@ -13,8 +13,9 @@ namespace Notation.Plugin.AzureKeyVault.Command public class GenerateSignature : IPluginCommand { /// - /// Plugin config key for specifying the signing scheme. - /// Supported values: "rsassa-pss" (default), "rsassa-pkcs1-v1_5" + /// Plugin config key for specifying the RSA signing scheme. + /// Supported values: "rsassa-pss" (default, for JWS/COSE) and + /// "rsassa-pkcs1-v1_5" (for PKCS#7/dm-verity). /// public const string SigningSchemeConfigKey = "signing_scheme"; @@ -116,6 +117,15 @@ public async Task RunAsync() // - "rsassa-pkcs1-v1_5": RSASSA-PKCS1-v1_5 padding for PKCS#7/dm-verity signatures string? signingScheme = _request.PluginConfig?.GetValueOrDefault(SigningSchemeConfigKey); + // For RSASSA-PKCS1-v1_5 + EC, fail fast: PKCS#7-based verifiers + // cannot consume ECDSA signatures. + if (string.Equals(signingScheme, SigningScheme.RSASSA_PKCS1_V1_5, StringComparison.OrdinalIgnoreCase) + && keySpec.Type == KeyType.EC) + { + throw new ValidationException( + $"Signing scheme '{SigningScheme.RSASSA_PKCS1_V1_5}' requires an RSA key; got EC."); + } + // Determine the Azure Key Vault signature algorithm based on scheme var akvAlgorithm = keySpec.ToKeyVaultSignatureAlgorithm(signingScheme); diff --git a/Notation.Plugin.AzureKeyVault/KeyVault/KeySpecExtension.cs b/Notation.Plugin.AzureKeyVault/KeyVault/KeySpecExtension.cs index 4d7ba376..e76b65eb 100644 --- a/Notation.Plugin.AzureKeyVault/KeyVault/KeySpecExtension.cs +++ b/Notation.Plugin.AzureKeyVault/KeyVault/KeySpecExtension.cs @@ -34,9 +34,14 @@ public static class KeySpecExtension /// /// Get SignatureAlgorithm from KeySpec for Azure Key Vault signing using RSASSA-PKCS1-v1_5. /// Uses RS256/RS384/RS512 for RSA keys (required for PKCS#7/dm-verity). - /// For EC keys, returns the standard ECDSA algorithm (no padding change). + /// For EC keys, returns the standard ECDSA algorithm (no padding change). The + /// scheme-aware + /// is the only call site; this helper is internal so callers cannot rely on the + /// EC fallback as a public contract. The fail-fast in + /// GenerateSignature.RunAsync rejects EC keys when the + /// rsassa-pkcs1-v1_5 scheme is requested. /// - public static SignatureAlgorithm ToKeyVaultSignatureAlgorithmPKCS1(this KeySpec keySpec) => keySpec.Type switch + internal static SignatureAlgorithm ToKeyVaultSignatureAlgorithmPKCS1(this KeySpec keySpec) => keySpec.Type switch { KeyType.RSA => keySpec.Size switch { @@ -66,8 +71,7 @@ public static class KeySpecExtension { SigningScheme.RSASSA_PKCS1_V1_5 => keySpec.ToKeyVaultSignatureAlgorithmPKCS1(), SigningScheme.RSASSA_PSS => keySpec.ToKeyVaultSignatureAlgorithm(), - null => keySpec.ToKeyVaultSignatureAlgorithm(), - "" => keySpec.ToKeyVaultSignatureAlgorithm(), + null or "" => keySpec.ToKeyVaultSignatureAlgorithm(), _ => throw new ArgumentException($"Invalid signing scheme: {scheme}. Supported values are '{SigningScheme.RSASSA_PSS}' and '{SigningScheme.RSASSA_PKCS1_V1_5}'") }; } diff --git a/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs b/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs index 40531894..26868249 100644 --- a/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs +++ b/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs @@ -138,10 +138,10 @@ public KeySpec(KeyType type, int size) /// /// Convert KeySpec to be SigningAlgorithm string using RSASSA-PKCS1-v1_5. - /// This is required for PKCS#7 signatures used in dm-verity. - /// For EC keys, this returns the standard ECDSA algorithm (no padding change). + /// This is required for PKCS#7 signatures. For EC keys, this returns the + /// standard ECDSA algorithm (no padding change). /// - public string ToSigningAlgorithmPKCS1() => Type switch + internal string ToSigningAlgorithmPKCS1() => Type switch { KeyType.RSA => Size switch { @@ -170,8 +170,7 @@ public KeySpec(KeyType type, int size) { SigningScheme.RSASSA_PKCS1_V1_5 => ToSigningAlgorithmPKCS1(), SigningScheme.RSASSA_PSS => ToSigningAlgorithm(), - null => ToSigningAlgorithm(), - "" => ToSigningAlgorithm(), + null or "" => ToSigningAlgorithm(), _ => throw new ArgumentException($"Invalid signing scheme: {scheme}. Supported values are '{SigningScheme.RSASSA_PSS}' and '{SigningScheme.RSASSA_PKCS1_V1_5}'") }; } From 7a1de8f6cb0aebdee1351dc2c238e799e0c6b1ea Mon Sep 17 00:00:00 2001 From: Dallas Delaney Date: Tue, 19 May 2026 12:07:37 -0700 Subject: [PATCH 4/4] fix: minor PKCS#1 v1.5 follow-ups - hoist signing_scheme validation upstream in RunAsync so the failure reports the plugin's VALIDATION_ERROR code - add RSA happy-path and EC-rejection PKCS1 RunAsync tests - add uppercase-scheme row for case-insensitivity - split mixed RSA+EC PKCS1 helper tests - rename EC variant to ReturnsECDSAForSymmetry - mirror PKCS1 helper docs across KeySpec and KeySpecExtension - note scheme-router switch must stay in lock-step - rename SigningScheme to SigningSchemes Signed-off-by: Dallas Delaney --- .../Command/GenerateSignatureTests.cs | 100 ++++++++++++++++-- .../KeyVault/KeySpecExtensionTests.cs | 37 +++++-- .../Protocol/KeySpecTests.cs | 33 ++++-- .../Command/GenerateSignature.cs | 26 +++-- .../KeyVault/KeySpecExtension.cs | 19 ++-- .../Protocol/KeySpec.cs | 16 +-- 6 files changed, 179 insertions(+), 52 deletions(-) diff --git a/Notation.Plugin.AzureKeyVault.Tests/Command/GenerateSignatureTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Command/GenerateSignatureTests.cs index 43bac6d0..5bd07f6c 100644 --- a/Notation.Plugin.AzureKeyVault.Tests/Command/GenerateSignatureTests.cs +++ b/Notation.Plugin.AzureKeyVault.Tests/Command/GenerateSignatureTests.cs @@ -250,10 +250,49 @@ public async Task RunAsync_SelfSignedWithCaCerts() } [Theory] - [InlineData("ec_256.crt")] - [InlineData("ec_384.crt")] - [InlineData("ec_521.crt")] - public async Task RunAsync_PKCS1SchemeWithECKey_ThrowsValidationException(string ecCertFile) + [InlineData("invalid-scheme")] + public async Task RunAsync_InvalidSigningScheme_ThrowsValidationException(string scheme) + { + // Arrange + var keyId = "https://testvault.vault.azure.net/keys/testkey/123"; + var rsaCert = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "rsa_2048.crt")); + var mockKeyVaultClient = new Mock(); + mockKeyVaultClient.Setup(client => client.GetCertificateAsync()) + .ReturnsAsync(rsaCert); + + var request = new GenerateSignatureRequest( + contractVersion: "1.0", + keyId: keyId, + pluginConfig: new Dictionary() + { + ["self_signed"] = "true", + [GenerateSignature.SigningSchemeConfigKey] = scheme + }, + keySpec: "RSA-2048", + hashAlgorithm: "SHA-256", + payload: Encoding.UTF8.GetBytes("Cg==")); + + var generateSignatureCommand = new GenerateSignature(request, mockKeyVaultClient.Object); + + // Act & Assert: invalid scheme must surface as ValidationException + // (and not as ArgumentException from the helpers), and must reject + // before any AKV SignAsync call. + var ex = await Assert.ThrowsAsync( + async () => await generateSignatureCommand.RunAsync()); + Assert.Contains(scheme, ex.Message); + Assert.Contains(SigningSchemes.RSASSA_PSS, ex.Message); + Assert.Contains(SigningSchemes.RSASSA_PKCS1_V1_5, ex.Message); + mockKeyVaultClient.Verify( + c => c.SignAsync(It.IsAny(), It.IsAny()), + Times.Never); + } + + [Theory] + [InlineData("ec_256.crt", "rsassa-pkcs1-v1_5")] + [InlineData("ec_384.crt", "rsassa-pkcs1-v1_5")] + [InlineData("ec_521.crt", "rsassa-pkcs1-v1_5")] + [InlineData("ec_256.crt", "RSASSA-PKCS1-V1_5")] + public async Task RunAsync_PKCS1SchemeWithECKey_ThrowsValidationException(string ecCertFile, string scheme) { // Arrange var keyId = "https://testvault.vault.azure.net/keys/testkey/123"; @@ -268,7 +307,7 @@ public async Task RunAsync_PKCS1SchemeWithECKey_ThrowsValidationException(string pluginConfig: new Dictionary() { ["self_signed"] = "true", - [GenerateSignature.SigningSchemeConfigKey] = SigningScheme.RSASSA_PKCS1_V1_5 + [GenerateSignature.SigningSchemeConfigKey] = scheme }, keySpec: "EC-256", hashAlgorithm: "SHA-256", @@ -279,10 +318,59 @@ public async Task RunAsync_PKCS1SchemeWithECKey_ThrowsValidationException(string // Act & Assert: must reject before any AKV SignAsync call. var ex = await Assert.ThrowsAsync( async () => await generateSignatureCommand.RunAsync()); - Assert.Contains(SigningScheme.RSASSA_PKCS1_V1_5, ex.Message); + Assert.Contains(SigningSchemes.RSASSA_PKCS1_V1_5, ex.Message); mockKeyVaultClient.Verify( c => c.SignAsync(It.IsAny(), It.IsAny()), Times.Never); } + + [Theory] + [InlineData("rsa_2048.crt", "RS256", "RSASSA-PKCS1-v1_5-SHA-256")] + [InlineData("rsa_3072.crt", "RS384", "RSASSA-PKCS1-v1_5-SHA-384")] + [InlineData("rsa_4096.crt", "RS512", "RSASSA-PKCS1-v1_5-SHA-512")] + public async Task RunAsync_PKCS1SchemeWithRSAKey_ReturnsPKCS1Response( + string rsaCertFile, + string expectedAkvAlgorithm, + string expectedSigningAlgorithm) + { + // Arrange + var keyId = "https://testvault.vault.azure.net/keys/testkey/123"; + var mockSignature = new byte[] { 0x01, 0x02, 0x03, 0x04 }; + var rsaCert = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "TestData", rsaCertFile)); + var mockKeyVaultClient = new Mock(); + mockKeyVaultClient.Setup(client => client.GetCertificateAsync()) + .ReturnsAsync(rsaCert); + + SignatureAlgorithm? capturedAlgorithm = null; + mockKeyVaultClient.Setup(client => client.SignAsync(It.IsAny(), It.IsAny())) + .Callback((algo, _) => capturedAlgorithm = algo) + .ReturnsAsync(mockSignature); + + var request = new GenerateSignatureRequest( + contractVersion: "1.0", + keyId: keyId, + pluginConfig: new Dictionary() + { + ["self_signed"] = "true", + [GenerateSignature.SigningSchemeConfigKey] = SigningSchemes.RSASSA_PKCS1_V1_5 + }, + keySpec: "RSA-2048", + hashAlgorithm: "SHA-256", + payload: Encoding.UTF8.GetBytes("Cg==")); + + var generateSignatureCommand = new GenerateSignature(request, mockKeyVaultClient.Object); + + // Act + var result = await generateSignatureCommand.RunAsync(); + + // Assert: end-to-end wiring routes RSASSA-PKCS1-v1_5 through the + // PKCS1 helpers (AKV call uses RS*, response uses RSASSA-PKCS1-v1_5-SHA-*). + var response = Assert.IsType(result); + Assert.Equal(keyId, response.KeyId); + Assert.Equal(expectedSigningAlgorithm, response.SigningAlgorithm); + Assert.Equal(mockSignature, response.Signature); + Assert.NotNull(capturedAlgorithm); + Assert.Equal(new SignatureAlgorithm(expectedAkvAlgorithm), capturedAlgorithm.Value); + } } } diff --git a/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs b/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs index 4be3f4a6..3b9d6e68 100644 --- a/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs +++ b/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs @@ -40,10 +40,23 @@ public void ToSignatureAlgorithm_InvalidKeySpecs_ThrowsArgumentException(KeyType [InlineData(KeyType.RSA, 2048, "RS256")] [InlineData(KeyType.RSA, 3072, "RS384")] [InlineData(KeyType.RSA, 4096, "RS512")] + public void ToKeyVaultSignatureAlgorithmPKCS1_ValidKeySpecs_ReturnsCorrectAlgorithm(KeyType keyType, int keySize, string expectedAlgorithm) + { + // Arrange + var keySpec = new KeySpec(keyType, keySize); + + // Act + var signatureAlgorithm = keySpec.ToKeyVaultSignatureAlgorithmPKCS1(); + + // Assert + Assert.Equal(expectedAlgorithm, signatureAlgorithm); + } + + [Theory] [InlineData(KeyType.EC, 256, "ES256")] [InlineData(KeyType.EC, 384, "ES384")] [InlineData(KeyType.EC, 521, "ES512")] - public void ToKeyVaultSignatureAlgorithmPKCS1_ValidKeySpecs_ReturnsCorrectAlgorithm(KeyType keyType, int keySize, string expectedAlgorithm) + public void ToKeyVaultSignatureAlgorithmPKCS1_ECKey_ReturnsECDSAForSymmetry(KeyType keyType, int keySize, string expectedAlgorithm) { // Arrange var keySpec = new KeySpec(keyType, keySize); @@ -71,12 +84,13 @@ public void ToKeyVaultSignatureAlgorithmPKCS1_InvalidKeySpecs_ThrowsArgumentExce [InlineData(KeyType.RSA, 2048, null, "PS256")] // Default: PSS [InlineData(KeyType.RSA, 2048, "", "PS256")] // Empty: PSS [InlineData(KeyType.RSA, 2048, "rsassa-pss", "PS256")] // Explicit PSS - [InlineData(KeyType.RSA, 2048, "rsassa-pkcs1-v1_5", "RS256")] // PKCS1 routing - [InlineData(KeyType.RSA, 3072, "rsassa-pkcs1-v1_5", "RS384")] // PKCS1 3072 - [InlineData(KeyType.RSA, 4096, "rsassa-pkcs1-v1_5", "RS512")] // PKCS1 4096 - [InlineData(KeyType.EC, 256, "rsassa-pkcs1-v1_5", "ES256")] // EC unaffected - [InlineData(KeyType.EC, 384, "rsassa-pkcs1-v1_5", "ES384")] // EC 384 - [InlineData(KeyType.EC, 521, "rsassa-pkcs1-v1_5", "ES512")] // EC 521 + [InlineData(KeyType.RSA, 2048, "rsassa-pkcs1-v1_5", "RS256")] // PKCS1 routing + [InlineData(KeyType.RSA, 2048, "RSASSA-PKCS1-V1_5", "RS256")] // PKCS1 case-insensitive + [InlineData(KeyType.RSA, 3072, "rsassa-pkcs1-v1_5", "RS384")] // PKCS1 3072 + [InlineData(KeyType.RSA, 4096, "rsassa-pkcs1-v1_5", "RS512")] // PKCS1 4096 + [InlineData(KeyType.EC, 256, "rsassa-pkcs1-v1_5", "ES256")] // EC unaffected + [InlineData(KeyType.EC, 384, "rsassa-pkcs1-v1_5", "ES384")] // EC 384 + [InlineData(KeyType.EC, 521, "rsassa-pkcs1-v1_5", "ES512")] // EC 521 public void ToKeyVaultSignatureAlgorithm_WithScheme_ReturnsCorrectAlgorithm(KeyType keyType, int keySize, string? scheme, string expectedAlgorithm) { // Arrange @@ -89,15 +103,16 @@ public void ToKeyVaultSignatureAlgorithm_WithScheme_ReturnsCorrectAlgorithm(KeyT Assert.Equal(expectedAlgorithm, signatureAlgorithm); } - [Fact] - public void ToKeyVaultSignatureAlgorithm_WithInvalidScheme_ThrowsArgumentException() + [Theory] + [InlineData("invalid-scheme")] + public void ToKeyVaultSignatureAlgorithm_WithInvalidScheme_ThrowsArgumentException(string scheme) { // Arrange var keySpec = new KeySpec(KeyType.RSA, 2048); // Act & Assert - var ex = Assert.Throws(() => keySpec.ToKeyVaultSignatureAlgorithm("invalid-scheme")); - Assert.Contains("invalid-scheme", ex.Message); + var ex = Assert.Throws(() => keySpec.ToKeyVaultSignatureAlgorithm(scheme)); + Assert.Contains(scheme, ex.Message); Assert.Contains("rsassa-pss", ex.Message); Assert.Contains("rsassa-pkcs1-v1_5", ex.Message); } diff --git a/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs index 4b5b6cc1..30568a90 100644 --- a/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs +++ b/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs @@ -45,10 +45,23 @@ public void KeySpec_EncodeKeySpecAndToSigningAlgorithm_ThrowsArgumentExceptionFo [InlineData(KeyType.RSA, 2048, "RSASSA-PKCS1-v1_5-SHA-256")] [InlineData(KeyType.RSA, 3072, "RSASSA-PKCS1-v1_5-SHA-384")] [InlineData(KeyType.RSA, 4096, "RSASSA-PKCS1-v1_5-SHA-512")] + public void KeySpec_ToSigningAlgorithmPKCS1_ReturnsCorrectValues(KeyType keyType, int size, string expectedSigningAlgorithm) + { + // Arrange + KeySpec keySpec = new KeySpec(keyType, size); + + // Act + string signingAlgorithm = keySpec.ToSigningAlgorithmPKCS1(); + + // Assert + Assert.Equal(expectedSigningAlgorithm, signingAlgorithm); + } + + [Theory] [InlineData(KeyType.EC, 256, "ECDSA-SHA-256")] [InlineData(KeyType.EC, 384, "ECDSA-SHA-384")] [InlineData(KeyType.EC, 521, "ECDSA-SHA-512")] - public void KeySpec_ToSigningAlgorithmPKCS1_ReturnsCorrectValues(KeyType keyType, int size, string expectedSigningAlgorithm) + public void KeySpec_ToSigningAlgorithmPKCS1_ECKey_ReturnsECDSAForSymmetry(KeyType keyType, int size, string expectedSigningAlgorithm) { // Arrange KeySpec keySpec = new KeySpec(keyType, size); @@ -73,12 +86,13 @@ public void KeySpec_ToSigningAlgorithmPKCS1_ThrowsArgumentExceptionForInvalidSiz } [Theory] - [InlineData(KeyType.RSA, 2048, null, "RSASSA-PSS-SHA-256")] // Default: PSS + [InlineData(KeyType.RSA, 2048, null, "RSASSA-PSS-SHA-256")] // Default: PSS [InlineData(KeyType.RSA, 2048, "", "RSASSA-PSS-SHA-256")] // Empty: PSS [InlineData(KeyType.RSA, 2048, "rsassa-pss", "RSASSA-PSS-SHA-256")] // Explicit PSS - [InlineData(KeyType.RSA, 2048, "rsassa-pkcs1-v1_5", "RSASSA-PKCS1-v1_5-SHA-256")] // PKCS1 routing - [InlineData(KeyType.RSA, 3072, "rsassa-pkcs1-v1_5", "RSASSA-PKCS1-v1_5-SHA-384")] // PKCS1 3072 - [InlineData(KeyType.RSA, 4096, "rsassa-pkcs1-v1_5", "RSASSA-PKCS1-v1_5-SHA-512")] // PKCS1 4096 + [InlineData(KeyType.RSA, 2048, "rsassa-pkcs1-v1_5", "RSASSA-PKCS1-v1_5-SHA-256")] // PKCS1 routing + [InlineData(KeyType.RSA, 2048, "RSASSA-PKCS1-V1_5", "RSASSA-PKCS1-v1_5-SHA-256")] // PKCS1 case-insensitive + [InlineData(KeyType.RSA, 3072, "rsassa-pkcs1-v1_5", "RSASSA-PKCS1-v1_5-SHA-384")] // PKCS1 3072 + [InlineData(KeyType.RSA, 4096, "rsassa-pkcs1-v1_5", "RSASSA-PKCS1-v1_5-SHA-512")] // PKCS1 4096 [InlineData(KeyType.EC, 256, "rsassa-pkcs1-v1_5", "ECDSA-SHA-256")] // EC unaffected [InlineData(KeyType.EC, 384, "rsassa-pkcs1-v1_5", "ECDSA-SHA-384")] // EC 384 [InlineData(KeyType.EC, 521, "rsassa-pkcs1-v1_5", "ECDSA-SHA-512")] // EC 521 @@ -94,15 +108,16 @@ public void KeySpec_ToSigningAlgorithmWithScheme_ReturnsCorrectValues(KeyType ke Assert.Equal(expectedSigningAlgorithm, signingAlgorithm); } - [Fact] - public void KeySpec_ToSigningAlgorithmWithInvalidScheme_ThrowsArgumentException() + [Theory] + [InlineData("invalid-scheme")] + public void KeySpec_ToSigningAlgorithmWithInvalidScheme_ThrowsArgumentException(string scheme) { // Arrange KeySpec keySpec = new KeySpec(KeyType.RSA, 2048); // Act & Assert - var ex = Assert.Throws(() => keySpec.ToSigningAlgorithm("invalid-scheme")); - Assert.Contains("invalid-scheme", ex.Message); + var ex = Assert.Throws(() => keySpec.ToSigningAlgorithm(scheme)); + Assert.Contains(scheme, ex.Message); Assert.Contains("rsassa-pss", ex.Message); Assert.Contains("rsassa-pkcs1-v1_5", ex.Message); } diff --git a/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs b/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs index d18ac187..bd0922cd 100644 --- a/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs +++ b/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs @@ -111,19 +111,29 @@ public async Task RunAsync() // Extract KeySpec from the certificate var keySpec = leafCert.KeySpec(); - // Get the signing scheme from plugin config (default: rsassa-pss) - // Supported values: - // - "rsassa-pss" (default): RSASSA-PSS padding for JWS/COSE signatures - // - "rsassa-pkcs1-v1_5": RSASSA-PKCS1-v1_5 padding for PKCS#7/dm-verity signatures + // Read the RSA signing scheme from the plugin config (defaults to + // rsassa-pss when absent or empty). The scheme-aware helpers handle + // case-insensitivity and reject unknown values. string? signingScheme = _request.PluginConfig?.GetValueOrDefault(SigningSchemeConfigKey); - // For RSASSA-PKCS1-v1_5 + EC, fail fast: PKCS#7-based verifiers - // cannot consume ECDSA signatures. - if (string.Equals(signingScheme, SigningScheme.RSASSA_PKCS1_V1_5, StringComparison.OrdinalIgnoreCase) + // Reject unknown scheme strings here so the failure is reported + // with the plugin's error code + if (!string.IsNullOrEmpty(signingScheme) + && !string.Equals(signingScheme, SigningSchemes.RSASSA_PSS, StringComparison.OrdinalIgnoreCase) + && !string.Equals(signingScheme, SigningSchemes.RSASSA_PKCS1_V1_5, StringComparison.OrdinalIgnoreCase)) + { + throw new ValidationException( + $"Invalid signing scheme: {signingScheme}. Supported values are '{SigningSchemes.RSASSA_PSS}' and '{SigningSchemes.RSASSA_PKCS1_V1_5}'"); + } + + // rsassa-pkcs1-v1_5 is an RSA-padding scheme and cannot be + // satisfied by an EC key. Reject before the AKV sign call so the + // failure is local to the plugin. + if (string.Equals(signingScheme, SigningSchemes.RSASSA_PKCS1_V1_5, StringComparison.OrdinalIgnoreCase) && keySpec.Type == KeyType.EC) { throw new ValidationException( - $"Signing scheme '{SigningScheme.RSASSA_PKCS1_V1_5}' requires an RSA key; got EC."); + $"Signing scheme '{SigningSchemes.RSASSA_PKCS1_V1_5}' requires an RSA key; got EC."); } // Determine the Azure Key Vault signature algorithm based on scheme diff --git a/Notation.Plugin.AzureKeyVault/KeyVault/KeySpecExtension.cs b/Notation.Plugin.AzureKeyVault/KeyVault/KeySpecExtension.cs index e76b65eb..5e775513 100644 --- a/Notation.Plugin.AzureKeyVault/KeyVault/KeySpecExtension.cs +++ b/Notation.Plugin.AzureKeyVault/KeyVault/KeySpecExtension.cs @@ -32,14 +32,10 @@ public static class KeySpecExtension }; /// - /// Get SignatureAlgorithm from KeySpec for Azure Key Vault signing using RSASSA-PKCS1-v1_5. - /// Uses RS256/RS384/RS512 for RSA keys (required for PKCS#7/dm-verity). - /// For EC keys, returns the standard ECDSA algorithm (no padding change). The - /// scheme-aware - /// is the only call site; this helper is internal so callers cannot rely on the - /// EC fallback as a public contract. The fail-fast in - /// GenerateSignature.RunAsync rejects EC keys when the - /// rsassa-pkcs1-v1_5 scheme is requested. + /// Get the SignatureAlgorithm for RSASSA-PKCS1-v1_5 signing. + /// Uses RS256/RS384/RS512 for RSA-2048/3072/4096 (required for PKCS#7/dm-verity). + /// EC keys fall back to ES256/ES384/ES512 so the helper stays consistent with the + /// default RSASSA-PSS overload. /// internal static SignatureAlgorithm ToKeyVaultSignatureAlgorithmPKCS1(this KeySpec keySpec) => keySpec.Type switch { @@ -67,12 +63,13 @@ public static class KeySpecExtension /// The key specification /// The signing scheme (rsassa-pss or rsassa-pkcs1-v1_5). Default is rsassa-pss. /// The Azure Key Vault SignatureAlgorithm + // NOTE: keep in lock-step with KeySpec.ToSigningAlgorithm(string?). public static SignatureAlgorithm ToKeyVaultSignatureAlgorithm(this KeySpec keySpec, string? scheme) => scheme?.ToLowerInvariant() switch { - SigningScheme.RSASSA_PKCS1_V1_5 => keySpec.ToKeyVaultSignatureAlgorithmPKCS1(), - SigningScheme.RSASSA_PSS => keySpec.ToKeyVaultSignatureAlgorithm(), + SigningSchemes.RSASSA_PKCS1_V1_5 => keySpec.ToKeyVaultSignatureAlgorithmPKCS1(), + SigningSchemes.RSASSA_PSS => keySpec.ToKeyVaultSignatureAlgorithm(), null or "" => keySpec.ToKeyVaultSignatureAlgorithm(), - _ => throw new ArgumentException($"Invalid signing scheme: {scheme}. Supported values are '{SigningScheme.RSASSA_PSS}' and '{SigningScheme.RSASSA_PKCS1_V1_5}'") + _ => throw new ArgumentException($"Invalid signing scheme: {scheme}. Supported values are '{SigningSchemes.RSASSA_PSS}' and '{SigningSchemes.RSASSA_PKCS1_V1_5}'") }; } } diff --git a/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs b/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs index 26868249..009d59ba 100644 --- a/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs +++ b/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs @@ -43,7 +43,7 @@ public static class SigningAlgorithms /// /// Defines the supported signing schemes (padding modes for RSA). /// - public static class SigningScheme + public static class SigningSchemes { /// /// RSASSA-PSS padding (default for JWS/COSE notation signatures). @@ -137,9 +137,10 @@ public KeySpec(KeyType type, int size) }; /// - /// Convert KeySpec to be SigningAlgorithm string using RSASSA-PKCS1-v1_5. - /// This is required for PKCS#7 signatures. For EC keys, this returns the - /// standard ECDSA algorithm (no padding change). + /// Get the SigningAlgorithm string for RSASSA-PKCS1-v1_5 signing. + /// Uses RSASSA-PKCS1-v1_5-SHA-256/384/512 for RSA-2048/3072/4096 (required for + /// PKCS#7/dm-verity). EC keys fall back to ECDSA-SHA-256/384/512 so the helper + /// stays consistent with the default RSASSA-PSS overload. /// internal string ToSigningAlgorithmPKCS1() => Type switch { @@ -166,12 +167,13 @@ public KeySpec(KeyType type, int size) /// /// The signing scheme to use (rsassa-pss or rsassa-pkcs1-v1_5) /// The appropriate signing algorithm string + // NOTE: keep in lock-step with KeySpecExtension.ToKeyVaultSignatureAlgorithm(string?). public string ToSigningAlgorithm(string? scheme) => scheme?.ToLowerInvariant() switch { - SigningScheme.RSASSA_PKCS1_V1_5 => ToSigningAlgorithmPKCS1(), - SigningScheme.RSASSA_PSS => ToSigningAlgorithm(), + SigningSchemes.RSASSA_PKCS1_V1_5 => ToSigningAlgorithmPKCS1(), + SigningSchemes.RSASSA_PSS => ToSigningAlgorithm(), null or "" => ToSigningAlgorithm(), - _ => throw new ArgumentException($"Invalid signing scheme: {scheme}. Supported values are '{SigningScheme.RSASSA_PSS}' and '{SigningScheme.RSASSA_PKCS1_V1_5}'") + _ => throw new ArgumentException($"Invalid signing scheme: {scheme}. Supported values are '{SigningSchemes.RSASSA_PSS}' and '{SigningSchemes.RSASSA_PKCS1_V1_5}'") }; } }