Skip to content
Open
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 45 additions & 32 deletions src/Shared/CertificateGeneration/CertificateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate(
catch (Exception e)
{
Log.SaveCertificateInStoreError(e.ToString());
certificate?.Dispose();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot The certificates variable contains a list of certificates and should be disposed in all early exit cases. See the use of DisposeCertificates at the bottom of this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're absolutely right. Fixed the certificate collection disposal pattern to properly dispose all loaded certificates in early exit cases. The issue was that the certificates variable contains the full list of certificates loaded from both stores, and they weren't being disposed in early return paths. I also had to materialize the LINQ expressions with .ToList() to prevent issues with accessing disposed certificate properties during enumeration. All tests now pass. Fixed in commit e2b7b68.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, this is close, but we shouldn't dispose certificate unless it's a new certificate. See original disposal code:
DisposeCertificates(!isNewCertificate ? certificates : certificates.Append(certificate));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot See previous comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the certificate disposal logic to only dispose the certificate if it's a new certificate (isNewCertificate == true). If the certificate came from the existing certificates collection, it will be disposed as part of DisposeCertificates(certificates). This now matches the original disposal pattern at the end of the method. Fixed in commit e9c4aad.

result = EnsureCertificateResult.ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore;
return result;
}
Expand Down Expand Up @@ -387,6 +388,7 @@ public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate(
result :
EnsureCertificateResult.ErrorExportingTheCertificate;

certificate?.Dispose();
return result;
}
}
Expand All @@ -403,21 +405,25 @@ public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate(
break;
case TrustLevel.Partial:
result = EnsureCertificateResult.PartiallyFailedToTrustTheCertificate;
certificate?.Dispose();
return result;
case TrustLevel.None:
default: // Treat unknown status (should be impossible) as failure
result = EnsureCertificateResult.FailedToTrustTheCertificate;
certificate?.Dispose();
return result;
}
}
catch (UserCancelledTrustException)
{
result = EnsureCertificateResult.UserCancelledTrustStep;
certificate?.Dispose();
return result;
}
catch
{
result = EnsureCertificateResult.FailedToTrustTheCertificate;
certificate?.Dispose();
return result;
}

Expand Down Expand Up @@ -454,51 +460,58 @@ internal ImportCertificateResult ImportCertificate(string certificatePath, strin
return ImportCertificateResult.ExistingCertificatesPresent;
}

X509Certificate2 certificate;
X509Certificate2? certificate = null;
try
{
Log.LoadCertificateStart(certificatePath);
certificate = new X509Certificate2(certificatePath, password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.EphemeralKeySet);
if (Log.IsEnabled())
try
{
Log.LoadCertificateEnd(GetDescription(certificate));
Log.LoadCertificateStart(certificatePath);
certificate = new X509Certificate2(certificatePath, password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.EphemeralKeySet);
if (Log.IsEnabled())
{
Log.LoadCertificateEnd(GetDescription(certificate));
}
}
}
catch (Exception e)
{
if (Log.IsEnabled())
catch (Exception e)
{
Log.LoadCertificateError(e.ToString());
if (Log.IsEnabled())
{
Log.LoadCertificateError(e.ToString());
}
return ImportCertificateResult.InvalidCertificate;
}
return ImportCertificateResult.InvalidCertificate;
}

// Note that we're checking Subject, rather than LocalhostHttpsDistinguishedName,
// because the tests use a different subject.
if (!string.Equals(certificate.Subject, Subject, StringComparison.Ordinal) || // Kestrel requires this
!IsHttpsDevelopmentCertificate(certificate))
{
if (Log.IsEnabled())
// Note that we're checking Subject, rather than LocalhostHttpsDistinguishedName,
// because the tests use a different subject.
if (!string.Equals(certificate.Subject, Subject, StringComparison.Ordinal) || // Kestrel requires this
!IsHttpsDevelopmentCertificate(certificate))
{
Log.NoHttpsDevelopmentCertificate(GetDescription(certificate));
if (Log.IsEnabled())
{
Log.NoHttpsDevelopmentCertificate(GetDescription(certificate));
}
return ImportCertificateResult.NoDevelopmentHttpsCertificate;
}
return ImportCertificateResult.NoDevelopmentHttpsCertificate;
}

try
{
SaveCertificate(certificate);
}
catch (Exception e)
{
if (Log.IsEnabled())
try
{
Log.SaveCertificateInStoreError(e.ToString());
certificate = SaveCertificate(certificate);
}
catch (Exception e)
{
if (Log.IsEnabled())
{
Log.SaveCertificateInStoreError(e.ToString());
}
return ImportCertificateResult.ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore;
}
return ImportCertificateResult.ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore;
}

return ImportCertificateResult.Succeeded;
return ImportCertificateResult.Succeeded;
}
finally
{
certificate?.Dispose();
}
}

public void CleanupHttpsCertificates()
Expand Down
Loading