diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index deea3828..64ad5040 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,6 +32,7 @@ jobs: DEFAULT_BRANCH: main DEFAULT_WORKSPACE: ./Notation.Plugin.AzureKeyVault GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FILTER_REGEX_EXCLUDE: .*Tests/.* build: name: "Build" runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index 45117191..ad30167e 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ build: ## builds binaries .PHONY: test test: ## run unit test + rm -rf $(BUILD_DIR)/TestResults dotnet test $(TEST_PROJECT_DIR) --collect:"XPlat Code Coverage" --logger trx --results-directory $(BUILD_DIR)/TestResults .PHONY: install diff --git a/Notation.Plugin.AzureKeyVault.Tests/Certificate/CertificateBundleTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Certificate/CertificateBundleTests.cs new file mode 100644 index 00000000..d7605e90 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/Certificate/CertificateBundleTests.cs @@ -0,0 +1,23 @@ +using System.IO; +using System.Security.Cryptography.X509Certificates; +using Xunit; + +namespace Notation.Plugin.AzureKeyVault.Certificate.Tests +{ + public class CertificateBundleTests + { + [Fact] + public void Create_WithValidPemFile_ReturnsCertificates() + { + // Arrange + string pemFilePath = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "rsa_2048.crt"); + + // Act + X509Certificate2Collection certificates = CertificateBundle.Create(pemFilePath); + + // Assert + Assert.NotNull(certificates); + Assert.True(certificates.Count > 0); + } + } +} diff --git a/Notation.Plugin.AzureKeyVault.Tests/Certificate/CertificateChainTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Certificate/CertificateChainTests.cs new file mode 100644 index 00000000..7f337c8f --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/Certificate/CertificateChainTests.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography.X509Certificates; +using Notation.Plugin.Protocol; +using Xunit; + +namespace Notation.Plugin.AzureKeyVault.Certificate.Tests +{ + public class CertificateChainTests + { + [Fact] + public void Build_WithValidLeafAndCertificateBundle_BuildsCertificateChain() + { + // Arrange + X509Certificate2 leafCert = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "leaf.crt")); + X509Certificate2Collection certificateBundle = CertificateBundle.Create(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "root.crt")); + + // Act + List certificateChain = CertificateChain.Build(leafCert, certificateBundle); + + // Assert + Assert.NotNull(certificateChain); + Assert.True(certificateChain.Count > 0); + } + + [Fact] + public void Build_WithInvalidLeafCertificate_ThrowsValidationException() + { + // Arrange + X509Certificate2 expiredLeafCert = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "expired_leaf.crt")); + X509Certificate2Collection certificateBundle = new X509Certificate2Collection(); + + // Act and Assert + Assert.Throws(() => CertificateChain.Build(expiredLeafCert, certificateBundle)); + } + + [Fact] + public void Build_WithIncompleteCertificateBundle_ThrowsValidationException() + { + // Arrange + X509Certificate2 invalidLeafCert = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "leaf.crt")); + X509Certificate2Collection certificateBundle = new X509Certificate2Collection(); + + // Act and Assert + Assert.Throws(() => CertificateChain.Build(invalidLeafCert, certificateBundle)); + } + } +} diff --git a/Notation.Plugin.AzureKeyVault.Tests/Command/DescribeKeyTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Command/DescribeKeyTests.cs new file mode 100644 index 00000000..ebc45ea4 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/Command/DescribeKeyTests.cs @@ -0,0 +1,61 @@ +using System.IO; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using Moq; +using Notation.Plugin.AzureKeyVault.Client; +using Notation.Plugin.Protocol; +using Xunit; + +namespace Notation.Plugin.AzureKeyVault.Command.Tests +{ + public class DescribeKeyTests + { + [Fact] + public async Task RunAsync_ReturnsValidDescribeKeyResponseAsync() + { + // Arrange + var keyId = "https://testvault.vault.azure.net/keys/testkey/123"; + var expectedKeySpec = "RSA-2048"; + var mockCert = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "rsa_2048.crt")); + + var mockKeyVaultClient = new Mock(); + mockKeyVaultClient.Setup(client => client.GetCertificateAsync()).ReturnsAsync(mockCert); + + var request = new DescribeKeyRequest(contractVersion: "1.0", keyId); + var describeKeyCommand = new DescribeKey(request, mockKeyVaultClient.Object); + + // Act + var result = await describeKeyCommand.RunAsync(); + + // Assert + Assert.IsType(result); + var response = result as DescribeKeyResponse; + if (response == null) + { + throw new System.Exception("response is null"); + } + Assert.Equal(keyId, response.KeyId); + Assert.Equal(expectedKeySpec, response.KeySpec); + } + + [Fact] + public void Constructor_ThrowsValidationException_WhenInvalidInput() + { + // Arrange + string invalidInputJson = "null"; + + // Act & Assert + Assert.Throws(() => new DescribeKey(invalidInputJson)); + } + + [Fact] + public void Constructor_Valid() + { + // Arrange + string validInputJson = "{\"contractVersion\":\"1.0\",\"keyId\":\"https://notationakvtest.vault.azure.net/keys/dotnetPluginCertPKCS12/3f06c6eeac0640ea9f93cd0bf69d2f17\"}"; + + // Act & Assert + Assert.Null(Record.Exception(() => new DescribeKey(validInputJson))); + } + } +} diff --git a/Notation.Plugin.AzureKeyVault.Tests/Command/GenerateSignatureTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Command/GenerateSignatureTests.cs new file mode 100644 index 00000000..7619a414 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/Command/GenerateSignatureTests.cs @@ -0,0 +1,171 @@ +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using Azure.Security.KeyVault.Keys.Cryptography; +using Moq; +using Notation.Plugin.AzureKeyVault.Certificate; +using Notation.Plugin.AzureKeyVault.Client; +using Notation.Plugin.Protocol; +using Xunit; + +namespace Notation.Plugin.AzureKeyVault.Command.Tests +{ + public class GenerateSignatureTests + { + [Fact] + public async Task RunAsync_SelfSigned_ReturnsValidGenerateSignatureResponseAsync() + { + // Arrange + var keyId = "https://testvault.vault.azure.net/keys/testkey/123"; + var expectedKeySpec = "RSA-2048"; + var mockCert = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "rsa_2048.crt")); + var mockSignature = new byte[] { 0x01, 0x02, 0x03, 0x04 }; + + var mockKeyVaultClient = new Mock(); + // mock GetCertificateAsync + mockKeyVaultClient.Setup(client => client.GetCertificateAsync()) + .ReturnsAsync(mockCert); + + // mock SignAsync + mockKeyVaultClient.Setup(client => client.SignAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(mockSignature); + + var request = new GenerateSignatureRequest( + contractVersion: "1.0", + keyId: keyId, + pluginConfig: null, + keySpec: expectedKeySpec, + hashAlgorithm: "SHA-256", + payload: Encoding.UTF8.GetBytes("Cg==")); + + var generateSignatureCommand = new GenerateSignature(request, mockKeyVaultClient.Object); + + var result = await generateSignatureCommand.RunAsync(); + + Assert.IsType(result); + var response = result as GenerateSignatureResponse; + if (response == null) + { + throw new System.Exception("response is null"); + } + Assert.Equal(keyId, response.KeyId); + Assert.Equal("RSASSA-PSS-SHA-256", response.SigningAlgorithm); + Assert.Equal(mockSignature, response.Signature); + Assert.Single(response.CertificateChain); + Assert.Equal(mockCert.RawData, response.CertificateChain[0]); + } + + [Fact] + public async Task RunAsync_ca_certs_ReturnsValidGenerateSignatureResponseAsync() + { + // Arrange + var keyId = "https://testvault.vault.azure.net/keys/testkey/123"; + var expectedKeySpec = "RSA-2048"; + var testRootCert = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "root.crt")); + var mockCert = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "leaf.crt")); + var mockSignature = new byte[] { 0x01, 0x02, 0x03, 0x04 }; + + var mockKeyVaultClient = new Mock(); + // mock GetCertificateAsync + mockKeyVaultClient.Setup(client => client.GetCertificateAsync()) + .ReturnsAsync(mockCert); + + // mock SignAsync + mockKeyVaultClient.Setup(client => client.SignAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(mockSignature); + + var request = new GenerateSignatureRequest( + contractVersion: "1.0", + keyId: keyId, + pluginConfig: new Dictionary() + { + ["ca_certs"] = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "root.crt") + }, + keySpec: expectedKeySpec, + hashAlgorithm: "SHA-256", + payload: Encoding.UTF8.GetBytes("Cg==")); + + var generateSignatureCommand = new GenerateSignature(request, mockKeyVaultClient.Object); + + var result = await generateSignatureCommand.RunAsync(); + + Assert.IsType(result); + var response = result as GenerateSignatureResponse; + if (response == null) + { + throw new System.Exception("response is null"); + } + Assert.Equal(keyId, response.KeyId); + Assert.Equal("RSASSA-PSS-SHA-256", response.SigningAlgorithm); + Assert.Equal(mockSignature, response.Signature); + Assert.Equal(2, response.CertificateChain.Count); + Assert.Equal(mockCert.RawData, response.CertificateChain[0]); + Assert.Equal(testRootCert.RawData, response.CertificateChain[1]); + } + + [Fact] + public async Task RunAsync_as_secret_ReturnsValidGenerateSignatureResponseAsync() + { + // Arrange + var keyId = "https://testvault.vault.azure.net/keys/testkey/123"; + var expectedKeySpec = "RSA-2048"; + var mockSignature = new byte[] { 0x01, 0x02, 0x03, 0x04 }; + var mockCertChain = CertificateBundle.Create(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "cert_chain.pem")); + + var mockKeyVaultClient = new Mock(); + // mock GetCertificateAsync + mockKeyVaultClient.Setup(client => client.GetCertificateChainAsync()) + .ReturnsAsync(mockCertChain); + + // mock SignAsync + mockKeyVaultClient.Setup(client => client.SignAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(mockSignature); + + var request = new GenerateSignatureRequest( + contractVersion: "1.0", + keyId: keyId, + pluginConfig: new Dictionary() + { + ["as_secret"] = "true" + }, + keySpec: expectedKeySpec, + hashAlgorithm: "SHA-256", + payload: Encoding.UTF8.GetBytes("Cg==")); + + var generateSignatureCommand = new GenerateSignature(request, mockKeyVaultClient.Object); + + var result = await generateSignatureCommand.RunAsync(); + + Assert.IsType(result); + var response = result as GenerateSignatureResponse; + if (response == null) + { + throw new System.Exception("response is null"); + } + Assert.Equal(keyId, response.KeyId); + Assert.Equal("RSASSA-PSS-SHA-256", response.SigningAlgorithm); + Assert.Equal(mockSignature, response.Signature); + Assert.Equal(2, response.CertificateChain.Count); + Assert.Equal(mockCertChain[0].RawData, response.CertificateChain[0]); + Assert.Equal(mockCertChain[1].RawData, response.CertificateChain[1]); + } + + [Fact] + public void Constructor_Valid() + { + string validInputJson = "{\"contractVersion\":\"1.0\",\"keyId\":\"https://notationakvtest.vault.azure.net/keys/dotnetPluginCert/b6046b30d069458886de94b0ac9ed121\",\"keySpec\":\"RSA-2048\",\"hashAlgorithm\":\"SHA-256\",\"payload\":\"Cg==\"}"; + + Assert.Null(Record.Exception(() => new GenerateSignature(validInputJson))); + } + + [Fact] + public void Constructor_Invalid() + { + string InvalidInputJson = "null"; + + Assert.Throws(() => new GenerateSignature(InvalidInputJson)); + } + } +} \ No newline at end of file diff --git a/Notation.Plugin.AzureKeyVault.Tests/Command/GetPluginMetadataTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Command/GetPluginMetadataTests.cs index c5b85bed..8b24ee6c 100644 --- a/Notation.Plugin.AzureKeyVault.Tests/Command/GetPluginMetadataTests.cs +++ b/Notation.Plugin.AzureKeyVault.Tests/Command/GetPluginMetadataTests.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -using Xunit; using Notation.Plugin.Protocol; +using Xunit; namespace Notation.Plugin.AzureKeyVault.Command.Tests { @@ -13,7 +13,7 @@ public async Task RunAsync_ReturnsExpectedMetadata() var getPluginMetadata = new GetPluginMetadata(); // Act - var result = await getPluginMetadata.RunAsync(""); + var result = await getPluginMetadata.RunAsync(); // Assert Assert.IsType(result); diff --git a/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs b/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs new file mode 100644 index 00000000..9912ef4b --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeySpecExtensionTests.cs @@ -0,0 +1,39 @@ +using System; +using Notation.Plugin.Protocol; +using Xunit; + +namespace Notation.Plugin.AzureKeyVault.Client.Tests +{ + public class KeySpecExtensionTests + { + [Theory] + [InlineData(KeyType.RSA, 2048, "PS256")] + [InlineData(KeyType.RSA, 3072, "PS384")] + [InlineData(KeyType.RSA, 4096, "PS512")] + [InlineData(KeyType.EC, 256, "ES256")] + [InlineData(KeyType.EC, 384, "ES384")] + [InlineData(KeyType.EC, 521, "ES512")] + public void ToSignatureAlgorithm_ValidKeySpecs_ReturnsCorrectSignatureAlgorithm(KeyType keyType, int keySize, string expectedAlgorithm) + { + // Arrange + var keySpec = new KeySpec(keyType, keySize); + // Act + var signatureAlgorithm = keySpec.ToKeyVaultSignatureAlgorithm(); + + // Assert + Assert.Equal(expectedAlgorithm, signatureAlgorithm); + } + + [Theory] + [InlineData(KeyType.RSA, 1024)] + [InlineData(KeyType.EC, 128)] + public void ToSignatureAlgorithm_InvalidKeySpecs_ThrowsArgumentException(KeyType keyType, int keySize) + { + // Arrange + var keySpec = new KeySpec(keyType, keySize); + + // Act & Assert + Assert.Throws(() => keySpec.ToKeyVaultSignatureAlgorithm()); + } + } +} \ No newline at end of file diff --git a/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeyVaultClientTests.cs b/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeyVaultClientTests.cs new file mode 100644 index 00000000..1875575f --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/KeyVault/KeyVaultClientTests.cs @@ -0,0 +1,253 @@ +using System; +using System.IO; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Azure; +using Azure.Core; +using Azure.Security.KeyVault.Certificates; +using Azure.Security.KeyVault.Keys; +using Azure.Security.KeyVault.Keys.Cryptography; +using Azure.Security.KeyVault.Secrets; +using Moq; +using Notation.Plugin.Protocol; +using Xunit; + +namespace Notation.Plugin.AzureKeyVault.Client.Tests +{ + public class KeyVaultClientTests + { + [Fact] + public void TestConstructorWithKeyId() + { + string keyId = "https://myvault.vault.azure.net/keys/my-key/123"; + + KeyVaultClient keyVaultClient = new KeyVaultClient(keyId); + + Assert.Equal("my-key", keyVaultClient.Name); + Assert.Equal("123", keyVaultClient.Version); + Assert.Equal(keyId, keyVaultClient.KeyId); + } + + [Fact] + public void TestConstructorWithKeyVaultUrlNameVersion() + { + string keyVaultUrl = "https://myvault.vault.azure.net"; + string name = "my-key"; + string version = "123"; + + KeyVaultClient keyVaultClient = new KeyVaultClient(keyVaultUrl, name, version); + + Assert.Equal(name, keyVaultClient.Name); + Assert.Equal(version, keyVaultClient.Version); + Assert.Equal($"{keyVaultUrl}/keys/{name}/{version}", keyVaultClient.KeyId); + } + + [Theory] + [InlineData("https://myvault.vault.azure.net/invalid/my-key/123")] + [InlineData("https://myvault.vault.azure.net/keys/my-key")] + [InlineData("https://myvault.vault.azure.net/keys/my-key/")] + [InlineData("http://myvault.vault.azure.net/keys/my-key/123")] + public void TestConstructorWithInvalidKeyId(string invalidKeyId) + { + Assert.Throws(() => new KeyVaultClient(invalidKeyId)); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void TestConstructorWithEmptyKeyId(string invalidKeyId) + { + Assert.Throws(() => new KeyVaultClient(invalidKeyId)); + } + + private class TestableKeyVaultClient : KeyVaultClient + { + public TestableKeyVaultClient(string keyVaultUrl, string name, string version, CryptographyClient cryptoClient) + : base(keyVaultUrl, name, version) + { + this._cryptoClient = new Lazy(() => cryptoClient); + } + + public TestableKeyVaultClient(string keyVaultUrl, string name, string version, CertificateClient certificateClient) + : base(keyVaultUrl, name, version) + { + this._certificateClient = new Lazy(() => certificateClient); + } + + public TestableKeyVaultClient(string keyVaultUrl, string name, string version, SecretClient secretClient) + : base(keyVaultUrl, name, version) + { + this._secretClient = new Lazy(() => secretClient); + } + } + + private TestableKeyVaultClient CreateMockedKeyVaultClient(SignResult signResult) + { + var mockCryptoClient = new Mock(new Uri("https://fake.vault.azure.net/keys/fake-key/123"), new Mock().Object); + mockCryptoClient.Setup(c => c.SignDataAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(signResult); + + return new TestableKeyVaultClient("https://fake.vault.azure.net", "fake-key", "123", mockCryptoClient.Object); + } + + private TestableKeyVaultClient CreateMockedKeyVaultClient(KeyVaultCertificate certificate) + { + var mockCertificateClient = new Mock(new Uri("https://fake.vault.azure.net/certificates/fake-certificate/123"), new Mock().Object); + mockCertificateClient.Setup(c => c.GetCertificateVersionAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(Response.FromValue(certificate, new Mock().Object)); + + return new TestableKeyVaultClient("https://fake.vault.azure.net", "fake-certificate", "123", mockCertificateClient.Object); + } + + private TestableKeyVaultClient CreateMockedKeyVaultClient(KeyVaultSecret secret) + { + var mockSecretClient = new Mock(new Uri("https://fake.vault.azure.net/secrets/fake-secret/123"), new Mock().Object); + mockSecretClient.Setup(c => c.GetSecretAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(Response.FromValue(secret, new Mock().Object)); + return new TestableKeyVaultClient("https://fake.vault.azure.net", "fake-certificate", "123", mockSecretClient.Object); + } + + [Fact] + public async Task TestSignAsyncReturnsExpectedSignature() + { + var signResult = CryptographyModelFactory.SignResult( + keyId: "https://fake.vault.azure.net/keys/fake-key/123", + signature: new byte[] { 1, 2, 3 }, + algorithm: SignatureAlgorithm.RS256); + + TestableKeyVaultClient keyVaultClient = CreateMockedKeyVaultClient(signResult); + byte[] payload = new byte[] { 4, 5, 6 }; + + byte[] signature = await keyVaultClient.SignAsync(SignatureAlgorithm.RS256, payload); + + Assert.Equal(signResult.Signature, signature); + } + + [Fact] + public async Task TestSignAsyncThrowsExceptionOnInvalidKeyId() + { + var signResult = CryptographyModelFactory.SignResult( + keyId: "https://fake.vault.azure.net/keys/invalid-key/123", + signature: new byte[] { 1, 2, 3 }, + algorithm: SignatureAlgorithm.RS256); + + TestableKeyVaultClient keyVaultClient = CreateMockedKeyVaultClient(signResult); + byte[] payload = new byte[] { 4, 5, 6 }; + + await Assert.ThrowsAsync(async () => await keyVaultClient.SignAsync(SignatureAlgorithm.RS256, payload)); + } + + [Fact] + public async Task TestSignAsyncThrowsExceptionOnInvalidAlgorithm() + { + var signResult = CryptographyModelFactory.SignResult( + keyId: "https://fake.vault.azure.net/keys/fake-key/123", + signature: new byte[] { 1, 2, 3 }, + algorithm: SignatureAlgorithm.RS384); + + TestableKeyVaultClient keyVaultClient = CreateMockedKeyVaultClient(signResult); + byte[] payload = new byte[] { 4, 5, 6 }; + await Assert.ThrowsAsync(async () => await keyVaultClient.SignAsync(SignatureAlgorithm.RS256, payload)); + } + + [Fact] + public async Task GetCertificateAsync_ReturnsCertificate() + { + var testCertificate = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "rsa_2048.crt")); + var signResult = CryptographyModelFactory.SignResult( + keyId: "https://fake.vault.azure.net/keys/fake-key/123", + signature: new byte[] { 1, 2, 3 }, + algorithm: SignatureAlgorithm.RS384); + + var keyVaultCertificate = CertificateModelFactory.KeyVaultCertificate( + properties: CertificateModelFactory.CertificateProperties(version: "123"), + cer: testCertificate.RawData); + + var keyVaultClient = CreateMockedKeyVaultClient(keyVaultCertificate); + var certificate = await keyVaultClient.GetCertificateAsync(); + + Assert.NotNull(certificate); + Assert.IsType(certificate); + Assert.Equal("123", keyVaultCertificate.Properties.Version); + Assert.Equal(testCertificate.RawData, certificate.RawData); + } + + [Fact] + public async Task GetCertificateAsyncThrowValidationException() + { + var testCertificate = new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "rsa_2048.crt")); + var signResult = CryptographyModelFactory.SignResult( + keyId: "https://fake.vault.azure.net/keys/fake-key/123", + signature: new byte[] { 1, 2, 3 }, + algorithm: SignatureAlgorithm.RS384); + + var keyVaultCertificate = CertificateModelFactory.KeyVaultCertificate( + properties: CertificateModelFactory.CertificateProperties(version: "1234"), + cer: testCertificate.RawData); + + var keyVaultClient = CreateMockedKeyVaultClient(keyVaultCertificate); + + await Assert.ThrowsAsync(async () => await keyVaultClient.GetCertificateAsync()); + } + + [Fact] + public async Task GetCertificateChainAsync_PKCS12() + { + var certChainBytes = File.ReadAllBytes(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "cert_chain.pfx")); + var testCertificateChain = new X509Certificate2Collection(); + testCertificateChain.Import(certChainBytes, "", X509KeyStorageFlags.Exportable); + var properties = SecretModelFactory.SecretProperties(); + properties.ContentType = "application/x-pkcs12"; + var secret = SecretModelFactory.KeyVaultSecret( + value: Convert.ToBase64String(certChainBytes), + properties: properties); + + var keyVaultClient = CreateMockedKeyVaultClient(secret); + + var certificateChain = await keyVaultClient.GetCertificateChainAsync(); + + Assert.NotNull(certificateChain); + Assert.IsType(certificateChain); + Assert.Equal(2, certificateChain.Count); + Assert.Equal(testCertificateChain[0].RawData, certificateChain[0].RawData); + Assert.Equal(testCertificateChain[1].RawData, certificateChain[1].RawData); + } + + [Fact] + public async Task GetCertificateChainAsync_PEM() + { + var certChainBytes = File.ReadAllBytes(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "cert_chain.pem")); + var testCertificateChain = new X509Certificate2Collection(); + testCertificateChain.ImportFromPemFile(Path.Combine(Directory.GetCurrentDirectory(), "TestData", "cert_chain.pem")); + var properties = SecretModelFactory.SecretProperties(); + properties.ContentType = "application/x-pem-file"; + var secret = SecretModelFactory.KeyVaultSecret( + value: Encoding.UTF8.GetString(certChainBytes), + properties: properties); + + var keyVaultClient = CreateMockedKeyVaultClient(secret); + + var certificateChain = await keyVaultClient.GetCertificateChainAsync(); + + Assert.NotNull(certificateChain); + Assert.IsType(certificateChain); + Assert.Equal(2, certificateChain.Count); + Assert.Equal(testCertificateChain[0].RawData, certificateChain[0].RawData); + Assert.Equal(testCertificateChain[1].RawData, certificateChain[1].RawData); + } + + [Fact] + public async Task GetCertificateChainAsync_UnknownFormat() + { + var properties = SecretModelFactory.SecretProperties(); + properties.ContentType = "application/x-unknown"; + var secret = SecretModelFactory.KeyVaultSecret( + properties: properties); + + var keyVaultClient = CreateMockedKeyVaultClient(secret); + await Assert.ThrowsAsync(async () => await keyVaultClient.GetCertificateChainAsync()); + } + } +} diff --git a/Notation.Plugin.AzureKeyVault.Tests/Notation.Plugin.AzureKeyVault.Tests.csproj b/Notation.Plugin.AzureKeyVault.Tests/Notation.Plugin.AzureKeyVault.Tests.csproj index 7b708c6d..b7e396f2 100644 --- a/Notation.Plugin.AzureKeyVault.Tests/Notation.Plugin.AzureKeyVault.Tests.csproj +++ b/Notation.Plugin.AzureKeyVault.Tests/Notation.Plugin.AzureKeyVault.Tests.csproj @@ -9,6 +9,8 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -18,6 +20,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + + + diff --git a/Notation.Plugin.AzureKeyVault.Tests/Protocol/CertificateExtensionTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Protocol/CertificateExtensionTests.cs new file mode 100644 index 00000000..218cf045 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/Protocol/CertificateExtensionTests.cs @@ -0,0 +1,61 @@ +using System.IO; +using System.Security.Cryptography.X509Certificates; +using Xunit; + +namespace Notation.Plugin.Protocol.Tests +{ + public class CertificateExtensionTests + { + [Theory] + [InlineData("RSA", 2048)] + [InlineData("RSA", 3072)] + [InlineData("RSA", 4096)] + [InlineData("EC", 256)] + [InlineData("EC", 384)] + [InlineData("EC", 521)] + public void KeySpec_ValidKeySize_ReturnsKeySpec(string keyType, int keySize) + { + // Arrange + X509Certificate2 certificate = LoadCertificate(keyType, keySize); + + // Act + KeySpec result = certificate.KeySpec(); + + // Assert + Assert.Equal(keyType, result.Type.ToString()); + Assert.Equal(keySize, result.Size); + } + + [Theory] + [InlineData("RSA", 1024)] + [InlineData("EC", 163)] + public void KeySpec_InvalidKeySize_ThrowsValidationException(string keyType, int keySize) + { + // Arrange + X509Certificate2 certificate = LoadCertificate(keyType, keySize); + + // Act & Assert + Assert.Throws(() => certificate.KeySpec()); + } + + [Fact] + public void KeySpec_UnsupportedPublicKeyType_ThrowsValidationException() + { + // Arrange + X509Certificate2 certificate = LoadCertificate("dsa", 2048); + + // Act & Assert + Assert.Throws(() => certificate.KeySpec()); + } + + // + // Load certificate from file. + // + private static X509Certificate2 LoadCertificate(string keyType, int keySize) + { + var certName = $"{keyType.ToLower()}_{keySize}.crt"; + return new X509Certificate2(Path.Combine(Directory.GetCurrentDirectory(), "TestData", certName)); + } + + } +} diff --git a/Notation.Plugin.AzureKeyVault.Tests/Protocol/DescribeKeyTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Protocol/DescribeKeyTests.cs new file mode 100644 index 00000000..e102b1ec --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/Protocol/DescribeKeyTests.cs @@ -0,0 +1,71 @@ +using System; +using Xunit; + +namespace Notation.Plugin.Protocol.Tests +{ + public class DescribeKeyTests + { + [Fact] + public void DescribeKeyRequest_ValidParameters() + { + // Arrange + string contractVersion = "1.0"; + string keyId = "test-key-id"; + + // Act + DescribeKeyRequest request = new DescribeKeyRequest(contractVersion, keyId); + + // Assert + Assert.Equal(contractVersion, request.ContractVersion); + Assert.Equal(keyId, request.KeyId); + } + + [Theory] + [InlineData(null, "test-key-id")] + [InlineData("", "test-key-id")] + [InlineData("1.0", null)] + [InlineData("1.0", "")] + public void DescribeKeyRequest_InvalidParameters_ThrowsException(string contractVersion, string keyId) + { + // Act & Assert + Assert.Throws(() => new DescribeKeyRequest(contractVersion, keyId)); + } + + [Fact] + public void DescribeKeyRequest_UnsupportedContractVersion_ThrowsException() + { + // Arrange + string contractVersion = "2.0"; + string keyId = "test-key-id"; + + // Act & Assert + Assert.Throws(() => new DescribeKeyRequest(contractVersion, keyId)); + } + + [Fact] + public void DescribeKeyResponse_ValidParameters() + { + // Arrange + string keyId = "test-key-id"; + string keySpec = "RSA-2048"; + + // Act + DescribeKeyResponse response = new DescribeKeyResponse(keyId, keySpec); + + // Assert + Assert.Equal(keyId, response.KeyId); + Assert.Equal(keySpec, response.KeySpec); + } + + [Theory] + [InlineData(null, "RSA-2048")] + [InlineData("", "RSA-2048")] + [InlineData("test-key-id", null)] + [InlineData("test-key-id", "")] + public void DescribeKeyResponse_InvalidParameters_ThrowsException(string keyId, string keySpec) + { + // Act & Assert + Assert.Throws(() => new DescribeKeyResponse(keyId, keySpec)); + } + } +} diff --git a/Notation.Plugin.AzureKeyVault.Tests/Protocol/ErrorTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Protocol/ErrorTests.cs new file mode 100644 index 00000000..aa10dc8f --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/Protocol/ErrorTests.cs @@ -0,0 +1,82 @@ +using System; +using System.IO; +using Xunit; + +namespace Notation.Plugin.Protocol.Tests +{ + [Collection(nameof(OutputTestCollectionDefinition))] + public class ErrorAndExceptionTests + { + [Fact] + public void PluginException_ValidParameters() + { + // Arrange + string message = "Test error message"; + string code = "TEST_ERROR_CODE"; + + // Act + PluginException exception = new PluginException(message, code); + + // Assert + Assert.Equal(message, exception.Message); + Assert.Equal(code, exception.Code); + } + + [Fact] + public void PluginException_DefaultErrorCode() + { + // Arrange + string message = "Test error message"; + + // Act + PluginException exception = new PluginException(message); + + // Assert + Assert.Equal(message, exception.Message); + Assert.Equal(Error.ERROR, exception.Code); + } + + [Fact] + public void ValidationException_ValidParameters() + { + // Arrange + string message = "Test validation error message"; + + // Act + ValidationException exception = new ValidationException(message); + + // Assert + Assert.Equal(message, exception.Message); + Assert.Equal(Error.VALIDATION_ERROR, exception.Code); + } + + [Fact] + public void PrintError_WritesToStandardError() + { + // Arrange + string errorCode = "TEST_ERROR_CODE"; + string errorMessage = "Test error message"; + string expectedOutput = "{\"errorCode\":\"TEST_ERROR_CODE\",\"errorMessage\":\"Test error message\"}"; + + // Redirect standard error output + StringWriter stringWriter = new StringWriter(); + TextWriter originalStdErr = Console.Error; + Console.SetError(stringWriter); + + try + { + // Act + Error.PrintError(errorCode, errorMessage); + + // Assert + string actualOutput = stringWriter.ToString().Trim(); + Assert.Equal(expectedOutput, actualOutput); + } + finally + { + // Restore original standard error output + Console.SetError(originalStdErr); + } + } + } +} \ No newline at end of file diff --git a/Notation.Plugin.AzureKeyVault.Tests/Protocol/GenerateSignatureTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Protocol/GenerateSignatureTests.cs new file mode 100644 index 00000000..d81f74bb --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/Protocol/GenerateSignatureTests.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using Xunit; + +namespace Notation.Plugin.Protocol.Tests +{ + public class GenerateSignatureTests + { + [Fact] + public void GenerateSignatureRequest_ThrowsArgumentNullException_WhenFieldsAreEmptyOrNull() + { + // Arrange + string contractVersion = "test-version"; + string keyId = "test-key-id"; + Dictionary pluginConfig = new Dictionary { { "key1", "value1" } }; + string keySpec = "test-key-spec"; + string hashAlgorithm = "SHA256"; + byte[] payload = new byte[] { 1, 2, 3, 4, 5 }; + + // Assert + Assert.Throws(() => new GenerateSignatureRequest(string.Empty, keyId, pluginConfig, keySpec, hashAlgorithm, payload)); + Assert.Throws(() => new GenerateSignatureRequest(contractVersion, string.Empty, pluginConfig, keySpec, hashAlgorithm, payload)); + Assert.Throws(() => new GenerateSignatureRequest(contractVersion, keyId, pluginConfig, string.Empty, hashAlgorithm, payload)); + Assert.Throws(() => new GenerateSignatureRequest(contractVersion, keyId, pluginConfig, keySpec, string.Empty, payload)); + Assert.Throws(() => new GenerateSignatureRequest(contractVersion, keyId, pluginConfig, keySpec, hashAlgorithm, new byte[0])); + } + + [Fact] + public void GenerateSignatureResponse_ThrowsArgumentNullException_WhenFieldsAreEmptyOrNull() + { + // Arrange + string keyId = "test-key-id"; + byte[] signature = new byte[] { 1, 2, 3, 4, 5 }; + string signingAlgorithm = "RSA-PSS"; + List certificateChain = new List { new byte[] { 6, 7, 8, 9, 10 } }; + + // Assert + Assert.Throws(() => new GenerateSignatureResponse(string.Empty, signature, signingAlgorithm, certificateChain)); + Assert.Throws(() => new GenerateSignatureResponse(keyId, new byte[0], signingAlgorithm, certificateChain)); + Assert.Throws(() => new GenerateSignatureResponse(keyId, signature, string.Empty, certificateChain)); + Assert.Throws(() => new GenerateSignatureResponse(keyId, signature, signingAlgorithm, new List())); + } + } +} diff --git a/Notation.Plugin.AzureKeyVault.Tests/Protocol/GetMetadataResponseTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Protocol/GetMetadataResponseTests.cs new file mode 100644 index 00000000..0843e5b5 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/Protocol/GetMetadataResponseTests.cs @@ -0,0 +1,30 @@ +using Xunit; + +namespace Notation.Plugin.Protocol.Tests +{ + public class GetMetadataResponseTests + { + [Fact] + public void GetMetadataResponse_CreatesInstance_WithCorrectValues() + { + // Arrange + string name = "Test Plugin"; + string description = "A test plugin for Notation"; + string version = "1.0.0"; + string url = "https://github.com/example/test-plugin"; + string[] supportedContractVersions = new[] { "1.0" }; + string[] capabilities = new[] { "describe-key", "generate-signature" }; + + // Act + GetMetadataResponse response = new GetMetadataResponse(name, description, version, url, supportedContractVersions, capabilities); + + // Assert + Assert.Equal(name, response.Name); + Assert.Equal(description, response.Description); + Assert.Equal(version, response.Version); + Assert.Equal(url, response.Url); + Assert.Equal(supportedContractVersions, response.SupportedContractVersions); + Assert.Equal(capabilities, response.Capabilities); + } + } +} diff --git a/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs new file mode 100644 index 00000000..6af88699 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/Protocol/KeySpecTests.cs @@ -0,0 +1,44 @@ +using System; +using Xunit; + +namespace Notation.Plugin.Protocol.Tests +{ + public class KeySpecTests + { + [Theory] + [InlineData(KeyType.RSA, 2048, "RSA-2048", "RSASSA-PSS-SHA-256")] + [InlineData(KeyType.RSA, 3072, "RSA-3072", "RSASSA-PSS-SHA-384")] + [InlineData(KeyType.RSA, 4096, "RSA-4096", "RSASSA-PSS-SHA-512")] + [InlineData(KeyType.EC, 256, "EC-256", "ECDSA-SHA-256")] + [InlineData(KeyType.EC, 384, "EC-384", "ECDSA-SHA-384")] + [InlineData(KeyType.EC, 521, "EC-521", "ECDSA-SHA-512")] + public void KeySpec_EncodeKeySpecAndToSigningAlgorithm_ReturnsCorrectValues(KeyType keyType, int size, string expectedKeySpec, string expectedSigningAlgorithm) + { + // Arrange + KeySpec keySpec = new KeySpec(keyType, size); + + // Act + string encodedKeySpec = keySpec.EncodeKeySpec(); + string signingAlgorithm = keySpec.ToSigningAlgorithm(); + + // Assert + Assert.Equal(expectedKeySpec, encodedKeySpec); + Assert.Equal(expectedSigningAlgorithm, signingAlgorithm); + } + + [Theory] + [InlineData(KeyType.RSA, 1024)] + [InlineData(KeyType.RSA, 3070)] + [InlineData(KeyType.EC, 128)] + [InlineData(KeyType.EC, 500)] + public void KeySpec_EncodeKeySpecAndToSigningAlgorithm_ThrowsArgumentExceptionForInvalidSizes(KeyType keyType, int size) + { + // Arrange + KeySpec keySpec = new KeySpec(keyType, size); + + // Act & Assert + Assert.Throws(() => keySpec.EncodeKeySpec()); + Assert.Throws(() => keySpec.ToSigningAlgorithm()); + } + } +} diff --git a/Notation.Plugin.AzureKeyVault.Tests/Protocol/PluginIOTests.cs b/Notation.Plugin.AzureKeyVault.Tests/Protocol/PluginIOTests.cs new file mode 100644 index 00000000..09ec1187 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/Protocol/PluginIOTests.cs @@ -0,0 +1,67 @@ +using System; +using System.IO; +using Xunit; + +namespace Notation.Plugin.Protocol.Tests +{ + [CollectionDefinition(nameof(OutputTestCollectionDefinition), DisableParallelization = true)] + public class OutputTestCollectionDefinition { } + + [Collection(nameof(OutputTestCollectionDefinition))] + public class PluginIOTests + { + + [Fact] + public void ReadInput_ReturnsCorrectString() + { + // Arrange + const string expectedInput = "{\"test\":\"value\"}"; + using var stringReader = new StringReader(expectedInput); + Console.SetIn(stringReader); + + // Act + string actualInput = PluginIO.ReadInput(); + + // Assert + Assert.Equal(expectedInput, actualInput); + } + + [Fact] + public void ReadInput_ThrowsValidationExceptionOnEmptyInput() + { + // Arrange + using var stringReader = new StringReader(string.Empty); + Console.SetIn(stringReader); + + // Act & Assert + Assert.Throws(() => PluginIO.ReadInput()); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void WriteOutput_WritesCorrectOutput(bool stderr) + { + // Arrange + var obj = new { test = "value" }; + const string expectedOutput = "{\"test\":\"value\"}\n"; + + using var stringWriter = new StringWriter(); + if (stderr) + { + Console.SetError(stringWriter); + } + else + { + Console.SetOut(stringWriter); + } + + // Act + PluginIO.WriteOutput(obj, stderr); + + // Assert + string actualOutput = stringWriter.ToString(); + Assert.Equal(expectedOutput, actualOutput); + } + } +} diff --git a/Notation.Plugin.AzureKeyVault.Tests/TestData/cert_chain.pem b/Notation.Plugin.AzureKeyVault.Tests/TestData/cert_chain.pem new file mode 100644 index 00000000..54eabb97 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/TestData/cert_chain.pem @@ -0,0 +1,39 @@ +-----BEGIN CERTIFICATE----- +MIIDOTCCAiGgAwIBAgIBAjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdUZXN0 +IENBMCAXDTIzMDUwNDA3MDIwNloYDzIxMjMwNDEwMDcwMjA2WjBDMREwDwYDVQQK +Ewhub3RhdGlvbjELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMRQwEgYDVQQDEwtU +ZXN0LVNpZ25lcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOZenQqj +cimpyADt540GvlXsAwYqAyFpXmJDVsWImNMvHt+kaWabK1C5HJjTlXqGE9wIg1oY +YG20pqbFKpPwULTBA7AkZvQipvMbkrqp9fJQUDi32HBLM4yIp1rYWciHIlKnv/Rk +tSbtNxg6ES50xLJl/V0YFwgxT9JayVYk6D5BEEgXffpZsMUtuGbRe0QsHED09jaX +onCvSV+sIpn0zAF4ahcWRseoTtfLAUfk5cJpFqwYvf3eIfvjWx7fvltpcEjfBDTj +8TxPXNlst9lgn3X5z2dr2dbltXOgNzX/NfLh7MbAe3uM0AE5fkrQ4CoIm/9tFuhT +nEv28Mk3WJQquX0CAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG +CCsGAQUFBwMDMB0GA1UdDgQWBBTyceghcpaJ6QYyDNhHjIq2UkWmYzAfBgNVHSME +GDAWgBRRmku1TQfBHTqI6paqiKR40uhYIDANBgkqhkiG9w0BAQsFAAOCAQEAlzNY +LsYcV3hk22ZDFzGj8mZg6TjhHaejMgfOEm88UAUSjeHLaathV38sYHfqTwPwHjtf +lGGoSqzhiWasQ4Le4wRPraD/kFhRMRiUJ3YmW9EwwpXRDc7YexypbqVIKr/48gb3 +oMtUMYEarYlAOfWfMaMPHvXBlAIOq1oGLtuZ12doNJ3NqyqPoyLXNyjwYETnq+I/ +6qZQ4R1prdOwCdBuay6vn4lSrD45WxdjdWTDpAbYoT6voje+nHtH21EIxo1iWMpD +jzwp93CVbVxQnVf+dvSthHrUDPah+WPQ8yu4qhITxOrrAggCzBee3sK6WknERShf +aIZK0TY5G9C8rxUIhg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDFzCCAf+gAwIBAgIUcXSzPd52oq3cTzlLBJOoO7fiprkwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAgFw0yMzA1MDQwNzAwNTVaGA8yMTIzMDQx +MDA3MDA1NVowEjEQMA4GA1UEAwwHVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAJoVdivOR5V/Qot+h8rwuHxYF4T0uWFF3XrIqJ3BIw7Ts8Ev +6EyIzc2oHlGOv5aM3IcW4Adn5bZgT0kPSi4ZAyxUVHG9o2TeeGIN6q8skrzP7316 +hnB92shXYCgqXwo3KP5uKhbNPRTQ7OVwhGQqLaVrJXLw9rddAnLLG9AtkiuCCi+b +U7ytPeq+vsP04BX1EKMuuErOt+TJLjJ7caYN6sobjmWEIrLcB31rPNuHBGJXXLYP +LXIJ/IcnDRIeq8M28QEOiEKpIb+OR6nojnkJG2zk14RCzrM+TrudPsE9PGUojYOL +qD5iKvexVLkum2zbYEOorl6q9BK+IL88m3Yr6j0CAwEAAaNjMGEwHQYDVR0OBBYE +FFGaS7VNB8EdOojqlqqIpHjS6FggMB8GA1UdIwQYMBaAFFGaS7VNB8EdOojqlqqI +pHjS6FggMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgIEMA0GCSqGSIb3 +DQEBCwUAA4IBAQCKJsj78P6r6+NFDHfrKC730JDUeThokcYKVqSLOKm1Sc0gEAx2 +wM88fZay2LRTisynt0qFxwUFcrPqlXJeob7bI+aJRGBEqyY/zDkycpEaOyHJFpiN +elrfpQ1bVjTgOpUqmG21URMenxCApbbrBiCeNmeHcIsL/MGLAU2UL1rhjG6NrZLE +IK/qRDNmDdntvn8SOHBg9nsc4u7OkNryKCTfqGwkB2TCNWB40DM6akPSRb5dVh/P +viqpIeyPYMHw+yvs74s6/xTim9/Jy9MROScurGxFb9Vrby7gjd+41WuGf1hUBQ7S +F/83FanddfSmtN/tyCz65FhyVyQJPT9oZATK +-----END CERTIFICATE----- diff --git a/Notation.Plugin.AzureKeyVault.Tests/TestData/cert_chain.pfx b/Notation.Plugin.AzureKeyVault.Tests/TestData/cert_chain.pfx new file mode 100644 index 00000000..1a2bff01 Binary files /dev/null and b/Notation.Plugin.AzureKeyVault.Tests/TestData/cert_chain.pfx differ diff --git a/Notation.Plugin.AzureKeyVault.Tests/TestData/dsa_2048.crt b/Notation.Plugin.AzureKeyVault.Tests/TestData/dsa_2048.crt new file mode 100644 index 00000000..ef5cc91f --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/TestData/dsa_2048.crt @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEyDCCBHYCFBmdP+PmCiki8H8/adVdqQvcXn8nMAsGCWCGSAFlAwQDAjBzMQsw +CQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZy +YW5jaXNjbzETMBEGA1UECgwKTXkgQ29tcGFueTELMAkGA1UECwwCSVQxFTATBgNV +BAMMDG15ZG9tYWluLmNvbTAgFw0yMzA1MDQwNjQ3MDZaGA8yMTIzMDQxMDA2NDcw +NlowczELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xEzARBgNVBAoMCk15IENvbXBhbnkxCzAJBgNVBAsMAklU +MRUwEwYDVQQDDAxteWRvbWFpbi5jb20wggNDMIICNQYHKoZIzjgEATCCAigCggEB +AJlRaewadUWKtOl+C6G9w2+htpGWZ+wDWirbqg9iB+871yX/gskoJgI+BOxTKFqz +8Ekzd3iYbt7MQshacpydTHCVuiFvxVgA7w47D+uiG782gXBkHwQFZvIedjv/vBi8 +LptT1sausQNcJnLp9PDC5KWaVs9Mq+C1DpI/rMW+ytxiC3QDJgtSy0AmZeWwxTPU +0x96NvOigHZWZiOeVEyeh5xLXPIk8leUhsnZNunuR0lbDNAcVnVe8yWVCgJOjDh4 +fAufWB6F1htJ7ZLQrskhpYcyHJmUEd253u/dmxWjOgjFmBXo2jc4rdUpffrTo/6o +n7kE27pLx/k6WiosIQIK1d8CHQCr5ChTU9JMSO2RYkemsCp7n9E8xZq167eR1E89 +AoIBAHjbn4AdQDRmcJMkyJ1gv9fRh3+xHfaj4hxodAjQxBTYSe0mcq8uxHP6rbJ8 +lDjgWhol9D6a95pWSZdKkGJcAjgBGJlgIqm2VHNHFLv+bZq1xmuadBSADX6n2cuR +Avk5dFgKANykv8QZNSfaPc+35EzlaLjrQ1cg7FUdm8E1BPRKXx3co29igVXNNdxc +SovomL6hwq9SQ7pkXPuK7+trcjkt4PA31z47zqzKIOqZlE8Y4DCWAZcrTDKkOP6v +kafM2VSMy4hZrRIqxadFK5jhePcBXXmroSOqW//1nesAsMaS63McJeEFQnCmX9QY +azh4b/EXIUZY2N0ZXGazkFEZgS0DggEGAAKCAQEAkwSTobUHxXDB6tNCyv39+U1A +mPA/3BXsMcgMDuA6FUfoUM3jPwgwnFqQpyKdILSbi5NVqsdAoNyXL21m5buiFjCM +y0e8dSyNq340dow5PQxOkvqidtwpyzQR7Jydz5hLo4adLadbAETu4x2y/SAmitF/ +9wyGTIhWocRGrVSI4wPGQBayEBlSIbl63y9K/4hPFcQsHJWvItdloq5hdGwvS2MK +ePmVpCFZdlmTztQZQC09ScttNp2bqGWU+zQmCCX1nVf20rLGBdEynV9ABLAytVoJ +Y/VYpe/gF49bimh6xe+bmt1nZLbSWv09uYBsk8XThkG1OvIDXgu2TfHXP9hp6DAL +BglghkgBZQMEAwIDPwAwPAIcaId5beZDGpXA3yYs7r5hx4zhWm8atPCH4oWcIQIc +Jjx7VjeeNAY98h8eHj/g/BOrSX+gGOCJ//GGuA== +-----END CERTIFICATE----- diff --git a/Notation.Plugin.AzureKeyVault.Tests/TestData/ec_163.crt b/Notation.Plugin.AzureKeyVault.Tests/TestData/ec_163.crt new file mode 100644 index 00000000..ae21d4e6 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/TestData/ec_163.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBsjCCAXACFDrgivxnY3RMpgoqC3zy2SxxaPXuMAoGCCqGSM49BAMCMHMxCzAJ +BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJh +bmNpc2NvMRMwEQYDVQQKDApNeSBDb21wYW55MQswCQYDVQQLDAJJVDEVMBMGA1UE +AwwMbXlkb21haW4uY29tMCAXDTIzMDUwNDA2NDI1OFoYDzIxMjMwNDEwMDY0MjU4 +WjBzMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN +U2FuIEZyYW5jaXNjbzETMBEGA1UECgwKTXkgQ29tcGFueTELMAkGA1UECwwCSVQx +FTATBgNVBAMMDG15ZG9tYWluLmNvbTBAMBAGByqGSM49AgEGBSuBBAABAywABAPe +31H+GKMpeGSRyrza2bCnCc+raQZtQ2kaTIDWEkHovnqPst0kN+yFOzAKBggqhkjO +PQQDAgMwADAtAhUDn15TovwKqxFYATKEjm/5vJ4IV/YCFGFe98554dipD3Rdur1m +2hXgCzOM +-----END CERTIFICATE----- diff --git a/Notation.Plugin.AzureKeyVault.Tests/TestData/ec_256.crt b/Notation.Plugin.AzureKeyVault.Tests/TestData/ec_256.crt new file mode 100644 index 00000000..215b6567 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/TestData/ec_256.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB4zCCAYkCFFdhNzqKhDNB+mkDRyDUgnMNZ3XuMAoGCCqGSM49BAMCMHMxCzAJ +BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJh +bmNpc2NvMRMwEQYDVQQKDApNeSBDb21wYW55MQswCQYDVQQLDAJJVDEVMBMGA1UE +AwwMbXlkb21haW4uY29tMCAXDTIzMDUwNDA2NDI1OFoYDzIxMjMwNDEwMDY0MjU4 +WjBzMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN +U2FuIEZyYW5jaXNjbzETMBEGA1UECgwKTXkgQ29tcGFueTELMAkGA1UECwwCSVQx +FTATBgNVBAMMDG15ZG9tYWluLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA +BI20AKm1zyeNoGOTAcq6Lj/LFrMYe/UQ8HNSdgXgRJ7JnmMe65iu+g5VDpfth6OU +5qlhPDSaPJtPePiOacnnWeYwCgYIKoZIzj0EAwIDSAAwRQIhAJfP8NTEvNkizHAc +PRIrDhQNz95uvv2lkBO2JoZUwni1AiBRg/uwNTHt+7Iyx0/wT645RLVmA1XdHElQ +ZsMvasCpQw== +-----END CERTIFICATE----- diff --git a/Notation.Plugin.AzureKeyVault.Tests/TestData/ec_384.crt b/Notation.Plugin.AzureKeyVault.Tests/TestData/ec_384.crt new file mode 100644 index 00000000..26fc9619 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/TestData/ec_384.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICHzCCAaYCFEHNB4yg2WucVv2GT3Droh9TxwWLMAoGCCqGSM49BAMCMHMxCzAJ +BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJh +bmNpc2NvMRMwEQYDVQQKDApNeSBDb21wYW55MQswCQYDVQQLDAJJVDEVMBMGA1UE +AwwMbXlkb21haW4uY29tMCAXDTIzMDUwNDA2NDI1OFoYDzIxMjMwNDEwMDY0MjU4 +WjBzMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN +U2FuIEZyYW5jaXNjbzETMBEGA1UECgwKTXkgQ29tcGFueTELMAkGA1UECwwCSVQx +FTATBgNVBAMMDG15ZG9tYWluLmNvbTB2MBAGByqGSM49AgEGBSuBBAAiA2IABH7N +rLRGi3vP+WT9dSCuY03dXbcF1laN4G7saBaNQJdEz/ucKkp8LkuwhUxKLuEjjsCp +Bbe/lWseIx8HcbeRuVDom5Az4fW3dPEzHaQunmvfx//0ErRhoAce2zW+USL9+TAK +BggqhkjOPQQDAgNnADBkAjAzbilUd+7HZ/1HCvdSiMimiN03U9bG0b4TmPKvshLz +JHVz8shFd3ZwjYJE9eJwhJwCMD8PNX95jP9Rj5wQQQlYhSEfoQOebTlodWbdrvIh +U2VtmodrB0iHum3APPc/bNdB3Q== +-----END CERTIFICATE----- diff --git a/Notation.Plugin.AzureKeyVault.Tests/TestData/ec_521.crt b/Notation.Plugin.AzureKeyVault.Tests/TestData/ec_521.crt new file mode 100644 index 00000000..d1fac87c --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/TestData/ec_521.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICazCCAcwCFEFLSmwCrUYqtQ7KeUZoJslTUszdMAoGCCqGSM49BAMCMHMxCzAJ +BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJh +bmNpc2NvMRMwEQYDVQQKDApNeSBDb21wYW55MQswCQYDVQQLDAJJVDEVMBMGA1UE +AwwMbXlkb21haW4uY29tMCAXDTIzMDUwNDA2NDI1OFoYDzIxMjMwNDEwMDY0MjU4 +WjBzMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN +U2FuIEZyYW5jaXNjbzETMBEGA1UECgwKTXkgQ29tcGFueTELMAkGA1UECwwCSVQx +FTATBgNVBAMMDG15ZG9tYWluLmNvbTCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAE +AeurhZNLqLR3ZYwH7nYIWbT+k5g47E2dBo5poSTsxH2B2lkwU/oz7FP/7FAOA8M1 +hMMvJ0MDN8K59JvTOtPUQNqOANJnEqrmloRr8W493Jwms9Mnk3PITiXDBXaM5msJ +DufZBhA4j8wG9xQlaRZcD+dBHKlckuVoAotgEfbsn6aIPQrdMAoGCCqGSM49BAMC +A4GMADCBiAJCAeeF8LX17OVhq0QYcR99k9VdQjg0M2FROUy2YDXmiaYsv71TgON0 +k1K2LZJu1wUn8BT1HZPSuYNIFK5b6wzYzJVXAkIAygRzkY8tiyAzR6DqoGHnSpnN +QuHh0qUBp56T8JgErZBpryfdl5GRhu+TNfJb8shf/8fGTlfIRuAlTMHqavqsyIk= +-----END CERTIFICATE----- diff --git a/Notation.Plugin.AzureKeyVault.Tests/TestData/expired_leaf.crt b/Notation.Plugin.AzureKeyVault.Tests/TestData/expired_leaf.crt new file mode 100644 index 00000000..8549521a --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/TestData/expired_leaf.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIUXGf4izZdJlBDh7DAvRivUChVe88wDQYJKoZIhvcNAQEL +BQAwRjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkRFMRUwEwYDVQQKDAxNeUNlcnQs +IEluYy4xEzARBgNVBAMMCm15Y2VydC5jb20wHhcNMjMwNDI3MDQwMjMxWhcNMjMw +NDI4MDQwMjMxWjBGMQswCQYDVQQGEwJVUzELMAkGA1UECAwCREUxFTATBgNVBAoM +DE15Q2VydCwgSW5jLjETMBEGA1UEAwwKbXljZXJ0LmNvbTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANVzV/+tCDpcUhFFS06icTo7wUVzrc7BxA/jvE8V +F7aMSPEktROKzbSCLx0vHhu3Ol6e4gm1PshMXuxflU2x3z1UNGoQWmumg7PQR5zY +LIVG7j001wOsRW82hvL8p0FrEyT0OaXrUoj0u0EJB6UrrzBMrdhFWVeGMwdNeuYL +RXm6DdlOKLrKswW6tnfBmYi5wUGy4+D7B2MJbMz/2MZWoAj5NbxrfzAFbr6Wgz+m +zmZ8cpVNmRJsoJ5kUCYpXuzTjFrvKjlUtowTv+X1WD6DgdXqniqYdtxT5xIjQxeH +ggiMBS6GqAe17cUS7rQYxx+lK5IgOZrxGlCamY1/cWAH8h8CAwEAAaNTMFEwHQYD +VR0OBBYEFH62QasR15Wks0QYolynMFBeLeY1MB8GA1UdIwQYMBaAFH62QasR15Wk +s0QYolynMFBeLeY1MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB +AA9Hyab+Zt1RAtym2fMWxjW7LRMNTD1IyakZPaEx3BUB8yyx2bGZ/xp63GJI4h+Z +5sarLVcq7pQNvVmzro0zzh7t2SZkuaVfAmt7v5PloByXalMtzQBieSCkjZgNLdjI +QOuG9VkzvTUfsChjgmDVG0gsUx8w2MgUyl5BmAqXB/cFRhXWUesDiXhmPHrm0Ctm +byANUWVQowuryPE7DJwASm6bwAIUQErXeQ6pLp+xkbIzX7Yw1/bth0MSSgNzB9Ka +czpsIeRP2geFwaOSdNZYjR+YqB+AdV9vXaR3qaehnguNZ7wWxtYDaK2OcxIOUUz8 +2n1N8dQBU+gGfGS1boPG0rE= +-----END CERTIFICATE----- diff --git a/Notation.Plugin.AzureKeyVault.Tests/TestData/leaf.crt b/Notation.Plugin.AzureKeyVault.Tests/TestData/leaf.crt new file mode 100644 index 00000000..efe6ed85 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/TestData/leaf.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDOTCCAiGgAwIBAgIBAjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdUZXN0 +IENBMCAXDTIzMDUwNDA3MDIwNloYDzIxMjMwNDEwMDcwMjA2WjBDMREwDwYDVQQK +Ewhub3RhdGlvbjELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMRQwEgYDVQQDEwtU +ZXN0LVNpZ25lcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOZenQqj +cimpyADt540GvlXsAwYqAyFpXmJDVsWImNMvHt+kaWabK1C5HJjTlXqGE9wIg1oY +YG20pqbFKpPwULTBA7AkZvQipvMbkrqp9fJQUDi32HBLM4yIp1rYWciHIlKnv/Rk +tSbtNxg6ES50xLJl/V0YFwgxT9JayVYk6D5BEEgXffpZsMUtuGbRe0QsHED09jaX +onCvSV+sIpn0zAF4ahcWRseoTtfLAUfk5cJpFqwYvf3eIfvjWx7fvltpcEjfBDTj +8TxPXNlst9lgn3X5z2dr2dbltXOgNzX/NfLh7MbAe3uM0AE5fkrQ4CoIm/9tFuhT +nEv28Mk3WJQquX0CAwEAAaNnMGUwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG +CCsGAQUFBwMDMB0GA1UdDgQWBBTyceghcpaJ6QYyDNhHjIq2UkWmYzAfBgNVHSME +GDAWgBRRmku1TQfBHTqI6paqiKR40uhYIDANBgkqhkiG9w0BAQsFAAOCAQEAlzNY +LsYcV3hk22ZDFzGj8mZg6TjhHaejMgfOEm88UAUSjeHLaathV38sYHfqTwPwHjtf +lGGoSqzhiWasQ4Le4wRPraD/kFhRMRiUJ3YmW9EwwpXRDc7YexypbqVIKr/48gb3 +oMtUMYEarYlAOfWfMaMPHvXBlAIOq1oGLtuZ12doNJ3NqyqPoyLXNyjwYETnq+I/ +6qZQ4R1prdOwCdBuay6vn4lSrD45WxdjdWTDpAbYoT6voje+nHtH21EIxo1iWMpD +jzwp93CVbVxQnVf+dvSthHrUDPah+WPQ8yu4qhITxOrrAggCzBee3sK6WknERShf +aIZK0TY5G9C8rxUIhg== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/Notation.Plugin.AzureKeyVault.Tests/TestData/root.crt b/Notation.Plugin.AzureKeyVault.Tests/TestData/root.crt new file mode 100644 index 00000000..ecd697ce --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/TestData/root.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFzCCAf+gAwIBAgIUcXSzPd52oq3cTzlLBJOoO7fiprkwDQYJKoZIhvcNAQEL +BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAgFw0yMzA1MDQwNzAwNTVaGA8yMTIzMDQx +MDA3MDA1NVowEjEQMA4GA1UEAwwHVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAJoVdivOR5V/Qot+h8rwuHxYF4T0uWFF3XrIqJ3BIw7Ts8Ev +6EyIzc2oHlGOv5aM3IcW4Adn5bZgT0kPSi4ZAyxUVHG9o2TeeGIN6q8skrzP7316 +hnB92shXYCgqXwo3KP5uKhbNPRTQ7OVwhGQqLaVrJXLw9rddAnLLG9AtkiuCCi+b +U7ytPeq+vsP04BX1EKMuuErOt+TJLjJ7caYN6sobjmWEIrLcB31rPNuHBGJXXLYP +LXIJ/IcnDRIeq8M28QEOiEKpIb+OR6nojnkJG2zk14RCzrM+TrudPsE9PGUojYOL +qD5iKvexVLkum2zbYEOorl6q9BK+IL88m3Yr6j0CAwEAAaNjMGEwHQYDVR0OBBYE +FFGaS7VNB8EdOojqlqqIpHjS6FggMB8GA1UdIwQYMBaAFFGaS7VNB8EdOojqlqqI +pHjS6FggMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgIEMA0GCSqGSIb3 +DQEBCwUAA4IBAQCKJsj78P6r6+NFDHfrKC730JDUeThokcYKVqSLOKm1Sc0gEAx2 +wM88fZay2LRTisynt0qFxwUFcrPqlXJeob7bI+aJRGBEqyY/zDkycpEaOyHJFpiN +elrfpQ1bVjTgOpUqmG21URMenxCApbbrBiCeNmeHcIsL/MGLAU2UL1rhjG6NrZLE +IK/qRDNmDdntvn8SOHBg9nsc4u7OkNryKCTfqGwkB2TCNWB40DM6akPSRb5dVh/P +viqpIeyPYMHw+yvs74s6/xTim9/Jy9MROScurGxFb9Vrby7gjd+41WuGf1hUBQ7S +F/83FanddfSmtN/tyCz65FhyVyQJPT9oZATK +-----END CERTIFICATE----- diff --git a/Notation.Plugin.AzureKeyVault.Tests/TestData/rsa_1024.crt b/Notation.Plugin.AzureKeyVault.Tests/TestData/rsa_1024.crt new file mode 100644 index 00000000..3e04c096 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/TestData/rsa_1024.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICajCCAdMCFFSmVSMkp1X9xf57Jh+VZ/5AKYUGMA0GCSqGSIb3DQEBCwUAMHMx +CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4g +RnJhbmNpc2NvMRMwEQYDVQQKDApNeSBDb21wYW55MQswCQYDVQQLDAJJVDEVMBMG +A1UEAwwMbXlkb21haW4uY29tMCAXDTIzMDUwNDA2MjYzMloYDzIxMjMwNDEwMDYy +NjMyWjBzMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UE +BwwNU2FuIEZyYW5jaXNjbzETMBEGA1UECgwKTXkgQ29tcGFueTELMAkGA1UECwwC +SVQxFTATBgNVBAMMDG15ZG9tYWluLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEAwB7iyxzVrZD0FG3vhnBtjOlVQMZgs12cuKNjXziFgB3NBlSaL1msSyGL +Bhu7O/I867a5vGkEF675rXzAMbcIjFkKSLuCvijlzjWAxo+iOG5zL+RYDbB9+cM5 +gbEoGfUV0zzkpA/f5v7eww4NXyZ6fjC5X27lySdTz6GI/00/IBECAwEAATANBgkq +hkiG9w0BAQsFAAOBgQCLDFZ+g4rmjpUYNQurzAU/gBQ1URRAxPAiKFmpbrWnO6GW +Pab/HQ/alksptpQfG9Dty5pONvvICVl/m2NqtFHTiVGDztEsNS2IXPQGsWOqAQPo +8jl+pFEykQLNoHZ/9DgoFhDllVLOhAXP+HkPDFMVdPpbAbFCbb/+D5xN6qK+aw== +-----END CERTIFICATE----- diff --git a/Notation.Plugin.AzureKeyVault.Tests/TestData/rsa_2048.crt b/Notation.Plugin.AzureKeyVault.Tests/TestData/rsa_2048.crt new file mode 100644 index 00000000..c0a93c29 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/TestData/rsa_2048.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbzCCAlcCFDZgpjL9UwQT1JLuOJ8hfcu6BFraMA0GCSqGSIb3DQEBCwUAMHMx +CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4g +RnJhbmNpc2NvMRMwEQYDVQQKDApNeSBDb21wYW55MQswCQYDVQQLDAJJVDEVMBMG +A1UEAwwMbXlkb21haW4uY29tMCAXDTIzMDUwNDA2MjYzMloYDzIxMjMwNDEwMDYy +NjMyWjBzMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UE +BwwNU2FuIEZyYW5jaXNjbzETMBEGA1UECgwKTXkgQ29tcGFueTELMAkGA1UECwwC +SVQxFTATBgNVBAMMDG15ZG9tYWluLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAJTxPEPsfGp0tn7p+qKzyehnDLNLJpyqzUq0/oaHgn0/Vay3sWl9 +ILIdb4PYMuSjCdNM91zqLCr00bjOauJsO64EREiKFquOy6D4XUs9Denf6dSS0XpM +9/h+KfhQK2w+12oLB7uwwS9bFlVFaomfyxoE83f76z6BKvqRJqshqFYYUZdn5KCb +tFORBh1EildvUJicnIIBIJxysgMJCK2iaShR8j1SNKsZ3t06nLqnH1Is5jYIA1Eu ++jVG5090xFB26wTy7WmG2KOT727dIqaddhv6UsGF18KqntgEjDwV7ROaaz0o1L/4 +TOzSQiucS5dx56OBtUPykWpYAAIJ5yxh5WsCAwEAATANBgkqhkiG9w0BAQsFAAOC +AQEADRKz5CQD7Ek1WrWF4MylMVd1KItxIgsQ0+lze/52DQ/Ycl61aBDaWQ70QhlA +KuBsLIEFJmIrhsWF4M3q1qY+Diu+uEMl6ZtW7JmzanUz4dNRmPjhWzh8aieghTAH +FqRtF62aR+L/pp1CLrYCOwvVEJTSr1th4pBX0W4Ax9xA08vIccq2IPo6HgibPxK5 ++Z7iGmrz7qAIaFj2jl4+d9MtJXFALffYz3HyKmzELPNLAbVsaA/t1YtfSCQX/mGU +80sn1HaMCdUmFz8WTcPRPlTTWSIQaS+LPtb0ZNONrISrHMD9pNWPil9yyIL4tCIk +CPVYHQnJdNHtEkbMFGbZCDP0Og== +-----END CERTIFICATE----- diff --git a/Notation.Plugin.AzureKeyVault.Tests/TestData/rsa_3072.crt b/Notation.Plugin.AzureKeyVault.Tests/TestData/rsa_3072.crt new file mode 100644 index 00000000..01d72be9 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/TestData/rsa_3072.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEbzCCAtcCFBAYi45tN/ReyWcgXVDLToaSARDjMA0GCSqGSIb3DQEBCwUAMHMx +CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4g +RnJhbmNpc2NvMRMwEQYDVQQKDApNeSBDb21wYW55MQswCQYDVQQLDAJJVDEVMBMG +A1UEAwwMbXlkb21haW4uY29tMCAXDTIzMDUwNDA2MjYzMloYDzIxMjMwNDEwMDYy +NjMyWjBzMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UE +BwwNU2FuIEZyYW5jaXNjbzETMBEGA1UECgwKTXkgQ29tcGFueTELMAkGA1UECwwC +SVQxFTATBgNVBAMMDG15ZG9tYWluLmNvbTCCAaIwDQYJKoZIhvcNAQEBBQADggGP +ADCCAYoCggGBALmKZDy6oAlq6Q+OOen0Q8d3VoW0Axv2/bNBCQX+ETzdC/f17Eqm +8TspHNFw/4YP4W8ChWcf265E2KMOOsf2AbYm1hbFthzsDmysPaWHcOCfyIzQtfiF +hboI57X+/wWY2bKY47aixZpSWS/hkcP5Cpe7QsLVFcSCKBPsNisijSLcYcFBcpt/ +lMsvGDvMF/ytn9t0+XFnuiNMteZdrbZRNAf6kmn4EgAnr4VrjtIqqrIL+cZk6jyF +5D4JJoKD6GeceCpTkb8s5yJMxcWyzIZot01V29aTrAKGNsDd2YfGaIHWvKTIqflk +DBTRGSVgO1HWmkbjcxfOybi154ebUGr7vfLrDpZyx4HkG8bIGBsC9Wk9iK84YaYb +SJ/qndOYMZDaIEOc4kGZgKdQPFF5JzRQphKkOYNn/odUk8kgAxR32jhud1Y+nEyX +4FVrJAOSBabqmcKZ8vJHWipxG0yZ6f3keSzeyXoOje5zUsygsB9DUgopwQq15sA/ +1iSlmgIHWWIysQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBgQBIPvsqwoVybcYVEAVh +ixcbe4HcHjYLSe6RpFf9Om5jraN/c0jnyW16gnY5WIA2oECZ/wMtB87eLYC07e/U +muqDC2LcHqKDdo/G4cRLzS6NFIX3fWn7NzIHR+VPowDyyeuzroymx3GKQEUo62nI +y6cBMHa4f/5Z+F+xBYOIpZ0ww/lEyftFoOm6o4IeFqjnSZpxSi3XWg/4DUF2YBrM +yWBa3Kyqe9nr8cP865RqsgHYcALmkliCeDi6dAqtt30dkAoMCLkvE5pX2nLX1oNU +fn7bchGGsdQb9iXXC7Y+MZTWfPShVLmzexluZ5mznAVEtS4JNjYO8beUsFgCv6v0 +GhLHf18MHR5O8JM4gcXB8uTIZYXqrZRfEpgqYvdZ9i7eiSboPOEWxBDZFb92E1Ax +tZ0jAeC4XLIZ/lUSTv88P97HoOzjmEhKLrOgPOJ/sRNE1y7vZxfyBY0WzKTl4A11 +RntiV/ZF2KFCbFV+Tpbsc8q/c+hPF/0Db1BwOPHY3w2bPxc= +-----END CERTIFICATE----- diff --git a/Notation.Plugin.AzureKeyVault.Tests/TestData/rsa_4096.crt b/Notation.Plugin.AzureKeyVault.Tests/TestData/rsa_4096.crt new file mode 100644 index 00000000..f06478e9 --- /dev/null +++ b/Notation.Plugin.AzureKeyVault.Tests/TestData/rsa_4096.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFbzCCA1cCFDv0RD7o1zyf3ms2cLK1tdQcO/9XMA0GCSqGSIb3DQEBCwUAMHMx +CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4g +RnJhbmNpc2NvMRMwEQYDVQQKDApNeSBDb21wYW55MQswCQYDVQQLDAJJVDEVMBMG +A1UEAwwMbXlkb21haW4uY29tMCAXDTIzMDUwNDA2MjYzNFoYDzIxMjMwNDEwMDYy +NjM0WjBzMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UE +BwwNU2FuIEZyYW5jaXNjbzETMBEGA1UECgwKTXkgQ29tcGFueTELMAkGA1UECwwC +SVQxFTATBgNVBAMMDG15ZG9tYWluLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBALhIV5E2V8lPoIkurStVBoAsibX+UuPdgkyhlVoJEbDl8EcqbJ3D +/tx4PkoWIff0ofzyTDb+xHQqBzAH2mDxYyRYtYHlzkArl3jPKG9gXbk2Ze5GPNKr +qgFrhTHApzVljYfVFlp5Y+jTO8KZO+HmSldsFAt4XdqLNCtlD+7iU+bX1RVWuL1p +KO07qViiBycLQSUfa2UqbBUpoYG1T5mjPNoy1SDQQnrhUFNgdFBfjoQwWhGtNhHp +hW5Kl/OprIvjc55rOd55mFOZ66YQbRoGl9auktbI4NYWi1MQqOND5gqgJRd5an09 +TveSvkB/BOnGAbPSmeJkrhkbJkQE9zcgwtoL8ViElAD2f3BkGPnSMvLTywfJUc8k +ypDIUzteSoMqzQ6Pt13No3GqEzxdTKOFARPvA0FwjqGCsy0YvX3Ae27lmk66+rBG +dQRxZpraBeMBbFl2CyqPJyYvHIo2DlhPDp5LH/DKZf0Zq6ijahetRaY1GFkue+W4 +rAe0XmjbWFeUQWE8rHvWh1nj5m24UE33Q9z3xw3FhS6t+AipZsJxozVIRsi4n0D9 +QU1qj5axmBAcH+Pvj8A/jvTnfhWP9FSRp4Zeuj5hUjAP5B1gxzqQe8faxK/fMcUT +rwKQe5tN22AW8rrpR1nEq7hywHewhcF5xEPPC0Kt8aJJE0cEJaUdMCc7AgMBAAEw +DQYJKoZIhvcNAQELBQADggIBAJ5lU6yH7SszSr4jvn1OB0Y9HaSW7zPfEM0iNBYM +x1L/je4K3kdyzYySXdenHG+LeQUJhYohQksWHLjYBCurWdOJZb8RMztLfhzDDse5 +Sn55bbsP9dNMQ5JJFUdW0T3CDBsCQK63S7iwf8+MQOeUAfacfebvMSxZb3O26e4l +HQhH0PmSoNgJP3/9Bir0DP8MSamVnmEPGVLHQQyerJKLFUXck9WTqmG4RZzPv1Yq +JGfd6WddtuxyUNO0HZ8yB7BdX67/WijmmLTBuYPTnB09GLbtJ0FU/3ottRExswbU +ZRz9aGsvQJgF342mqcbT2J8hn0826Meoy4dFb0MbV8prXR0mtAHwjXsLBfALwagy +/ML+O85WwJxwayVlFrqWK4q9cd+vlW5VKC+2IzMuJpkTq1PbQQcoRmZSt6cx2u2C +xAqizMllt6J/oeaG4oiFdX0EXpeyH5CZqFm5eeyN3eY5c4X0oqVeCSqnLM/kSfHL +PXFQB/0Sby7rcmHsesYyVieVuyc4jeXlyUsp0u45IYn/xCMu0J9dlLviexe+97Fk +SEIA5R4pqJG9Ycd94xOEOvlE2hF7oN3614qXIsLz/iksNxPtiZjrzY3IZMPgrfxK +FiLJs2JtyzpY9nJjVU12j9Mye3M902xCA4PitY7u6wcx/EfPodvCtYsw7vf/bWLr +HBL9 +-----END CERTIFICATE----- diff --git a/Notation.Plugin.AzureKeyVault/Command/DescribeKey.cs b/Notation.Plugin.AzureKeyVault/Command/DescribeKey.cs index 0b7d4437..7930d2d8 100644 --- a/Notation.Plugin.AzureKeyVault/Command/DescribeKey.cs +++ b/Notation.Plugin.AzureKeyVault/Command/DescribeKey.cs @@ -1,6 +1,6 @@ using System.Text.Json; -using Notation.Plugin.Protocol; using Notation.Plugin.AzureKeyVault.Client; +using Notation.Plugin.Protocol; namespace Notation.Plugin.AzureKeyVault.Command { @@ -9,23 +9,41 @@ namespace Notation.Plugin.AzureKeyVault.Command /// public class DescribeKey : IPluginCommand { + private DescribeKeyRequest _request; + private IKeyVaultClient _keyVaultClient; private const string invalidInputError = "Invalid input. The valid input format is '{\"contractVersion\":\"1.0\",\"keyId\":\"https://.vault.azure.net///\"}'"; - public async Task RunAsync(string inputJson) + /// + /// Constructor to create DescribeKey object from JSON string. + /// + public DescribeKey(string inputJson) { // Deserialize JSON string to DescribeKeyRequest object - DescribeKeyRequest? request = JsonSerializer.Deserialize(inputJson); + var request = JsonSerializer.Deserialize(inputJson); if (request == null) { throw new ValidationException(invalidInputError); } + this._request = request; + this._keyVaultClient = new KeyVaultClient(request.KeyId); + } + /// + /// Constructor for unit test. + /// + public DescribeKey(DescribeKeyRequest request, IKeyVaultClient keyVaultClient) + { + this._request = request; + this._keyVaultClient = keyVaultClient; + } + + public async Task RunAsync() + { // Get certificate from Azure Key Vault - var akvClient = new KeyVaultClient(request.KeyId); - var cert = await akvClient.GetCertificateAsync(); + var cert = await _keyVaultClient.GetCertificateAsync(); return new DescribeKeyResponse( - keyId: request.KeyId, + keyId: _request.KeyId, keySpec: cert.KeySpec().EncodeKeySpec()); } } diff --git a/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs b/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs index 5a1289ec..e89622aa 100644 --- a/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs +++ b/Notation.Plugin.AzureKeyVault/Command/GenerateSignature.cs @@ -11,37 +11,55 @@ namespace Notation.Plugin.AzureKeyVault.Command /// public class GenerateSignature : IPluginCommand { - public async Task RunAsync(string inputJson) + private GenerateSignatureRequest _request; + private IKeyVaultClient _keyVaultClient; + + /// + /// Constructor to create GenerateSignature object from JSON string. + /// + public GenerateSignature(string inputJson) { // Parse the input - GenerateSignatureRequest? input = JsonSerializer.Deserialize(inputJson); - if (input == null) + var request = JsonSerializer.Deserialize(inputJson); + if (request == null) { throw new ValidationException("Invalid input"); } + this._request = request; + this._keyVaultClient = new KeyVaultClient(request.KeyId); + } - var akvClient = new KeyVaultClient(input.KeyId); + /// + /// Constructor for unit test. + /// + public GenerateSignature(GenerateSignatureRequest request, IKeyVaultClient keyVaultClient) + { + this._request = request; + this._keyVaultClient = keyVaultClient; + } + public async Task RunAsync() + { // Obtain the certificate chain X509Certificate2Collection certBundle; X509Certificate2 leafCert; - if (input.PluginConfig?.TryGetValue("ca_certs", out var certBundlePath) == true) + if (_request.PluginConfig?.TryGetValue("ca_certs", out var certBundlePath) == true) { // Obtain the certificate bundle from file // (including the intermediate and root certificates). certBundle = CertificateBundle.Create(certBundlePath); // obtain the leaf certificate from Azure Key Vault - leafCert = await akvClient.GetCertificateAsync(); + leafCert = await _keyVaultClient.GetCertificateAsync(); } - else if (input.PluginConfig?.TryGetValue("as_secret", out var asSecret) == true && asSecret.Equals("true", StringComparison.OrdinalIgnoreCase)) + else if (_request.PluginConfig?.TryGetValue("as_secret", out var asSecret) == true && asSecret.Equals("true", StringComparison.OrdinalIgnoreCase)) { // Obtain the certificate chain from Azure Key Vault using // GetSecret permission. Ensure intermediate and root // certificates are merged into the Key Vault certificate to // retrieve the full chain. // reference: https://learn.microsoft.com//azure/key-vault/certificates/create-certificate-signing-request - var certificateChain = await akvClient.GetCertificateChainAsync(); + var certificateChain = await _keyVaultClient.GetCertificateChainAsync(); // the certBundle is the certificates start from the second one of certificateChain certBundle = new X509Certificate2Collection(certificateChain.Skip(1).ToArray()); @@ -53,17 +71,17 @@ public async Task RunAsync(string inputJson) { // only have the leaf certificate certBundle = new X509Certificate2Collection(); - leafCert = await akvClient.GetCertificateAsync(); + leafCert = await _keyVaultClient.GetCertificateAsync(); } // Extract KeySpec from the certificate var keySpec = leafCert.KeySpec(); // Sign - var signature = await akvClient.SignAsync(keySpec.ToKeyVaultSignatureAlgorithm(), input.Payload); + var signature = await _keyVaultClient.SignAsync(keySpec.ToKeyVaultSignatureAlgorithm(), _request.Payload); return new GenerateSignatureResponse( - keyId: input.KeyId, + keyId: _request.KeyId, signature: signature, signingAlgorithm: keySpec.ToSigningAlgorithm(), certificateChain: CertificateChain.Build(leafCert, certBundle)); diff --git a/Notation.Plugin.AzureKeyVault/Command/GetPluginMetadata.cs b/Notation.Plugin.AzureKeyVault/Command/GetPluginMetadata.cs index 8ad47a0f..4091d062 100644 --- a/Notation.Plugin.AzureKeyVault/Command/GetPluginMetadata.cs +++ b/Notation.Plugin.AzureKeyVault/Command/GetPluginMetadata.cs @@ -10,7 +10,7 @@ public partial class GetPluginMetadata : IPluginCommand public static readonly string Version; public static readonly string CommitHash; - public async Task RunAsync(string _) + public async Task RunAsync() { return await Task.FromResult(new GetMetadataResponse( name: "azure-kv", diff --git a/Notation.Plugin.AzureKeyVault/Command/IPluginCommand.cs b/Notation.Plugin.AzureKeyVault/Command/IPluginCommand.cs index 15c2baf4..8a29df45 100644 --- a/Notation.Plugin.AzureKeyVault/Command/IPluginCommand.cs +++ b/Notation.Plugin.AzureKeyVault/Command/IPluginCommand.cs @@ -3,7 +3,8 @@ namespace Notation.Plugin.AzureKeyVault.Command /// /// Interface for plugin commands. /// - public interface IPluginCommand{ - Task RunAsync(string inputJson); + public interface IPluginCommand + { + Task RunAsync(); } -} \ No newline at end of file +} diff --git a/Notation.Plugin.AzureKeyVault/KeyVault/KeyVaultClient.cs b/Notation.Plugin.AzureKeyVault/KeyVault/KeyVaultClient.cs index c3eab296..11821476 100644 --- a/Notation.Plugin.AzureKeyVault/KeyVault/KeyVaultClient.cs +++ b/Notation.Plugin.AzureKeyVault/KeyVault/KeyVaultClient.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using System.Security.Cryptography.X509Certificates; using Azure.Identity; using Azure.Security.KeyVault.Certificates; @@ -5,9 +6,28 @@ using Azure.Security.KeyVault.Secrets; using Notation.Plugin.Protocol; +[assembly: InternalsVisibleTo("Notation.Plugin.AzureKeyVault.Tests")] namespace Notation.Plugin.AzureKeyVault.Client { - class KeyVaultClient + public interface IKeyVaultClient + { + /// + /// Sign the payload with the specified algorithm. + /// + public Task SignAsync(SignatureAlgorithm algorithm, byte[] payload); + + /// + /// Get the certificate from KeyVault. + /// + public Task GetCertificateAsync(); + + /// + /// Get the certificate chain from KeyVault. + /// + public Task GetCertificateChainAsync(); + } + + public class KeyVaultClient : IKeyVaultClient { /// /// A helper record to store KeyVault metadata. @@ -15,20 +35,26 @@ class KeyVaultClient private record KeyVaultMetadata(string KeyVaultUrl, string Name, string Version); // Certificate client (lazy initialization) - private Lazy _certificateClient; + // Protected for unit test + protected Lazy _certificateClient; // Cryptography client (lazy initialization) - private Lazy _cryptoClient; + protected Lazy _cryptoClient; // Secret client (lazy initialization) - private Lazy _secretClient; + protected Lazy _secretClient; // Error message for invalid input private const string INVALID_INPUT_ERROR_MSG = "Invalid input. The valid input format is '{\"contractVersion\":\"1.0\",\"keyId\":\"https://.vault.azure.net///\"}'"; // Key name or certificate name - public string _name; + private string _name; // Key version or certificate version - public string _version; + private string _version; // Key identifier (e.g. https://.vault.azure.net/keys//) - public string _keyId; + private string _keyId; + + // Internal getters for unit test + internal string Name => _name; + internal string Version => _version; + internal string KeyId => _keyId; /// /// Constructor to create AzureKeyVault object from keyVaultUrl, name diff --git a/Notation.Plugin.AzureKeyVault/Program.cs b/Notation.Plugin.AzureKeyVault/Program.cs index a2c56033..3219afc4 100644 --- a/Notation.Plugin.AzureKeyVault/Program.cs +++ b/Notation.Plugin.AzureKeyVault/Program.cs @@ -31,6 +31,9 @@ static async Task ExecuteAsync(string[] args) return; } + // read the input + var inputJson = PluginIO.ReadInput(); + IPluginCommand? cmd = null; switch (args[0]) { @@ -38,20 +41,17 @@ static async Task ExecuteAsync(string[] args) cmd = new GetPluginMetadata(); break; case "describe-key": - cmd = new DescribeKey(); + cmd = new DescribeKey(inputJson); break; case "generate-signature": - cmd = new GenerateSignature(); + cmd = new GenerateSignature(inputJson); break; default: throw new ValidationException($"Invalid command: {args[0]}"); } - // read the input - var inputJson = PluginIO.ReadInput(); - // execute the command - var resp = await cmd.RunAsync(inputJson); + var resp = await cmd.RunAsync(); // print the output PluginIO.WriteOutput(resp); diff --git a/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs b/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs index b993960f..a1a5f79c 100644 --- a/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs +++ b/Notation.Plugin.AzureKeyVault/Protocol/KeySpec.cs @@ -1,6 +1,3 @@ -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; - namespace Notation.Plugin.Protocol { ///