diff --git a/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/CertificateOperation.cs b/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/CertificateOperation.cs index 51f8c9d08a908..2b4513958f44e 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/CertificateOperation.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/CertificateOperation.cs @@ -13,6 +13,9 @@ namespace Azure.Security.KeyVault.Certificates /// public class CertificateOperation : Operation { + private const string CancelledStatus = "cancelled"; + private const string CompletedStatus = "completed"; + private readonly CertificateClient _client; private bool _completed; @@ -73,7 +76,7 @@ public override KeyVaultCertificateWithPolicy Value throw new InvalidOperationException("The operation was deleted so no value is available."); } - if (Properties.Status == "cancelled") + if (Properties.Status == CancelledStatus) { throw new OperationCanceledException("The operation was canceled so no value is available."); } @@ -110,6 +113,8 @@ public override ValueTask> WaitForComple /// The raw response of the poll operation. public override Response UpdateStatus(CancellationToken cancellationToken = default) { + using var _ = new UpdateStatusActivity(this); + if (!_completed) { Response pollResponse = _client.GetPendingCertificate(Properties.Name, cancellationToken); @@ -126,7 +131,7 @@ public override Response UpdateStatus(CancellationToken cancellationToken = defa } } - if (Properties.Status == "completed") + if (Properties.Status == CompletedStatus) { Response getResponse = _client.GetCertificate(Properties.Name, cancellationToken); @@ -136,7 +141,7 @@ public override Response UpdateStatus(CancellationToken cancellationToken = defa _completed = true; } - else if (Properties.Status == "cancelled") + else if (Properties.Status == CancelledStatus) { _completed = true; } @@ -158,6 +163,8 @@ public override Response UpdateStatus(CancellationToken cancellationToken = defa /// The raw response of the poll operation. public override async ValueTask UpdateStatusAsync(CancellationToken cancellationToken = default) { + using var _ = new UpdateStatusActivity(this); + if (!_completed) { Response pollResponse = await _client.GetPendingCertificateAsync(Properties.Name, cancellationToken).ConfigureAwait(false); @@ -174,7 +181,7 @@ public override async ValueTask UpdateStatusAsync(CancellationToken ca } } - if (Properties.Status == "completed") + if (Properties.Status == CompletedStatus) { Response getResponse = await _client.GetCertificateAsync(Properties.Name, cancellationToken).ConfigureAwait(false); @@ -184,7 +191,7 @@ public override async ValueTask UpdateStatusAsync(CancellationToken ca _completed = true; } - else if (Properties.Status == "cancelled") + else if (Properties.Status == CancelledStatus) { _completed = true; } @@ -261,5 +268,24 @@ public virtual async Task DeleteAsync(CancellationToken cancellationToken = defa Properties = response; } + + private class UpdateStatusActivity : IDisposable + { + private readonly CertificateOperation _operation; + + public UpdateStatusActivity(CertificateOperation operation) + { + _operation = operation; + + EventSource.BeginUpdateStatus(_operation.Properties); + } + + public void Dispose() + { + EventSource.EndUpdateStatus(_operation.Properties); + } + + private static CertificatesEventSource EventSource => CertificatesEventSource.Singleton; + } } } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/CertificatesEventSource.cs b/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/CertificatesEventSource.cs new file mode 100644 index 0000000000000..5ec8b46b7e837 --- /dev/null +++ b/sdk/keyvault/Azure.Security.KeyVault.Certificates/src/CertificatesEventSource.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics.Tracing; +using Azure.Core.Diagnostics; + +namespace Azure.Security.KeyVault.Certificates +{ + [EventSource(Name = EventSourceName)] + internal sealed class CertificatesEventSource : EventSource + { + internal const int BeginUpdateStatusEvent = 1; + internal const int EndUpdateStatusEvent = 2; + + private const string EventSourceName = "Azure-Security-KeyVault-Certificates"; + private const string Deleted = "(deleted)"; + private const string NoError = "(none)"; + + private CertificatesEventSource() : base(EventSourceName, EventSourceSettings.Default, AzureEventSourceListener.TraitName, AzureEventSourceListener.TraitValue) { } + + public static CertificatesEventSource Singleton { get; } = new CertificatesEventSource(); + + [NonEvent] + public void BeginUpdateStatus(CertificateOperationProperties properties) => + BeginUpdateStatus(properties?.Id.ToString(), properties?.Status, properties?.Error?.Message); + + [Event(BeginUpdateStatusEvent, Level = EventLevel.Verbose, Message = "Updating certificate operation status: {0}, current status: {1}, error: {2}")] + public void BeginUpdateStatus(string id, string status, string error) => WriteEvent(BeginUpdateStatusEvent, id ?? Deleted, status, error ?? NoError); + + [NonEvent] + public void EndUpdateStatus(CertificateOperationProperties properties) => + EndUpdateStatus(properties?.Id.ToString(), properties?.Status, properties?.Error?.Message); + + [Event(EndUpdateStatusEvent, Level = EventLevel.Verbose, Message = "Updated certificate operation status: {0}, ending status: {1}, error: {2}")] + public void EndUpdateStatus(string id, string status, string error) => WriteEvent(EndUpdateStatusEvent, id ?? Deleted, status, error ?? NoError); + } +} diff --git a/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/CertificateClientLiveTests.cs b/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/CertificateClientLiveTests.cs index b5e4d94791a64..5d35d810fedfb 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/CertificateClientLiveTests.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/CertificateClientLiveTests.cs @@ -110,7 +110,7 @@ public async Task VerifyCancelCertificateOperation() } OperationCanceledException ex = Assert.ThrowsAsync( - () => WaitForCompletion(operation), + async () => await WaitForCompletion(operation), $"Expected exception {nameof(OperationCanceledException)} not thrown. Operation status: {operation?.Properties?.Status}, error: {operation?.Properties?.Error?.Message}"); Assert.AreEqual("The operation was canceled so no value is available.", ex.Message); @@ -142,7 +142,7 @@ public async Task VerifyUnexpectedCancelCertificateOperation() } OperationCanceledException ex = Assert.ThrowsAsync( - () => WaitForCompletion(operation), + async () => await WaitForCompletion(operation), $"Expected exception {nameof(OperationCanceledException)} not thrown. Operation status: {operation?.Properties?.Status}, error: {operation?.Properties?.Error?.Message}"); Assert.AreEqual("The operation was canceled so no value is available.", ex.Message); @@ -165,7 +165,7 @@ public async Task VerifyDeleteCertificateOperation() await operation.DeleteAsync(); - InvalidOperationException ex = Assert.ThrowsAsync(() => WaitForCompletion(operation)); + InvalidOperationException ex = Assert.ThrowsAsync(async () => await WaitForCompletion(operation)); Assert.AreEqual("The operation was deleted so no value is available.", ex.Message); Assert.IsTrue(operation.HasCompleted); @@ -195,7 +195,7 @@ public async Task VerifyUnexpectedDeleteCertificateOperation() Assert.Inconclusive("The create operation completed before it could be canceled."); } - InvalidOperationException ex = Assert.ThrowsAsync(() => WaitForCompletion(operation)); + InvalidOperationException ex = Assert.ThrowsAsync(async () => await WaitForCompletion(operation)); Assert.AreEqual("The operation was deleted so no value is available.", ex.Message); Assert.IsTrue(operation.HasCompleted); @@ -228,8 +228,8 @@ public async Task VerifyCertificateOperationError() RegisterForCleanup(certName); - using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)); - TimeSpan pollingInterval = TimeSpan.FromSeconds((Mode == RecordedTestMode.Playback) ? 0 : 1); + using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromMinutes(2)); + TimeSpan pollingInterval = TimeSpan.FromSeconds((Mode == RecordedTestMode.Playback) ? 0 : 2); while (!operation.HasCompleted) { @@ -321,8 +321,8 @@ public async Task VerifyGetCertificateCompletedSubsequently() CertificateOperation operation = new CertificateOperation(Client, certName); // Need to call the real async wait method or the sync version of this test fails because it's using the instrumented Client directly. - using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)); - TimeSpan pollingInterval = TimeSpan.FromSeconds((Mode == RecordedTestMode.Playback) ? 0 : 1); + using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromMinutes(2)); + TimeSpan pollingInterval = TimeSpan.FromSeconds((Mode == RecordedTestMode.Playback) ? 0 : 2); await operation.WaitForCompletionAsync(pollingInterval, cts.Token); diff --git a/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/CertificateOperationTests.cs b/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/CertificateOperationTests.cs new file mode 100644 index 0000000000000..83b339fbe15be --- /dev/null +++ b/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/CertificateOperationTests.cs @@ -0,0 +1,325 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core; +using Azure.Core.Pipeline; +using Azure.Core.TestFramework; +using NUnit.Framework; + +namespace Azure.Security.KeyVault.Certificates.Tests +{ + [NonParallelizable] + public class CertificateOperationTests : ClientTestBase + { + private const string VaultUri = "https://test.vault.azure.net"; + private const string CertificateId = "https://test.vault.azure.net/certificates/test-cert"; + private const string CertificateName = "test-cert"; + + private static readonly string s_policyJson = $@"{{""id"":""{CertificateId}/policy"",""issuer"":{{""name"":""Self""}}}}"; + private static readonly CertificatePolicy s_policy; + + private TestEventListener _listener; + + public CertificateOperationTests(bool isAsync) : base(isAsync) + { + } + + static CertificateOperationTests() + { + var policy = new CertificatePolicy(); + ((IJsonDeserializable)policy).ReadProperties(JsonDocument.Parse(s_policyJson).RootElement); + + s_policy = policy; + } + + [SetUp] + public void Setup() + { + _listener = new TestEventListener(); + _listener.EnableEvents(CertificatesEventSource.Singleton, EventLevel.Verbose); + } + + [TearDown] + public void TearDown() + { + _listener.Dispose(); + } + + [Test] + public async Task UpdateStatusCompleted() + { + var transport = new MockTransport(new[] + { + new MockResponse(202).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""completed""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/1"",""policy"":{s_policyJson}}}"), + }); + + CertificateClient client = CreateClient(transport); + CertificateOperation operation = await client.StartCreateCertificateAsync(CertificateName, s_policy); + + await WaitForOperationAsync(operation); + + // Begin + IEnumerable messages = _listener.EventsById(CertificatesEventSource.BeginUpdateStatusEvent); + Assert.AreEqual(1, messages.Count()); + + EventWrittenEventArgs message = messages.Last(); + Assert.AreEqual(EventLevel.Verbose, message.Level); + Assert.AreEqual("BeginUpdateStatus", message.EventName); + Assert.AreEqual($"{CertificateId}/pending", message.GetProperty("id")); + Assert.AreEqual("inProgress", message.GetProperty("status")); + Assert.AreEqual("(none)", message.GetProperty("error")); + + // End + messages = _listener.EventsById(CertificatesEventSource.EndUpdateStatusEvent); + Assert.AreEqual(1, messages.Count()); + + message = messages.Last(); + Assert.AreEqual(EventLevel.Verbose, message.Level); + Assert.AreEqual("EndUpdateStatus", message.EventName); + Assert.AreEqual($"{CertificateId}/pending", message.GetProperty("id")); + Assert.AreEqual("completed", message.GetProperty("status")); + Assert.AreEqual("(none)", message.GetProperty("error")); + } + + [Test] + public async Task UpdateStatusEventuallyCompleted() + { + var transport = new MockTransport(new[] + { + new MockResponse(202).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""completed""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/1"",""policy"":{s_policyJson}}}"), + }); + + CertificateClient client = CreateClient(transport); + CertificateOperation operation = await client.StartCreateCertificateAsync(CertificateName, s_policy); + + await WaitForOperationAsync(operation); + + // Begin + IEnumerable messages = _listener.EventsById(CertificatesEventSource.BeginUpdateStatusEvent); + Assert.AreEqual(10, messages.Count()); + + EventWrittenEventArgs message = messages.Last(); + Assert.AreEqual(EventLevel.Verbose, message.Level); + Assert.AreEqual("BeginUpdateStatus", message.EventName); + Assert.AreEqual($"{CertificateId}/pending", message.GetProperty("id")); + Assert.AreEqual("inProgress", message.GetProperty("status")); + Assert.AreEqual("(none)", message.GetProperty("error")); + + // End + messages = _listener.EventsById(CertificatesEventSource.EndUpdateStatusEvent); + Assert.AreEqual(10, messages.Count()); + + message = messages.Last(); + Assert.AreEqual(EventLevel.Verbose, message.Level); + Assert.AreEqual("EndUpdateStatus", message.EventName); + Assert.AreEqual($"{CertificateId}/pending", message.GetProperty("id")); + Assert.AreEqual("completed", message.GetProperty("status")); + Assert.AreEqual("(none)", message.GetProperty("error")); + } + + [Test] + public async Task UpdateStatusCanceled() + { + var transport = new MockTransport(new[] + { + new MockResponse(202).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""cancelled""}}"), + }); + + CertificateClient client = CreateClient(transport); + CertificateOperation operation = await client.StartCreateCertificateAsync(CertificateName, s_policy); + + Exception ex = Assert.ThrowsAsync(async () => await WaitForOperationAsync(operation)); + Assert.AreEqual("The operation was canceled so no value is available.", ex.Message); + + // Begin + IEnumerable messages = _listener.EventsById(CertificatesEventSource.BeginUpdateStatusEvent); + Assert.AreEqual(5, messages.Count()); + + EventWrittenEventArgs message = messages.Last(); + Assert.AreEqual(EventLevel.Verbose, message.Level); + Assert.AreEqual("BeginUpdateStatus", message.EventName); + Assert.AreEqual($"{CertificateId}/pending", message.GetProperty("id")); + Assert.AreEqual("inProgress", message.GetProperty("status")); + Assert.AreEqual("(none)", message.GetProperty("error")); + + // End + messages = _listener.EventsById(CertificatesEventSource.EndUpdateStatusEvent); + Assert.AreEqual(5, messages.Count()); + + message = messages.Last(); + Assert.AreEqual(EventLevel.Verbose, message.Level); + Assert.AreEqual("EndUpdateStatus", message.EventName); + Assert.AreEqual($"{CertificateId}/pending", message.GetProperty("id")); + Assert.AreEqual("cancelled", message.GetProperty("status")); + Assert.AreEqual("(none)", message.GetProperty("error")); + } + + [Test] + public async Task UpdateStatusDeleted() + { + var transport = new MockTransport(new[] + { + new MockResponse(202).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + + new MockResponse(404), + }); + + CertificateClient client = CreateClient(transport); + CertificateOperation operation = await client.StartCreateCertificateAsync(CertificateName, s_policy); + + Exception ex = Assert.ThrowsAsync(async () => await WaitForOperationAsync(operation)); + Assert.AreEqual("The operation was deleted so no value is available.", ex.Message); + + // Begin + IEnumerable messages = _listener.EventsById(CertificatesEventSource.BeginUpdateStatusEvent); + Assert.AreEqual(5, messages.Count()); + + EventWrittenEventArgs message = messages.Last(); + Assert.AreEqual(EventLevel.Verbose, message.Level); + Assert.AreEqual("BeginUpdateStatus", message.EventName); + Assert.AreEqual($"{CertificateId}/pending", message.GetProperty("id")); + Assert.AreEqual("inProgress", message.GetProperty("status")); + Assert.AreEqual("(none)", message.GetProperty("error")); + + // End + messages = _listener.EventsById(CertificatesEventSource.EndUpdateStatusEvent); + Assert.AreEqual(5, messages.Count()); + + message = messages.Last(); + Assert.AreEqual(EventLevel.Verbose, message.Level); + Assert.AreEqual("EndUpdateStatus", message.EventName); + Assert.AreEqual("(deleted)", message.GetProperty("id")); + Assert.AreEqual(string.Empty, message.GetProperty("status")); + Assert.AreEqual("(none)", message.GetProperty("error")); + } + + [Test] + public async Task UpdateStatusErred() + { + var transport = new MockTransport(new[] + { + new MockResponse(202).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""inProgress""}}"), + + new MockResponse(200).WithContent($@"{{""id"":""{CertificateId}/pending"",""status"":""failed"",""error"":{{""code"":""mock failure code"",""message"":""mock failure message""}}}}"), + }); + + CertificateClient client = CreateClient(transport); + CertificateOperation operation = await client.StartCreateCertificateAsync(CertificateName, s_policy); + + Exception ex = Assert.ThrowsAsync(async () => await WaitForOperationAsync(operation)); + Assert.AreEqual("The certificate operation failed: mock failure message", ex.Message); + + // Begin + IEnumerable messages = _listener.EventsById(CertificatesEventSource.BeginUpdateStatusEvent); + Assert.AreEqual(5, messages.Count()); + + EventWrittenEventArgs message = messages.Last(); + Assert.AreEqual(EventLevel.Verbose, message.Level); + Assert.AreEqual("BeginUpdateStatus", message.EventName); + Assert.AreEqual($"{CertificateId}/pending", message.GetProperty("id")); + Assert.AreEqual("inProgress", message.GetProperty("status")); + Assert.AreEqual("(none)", message.GetProperty("error")); + + // End + messages = _listener.EventsById(CertificatesEventSource.EndUpdateStatusEvent); + Assert.AreEqual(5, messages.Count()); + + message = messages.Last(); + Assert.AreEqual(EventLevel.Verbose, message.Level); + Assert.AreEqual("EndUpdateStatus", message.EventName); + Assert.AreEqual($"{CertificateId}/pending", message.GetProperty("id")); + Assert.AreEqual("failed", message.GetProperty("status")); + Assert.AreEqual("mock failure message", message.GetProperty("error")); + } + + private CertificateClient CreateClient(HttpPipelineTransport transport) + { + CertificateClientOptions options = new CertificateClientOptions + { + Transport = transport, + }; + + return InstrumentClient( + new CertificateClient( + new Uri(VaultUri), + new MockCredential(), + options + )); + } + + private async ValueTask WaitForOperationAsync(CertificateOperation operation) + { + var rand = new Random(); + TimeSpan PollingInterval() => TimeSpan.FromMilliseconds(rand.Next(1, 50)); + + if (IsAsync) + { + return await operation.WaitForCompletionAsync(PollingInterval(), default); + } + else + { + while (!operation.HasCompleted) + { + operation.UpdateStatus(); + await Task.Delay(PollingInterval()); + } + + return operation.Value; + } + } + + public class MockCredential : TokenCredential + { + private readonly AccessToken _token = new AccessToken("mockToken", DateTimeOffset.UtcNow.AddHours(1)); + + public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + return _token; + } + + public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + return new ValueTask(_token); + } + } + } +} diff --git a/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/LightweightPkcs8DecoderTests.cs b/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/LightweightPkcs8DecoderTests.cs index edb43851babd0..966492aa2cd72 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/LightweightPkcs8DecoderTests.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/LightweightPkcs8DecoderTests.cs @@ -16,9 +16,15 @@ namespace Azure.Security.KeyVault.Certificates.Tests public class LightweightPkcs8DecoderTests { [Test] - [Ignore("Temporarily disable until https://github.com/Azure/azure-sdk-for-net/pull/19612")] - public void VerifyECDecoderPrime256v1Imported() => - VerifyECDecoder(EcPrime256v1PrivateKeyImported, CertificateKeyCurveName.P256K, @"DFTTJrKrtao7G/B0bK5yv+mX0/3Sefv2MS1gzd6DfYH2ASe9Tw7rSbLjZ8wM0p7I/opbIG1+zHhpYqOGnQNQyw==", EcPrime256v1CertificateImported); + public void VerifyECDecoderPrime256v1Imported() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Assert.Ignore("The curve is not supported by the current platform"); + } + + VerifyECDecoder(EcPrime256v1PrivateKeyImported, CertificateKeyCurveName.P256, @"DFTTJrKrtao7G/B0bK5yv+mX0/3Sefv2MS1gzd6DfYH2ASe9Tw7rSbLjZ8wM0p7I/opbIG1+zHhpYqOGnQNQyw==", EcPrime256v1CertificateImported); + } [Test] public void VerifyECDecoderSecp256k1() => @@ -94,7 +100,7 @@ private static void VerifyECDecoder(string key, CertificateKeyCurveName keyCurve catch (Exception ex) when ( (ex is CryptographicException || (ex is TargetInvocationException && ex.InnerException is CryptographicException)) && RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && - keyCurveName == CertificateKeyCurveName.P256K) + keyCurveName == CertificateKeyCurveName.P256 || keyCurveName == CertificateKeyCurveName.P256K) { Assert.Ignore("The curve is not supported by the current platform"); } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/PemReaderTests.cs b/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/PemReaderTests.cs index 43318f6938dc4..a35e8823d8e73 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/PemReaderTests.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/PemReaderTests.cs @@ -99,15 +99,12 @@ public void LoadRSACertificate() } [Test] - [Ignore("Temporarily disable until https://github.com/Azure/azure-sdk-for-net/pull/19612")] public void LoadECDsaPrime256v1Certificate() { #if NET461 Assert.Ignore("Loading X509Certificate2 with private EC key not supported on this platform"); -#elif NETCOREAPP2_1 - Assert.Ignore("Different OIDs in the certificate and private key prevent this from passing currently"); #endif - using X509Certificate2 certificate = PemReader.LoadCertificate(ECDsaPrime256v1Certificate.AsSpan(), keyType: PemReader.KeyType.ECDsa); + using X509Certificate2 certificate = PemReader.LoadCertificate(s_ecdsaFullCertificate.AsSpan(), keyType: PemReader.KeyType.ECDsa); Assert.AreEqual("CN=Azure SDK", certificate.Subject); Assert.IsTrue(certificate.HasPrivateKey); } @@ -180,32 +177,6 @@ public void LoadECDsaPrime256v1Certificate() lkApwUZg00+9hRWxv0DTh/mRS2zu5i/9W+cZbIcRah0JHgOzAjvsyY9RHjqZ9r7c Md7RrFHxnAKJj5TZJJJOf5h3OaaF3A5W8gf9Bc68aGQLFT5Y2afIawkYNSULypc3 pn29yMivL7r48dlo ------END CERTIFICATE-----"; - - private const string ECDsaPrime256v1Certificate = @" ------BEGIN PRIVATE KEY----- -MIIBMgIBADCBrgYHKoZIzj0CATCBogIBATAsBgcqhkjOPQEBAiEA//////////// -/////////////////////////v///C8wBgQBAAQBBwRBBHm+Zn753LusVaBilc6H -CwcCm/zbLc4o2VnygVsW+BeYSDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj/sQ -1LgCIQD////////////////////+uq7c5q9IoDu/0l6M0DZBQQIBAQRtMGsCAQEE -INIX6ZEliNAwvZAq3GkJHNHSvQOlIzFMkifg/C8DfVAhoUQDQgAEnxIn+jXd3Pfw -IcpGkQx9b1L/BYXHDzNINDOTwmOku3fG+zI6vT3R3VkHfcd7GR9aJxp1tdxMnIkF -xU2Z1eNVXqANMAsGA1UdDzEEAwIAgA== ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIICPzCCAeWgAwIBAgIQdclPKrRQTmCpZ0p2lM2pmDAKBggqhkjOPQQDAjAUMRIw -EAYDVQQDEwlBenVyZSBTREswHhcNMjEwMzAzMDEwOTIxWhcNMjIwMzAzMDExOTIx -WjAUMRIwEAYDVQQDEwlBenVyZSBTREswgfUwga4GByqGSM49AgEwgaICAQEwLAYH -KoZIzj0BAQIhAP////////////////////////////////////7///wvMAYEAQAE -AQcEQQR5vmZ++dy7rFWgYpXOhwsHApv82y3OKNlZ8oFbFvgXmEg62ncmo8RlXaT7 -/A4RCKj9F7RIpoVUGZxH0I/7ENS4AiEA/////////////////////rqu3OavSKA7 -v9JejNA2QUECAQEDQgAEnxIn+jXd3PfwIcpGkQx9b1L/BYXHDzNINDOTwmOku3fG -+zI6vT3R3VkHfcd7GR9aJxp1tdxMnIkFxU2Z1eNVXqN8MHowDgYDVR0PAQH/BAQD -AgeAMAkGA1UdEwQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB8G -A1UdIwQYMBaAFAJEtqOacY6KuA8KAyt3dgByxWGlMB0GA1UdDgQWBBQCRLajmnGO -irgPCgMrd3YAcsVhpTAKBggqhkjOPQQDAgNIADBFAiEAlGVRNxgGsOpmBAGud2v1 -aSnz2Zm8A9EV1a+5EVp8Rz8CIFkJYAXOL/n0xo4fp+7yR+9SwVa3c6wNimYSfhoU -+YpQ -----END CERTIFICATE-----"; private static readonly string s_ecdsaFullCertificate = ECDsaPrivateKey + ECDsaCertificate; diff --git a/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/TestExtensionMethods.cs b/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/TestExtensionMethods.cs index dca17100d7e38..42df51bab4aec 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/TestExtensionMethods.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/TestExtensionMethods.cs @@ -3,6 +3,7 @@ using System; using System.Security.Cryptography; +using Azure.Core.TestFramework; using Azure.Security.KeyVault.Keys.Cryptography; namespace Azure.Security.KeyVault.Certificates.Tests @@ -35,5 +36,11 @@ public static class TestExtensionMethods "P-521" => 521, _ => throw new NotSupportedException($"{keyCurveName} is not supported"), }; + + public static MockResponse WithContent(this MockResponse response, string content) + { + response.SetContent(content); + return response; + } } } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Keys/tests/KeyClientLiveTests.cs b/sdk/keyvault/Azure.Security.KeyVault.Keys/tests/KeyClientLiveTests.cs index a13708c604968..ee9d93abe84cb 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Keys/tests/KeyClientLiveTests.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Keys/tests/KeyClientLiveTests.cs @@ -904,13 +904,17 @@ public async Task GetDeletedKeys() RegisterForCleanup(Key.Name); } + List deletingKeys = new List(); foreach (KeyVaultKey deletedKey in createdKeys) { - await WaitForDeletedKey(deletedKey.Name); + // WaitForDeletedKey disables recording, so we can wait concurrently. + // Wait a little longer for deleting keys since tests occasionally fail after max attempts. + deletingKeys.Add(WaitForDeletedKey(deletedKey.Name, delay: TimeSpan.FromSeconds(5))); } - List allKeys = await Client.GetDeletedKeysAsync().ToEnumerableAsync(); + await Task.WhenAll(deletingKeys); + List allKeys = await Client.GetDeletedKeysAsync().ToEnumerableAsync(); foreach (KeyVaultKey createdKey in createdKeys) { KeyVaultKey returnedKey = allKeys.Single(s => s.Properties.Name == createdKey.Name); diff --git a/sdk/keyvault/Azure.Security.KeyVault.Keys/tests/KeysTestBase.cs b/sdk/keyvault/Azure.Security.KeyVault.Keys/tests/KeysTestBase.cs index f7491503354ef..fab936810d5c7 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Keys/tests/KeysTestBase.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Keys/tests/KeysTestBase.cs @@ -251,7 +251,7 @@ protected static void AssertAreEqual(IDictionary exp } } - protected Task WaitForDeletedKey(string name) + protected Task WaitForDeletedKey(string name, TimeSpan? delay = null) { if (Mode == RecordedTestMode.Playback) { @@ -260,7 +260,8 @@ protected Task WaitForDeletedKey(string name) using (Recording.DisableRecording()) { - return TestRetryHelper.RetryAsync(async () => await Client.GetDeletedKeyAsync(name), delay: PollingInterval); + delay ??= PollingInterval; + return TestRetryHelper.RetryAsync(async () => await Client.GetDeletedKeyAsync(name), delay: delay.Value); } } diff --git a/sdk/keyvault/Azure.Security.KeyVault.Secrets/tests/SecretClientLiveTests.cs b/sdk/keyvault/Azure.Security.KeyVault.Secrets/tests/SecretClientLiveTests.cs index 7fe1bbccf7742..1e4226a45b1c3 100644 --- a/sdk/keyvault/Azure.Security.KeyVault.Secrets/tests/SecretClientLiveTests.cs +++ b/sdk/keyvault/Azure.Security.KeyVault.Secrets/tests/SecretClientLiveTests.cs @@ -444,13 +444,16 @@ public async Task GetDeletedSecrets() RegisterForCleanup(secret.Name); } + List deletingSecrets = new List(); foreach (KeyVaultSecret deletedSecret in deletedSecrets) { - await WaitForDeletedSecret(deletedSecret.Name); + // WaitForDeletedSecret disables recording, so we can wait concurrently. + deletingSecrets.Add(WaitForDeletedSecret(deletedSecret.Name)); } - List allSecrets = await Client.GetDeletedSecretsAsync().ToEnumerableAsync(); + await Task.WhenAll(deletingSecrets); + List allSecrets = await Client.GetDeletedSecretsAsync().ToEnumerableAsync(); foreach (KeyVaultSecret deletedSecret in deletedSecrets) { KeyVaultSecret returnedSecret = allSecrets.Single(s => s.Name == deletedSecret.Name);