Skip to content

Commit 76c143e

Browse files
[release/7.0] Make Kestrel prefer on-disk cert for macOS bind. (#44856)
* Make Kestrel prefer on-disk cert for macOS bind. * Clarify Intersect behavior in comment. Co-authored-by: Aditya Mandaleeka <[email protected]>
1 parent 16a875a commit 76c143e

File tree

3 files changed

+13
-10
lines changed

3 files changed

+13
-10
lines changed

src/Servers/Kestrel/Core/src/KestrelServerOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ private void EnsureDefaultCert()
290290
var logger = ApplicationServices!.GetRequiredService<ILogger<KestrelServer>>();
291291
try
292292
{
293-
DefaultCertificate = CertificateManager.Instance.ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: true)
293+
DefaultCertificate = CertificateManager.Instance.ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: true, requireExportable: false)
294294
.FirstOrDefault();
295295

296296
if (DefaultCertificate != null)

src/Shared/CertificateGeneration/CertificateManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public IList<X509Certificate2> ListCertificates(
7878
{
7979
using var store = new X509Store(storeName, location);
8080
store.Open(OpenFlags.ReadOnly);
81-
PopulateCertificatesFromStore(store, certificates);
81+
PopulateCertificatesFromStore(store, certificates, requireExportable);
8282
IEnumerable<X509Certificate2> matchingCertificates = certificates;
8383
matchingCertificates = matchingCertificates
8484
.Where(c => HasOid(c, AspNetHttpsOid));
@@ -161,7 +161,7 @@ bool IsValidCertificate(X509Certificate2 certificate, DateTimeOffset currentDate
161161
GetCertificateVersion(certificate) >= AspNetHttpsCertificateVersion;
162162
}
163163

164-
protected virtual void PopulateCertificatesFromStore(X509Store store, List<X509Certificate2> certificates)
164+
protected virtual void PopulateCertificatesFromStore(X509Store store, List<X509Certificate2> certificates, bool requireExportable)
165165
{
166166
certificates.AddRange(store.Certificates.OfType<X509Certificate2>());
167167
}

src/Shared/CertificateGeneration/MacOSCertificateManager.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -373,25 +373,28 @@ protected override IList<X509Certificate2> GetCertificatesToRemove(StoreName sto
373373
return ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: false);
374374
}
375375

376-
protected override void PopulateCertificatesFromStore(X509Store store, List<X509Certificate2> certificates)
376+
protected override void PopulateCertificatesFromStore(X509Store store, List<X509Certificate2> certificates, bool requireExportable)
377377
{
378378
if (store.Name! == StoreName.My.ToString() && store.Location == StoreLocation.CurrentUser && Directory.Exists(MacOSUserHttpsCertificateLocation))
379379
{
380380
var certsFromDisk = GetCertsFromDisk();
381381

382382
var certsFromStore = new List<X509Certificate2>();
383-
base.PopulateCertificatesFromStore(store, certsFromStore);
383+
base.PopulateCertificatesFromStore(store, certsFromStore, requireExportable);
384384

385385
// Certs created by pre-.NET 7.
386386
var onlyOnKeychain = certsFromStore.Except(certsFromDisk, ThumbprintComparer.Instance);
387387

388388
// Certs created (or "upgraded") by .NET 7+.
389389
// .NET 7+ installs the certificate on disk as well as on the user keychain (for backwards
390390
// compatibility with pre-.NET 7).
391-
// Note that the actual certs we populate need to be the ones from the store location, and
392-
// not the version from disk, since we may do other operations with these certs later (such
393-
// as exporting) which would fail with crypto errors otherwise.
394-
var onDiskAndKeychain = certsFromStore.Intersect(certsFromDisk, ThumbprintComparer.Instance);
391+
// Note that if we require exportable certs, the actual certs we populate need to be the ones
392+
// from the store location, and not the version from disk. If we don't require exportability,
393+
// we favor the version of the cert that's on disk (avoiding unnecessary keychain access
394+
// prompts). Intersect compares with the specified comparer and returns the matching elements
395+
// from the first set.
396+
var onDiskAndKeychain = requireExportable ? certsFromStore.Intersect(certsFromDisk, ThumbprintComparer.Instance)
397+
: certsFromDisk.Intersect(certsFromStore, ThumbprintComparer.Instance);
395398

396399
// The only times we can find a certificate on the keychain and a certificate on keychain+disk
397400
// are when the certificate on disk and keychain has expired and a pre-.NET 7 SDK has been
@@ -403,7 +406,7 @@ protected override void PopulateCertificatesFromStore(X509Store store, List<X509
403406
}
404407
else
405408
{
406-
base.PopulateCertificatesFromStore(store, certificates);
409+
base.PopulateCertificatesFromStore(store, certificates, requireExportable);
407410
}
408411
}
409412

0 commit comments

Comments
 (0)