Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions Notation.Plugin.AzureKeyVault.Tests/Command/GenerateSignatureTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,5 +248,129 @@ public async Task RunAsync_SelfSignedWithCaCerts()

await Assert.ThrowsAsync<PluginException>(async () => await generateSignatureCommand.RunAsync());
}

[Theory]
[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<IKeyVaultClient>();
mockKeyVaultClient.Setup(client => client.GetCertificateAsync())
.ReturnsAsync(rsaCert);

var request = new GenerateSignatureRequest(
contractVersion: "1.0",
keyId: keyId,
pluginConfig: new Dictionary<string, string>()
{
["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<ValidationException>(
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<SignatureAlgorithm>(), It.IsAny<byte[]>()),
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";
var ecCert = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "TestData", ecCertFile));
var mockKeyVaultClient = new Mock<IKeyVaultClient>();
mockKeyVaultClient.Setup(client => client.GetCertificateAsync())
.ReturnsAsync(ecCert);

var request = new GenerateSignatureRequest(
contractVersion: "1.0",
keyId: keyId,
pluginConfig: new Dictionary<string, string>()
{
["self_signed"] = "true",
[GenerateSignature.SigningSchemeConfigKey] = scheme
},
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<ValidationException>(
async () => await generateSignatureCommand.RunAsync());
Assert.Contains(SigningSchemes.RSASSA_PKCS1_V1_5, ex.Message);
mockKeyVaultClient.Verify(
c => c.SignAsync(It.IsAny<SignatureAlgorithm>(), It.IsAny<byte[]>()),
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<IKeyVaultClient>();
mockKeyVaultClient.Setup(client => client.GetCertificateAsync())
.ReturnsAsync(rsaCert);

SignatureAlgorithm? capturedAlgorithm = null;
mockKeyVaultClient.Setup(client => client.SignAsync(It.IsAny<SignatureAlgorithm>(), It.IsAny<byte[]>()))
.Callback<SignatureAlgorithm, byte[]>((algo, _) => capturedAlgorithm = algo)
.ReturnsAsync(mockSignature);

var request = new GenerateSignatureRequest(
contractVersion: "1.0",
keyId: keyId,
pluginConfig: new Dictionary<string, string>()
{
["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<GenerateSignatureResponse>(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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,86 @@ public void ToSignatureAlgorithm_InvalidKeySpecs_ThrowsArgumentException(KeyType
// Act & Assert
Assert.Throws<ArgumentException>(() => keySpec.ToKeyVaultSignatureAlgorithm());
}

[Theory]
[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_ECKey_ReturnsECDSAForSymmetry(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<ArgumentException>(() => 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, 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
var keySpec = new KeySpec(keyType, keySize);

// Act
var signatureAlgorithm = keySpec.ToKeyVaultSignatureAlgorithm(scheme);

// Assert
Assert.Equal(expectedAlgorithm, signatureAlgorithm);
}

[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<ArgumentException>(() => keySpec.ToKeyVaultSignatureAlgorithm(scheme));
Assert.Contains(scheme, ex.Message);
Assert.Contains("rsassa-pss", ex.Message);
Assert.Contains("rsassa-pkcs1-v1_5", ex.Message);
}
}
}
}
81 changes: 81 additions & 0 deletions Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,86 @@ public void KeySpec_EncodeKeySpecAndToSigningAlgorithm_ThrowsArgumentExceptionFo
Assert.Throws<ArgumentException>(() => keySpec.EncodeKeySpec());
Assert.Throws<ArgumentException>(() => keySpec.ToSigningAlgorithm());
}

[Theory]
[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_ECKey_ReturnsECDSAForSymmetry(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<ArgumentException>(() => 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, 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
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);
}

[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<ArgumentException>(() => keySpec.ToSigningAlgorithm(scheme));
Assert.Contains(scheme, ex.Message);
Assert.Contains("rsassa-pss", ex.Message);
Assert.Contains("rsassa-pkcs1-v1_5", ex.Message);
}
}
}
44 changes: 41 additions & 3 deletions Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ namespace Notation.Plugin.AzureKeyVault.Command
/// </summary>
public class GenerateSignature : IPluginCommand
{
/// <summary>
/// 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).
/// </summary>
public const string SigningSchemeConfigKey = "signing_scheme";
Comment thread
dallasd1 marked this conversation as resolved.

private GenerateSignatureRequest _request;
private IKeyVaultClient _keyVaultClient;

Expand Down Expand Up @@ -104,13 +111,44 @@ public async Task<IPluginResponse> RunAsync()
// Extract KeySpec from the certificate
var keySpec = leafCert.KeySpec();

// Sign
var signature = await _keyVaultClient.SignAsync(keySpec.ToKeyVaultSignatureAlgorithm(), _request.Payload);
// 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);

// 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 '{SigningSchemes.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);

// 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());
}
}
Expand Down
Loading
Loading