Skip to content

Commit 0796e70

Browse files
Add host.docker.internal and host.containers.internal to the dev cert SAN (#61265)
* Add notion of minimum cert version Add new SAN for dev cert + json output for the tool * Cleaner json output version * Revert unecessary change * Rename option name and add a test * Modify ListCertificates_AlwaysReturnsTheCertificate_WithHighestVersion to create 3 certs * Skip on Helix the added test * Skip EnsureCreateHttpsCertificate_DoNotOverrideValidOldCertificate on OSX
1 parent 3926315 commit 0796e70

File tree

7 files changed

+202
-35
lines changed

7 files changed

+202
-35
lines changed

src/Shared/CertificateGeneration/CertificateManager.cs

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,31 @@
99
using System.Security.Cryptography;
1010
using System.Security.Cryptography.X509Certificates;
1111
using System.Text;
12+
using System.Text.Json;
13+
using System.Text.Json.Nodes;
14+
using System.Text.Json.Serialization;
1215

1316
#nullable enable
1417

1518
namespace Microsoft.AspNetCore.Certificates.Generation;
1619

1720
internal abstract class CertificateManager
1821
{
19-
internal const int CurrentAspNetCoreCertificateVersion = 2;
22+
internal const int CurrentAspNetCoreCertificateVersion = 3;
23+
internal const int CurrentMinimumAspNetCoreCertificateVersion = 3;
2024

2125
// OID used for HTTPS certs
2226
internal const string AspNetHttpsOid = "1.3.6.1.4.1.311.84.1.1";
2327
internal const string AspNetHttpsOidFriendlyName = "ASP.NET Core HTTPS development certificate";
2428

2529
private const string ServerAuthenticationEnhancedKeyUsageOid = "1.3.6.1.5.5.7.3.1";
2630
private const string ServerAuthenticationEnhancedKeyUsageOidFriendlyName = "Server Authentication";
31+
32+
// dns names of the host from a container
33+
private const string LocalHostDockerHttpsDnsName = "host.docker.internal";
34+
private const string ContainersDockerHttpsDnsName = "host.containers.internal";
2735

36+
// main cert subject
2837
private const string LocalhostHttpsDnsName = "localhost";
2938
internal const string LocalhostHttpsDistinguishedName = "CN=" + LocalhostHttpsDnsName;
3039

@@ -46,7 +55,28 @@ public int AspNetHttpsCertificateVersion
4655
{
4756
get;
4857
// For testing purposes only
49-
internal set;
58+
internal set
59+
{
60+
ArgumentOutOfRangeException.ThrowIfLessThan(
61+
value,
62+
MinimumAspNetHttpsCertificateVersion,
63+
$"{nameof(AspNetHttpsCertificateVersion)} cannot be lesser than {nameof(MinimumAspNetHttpsCertificateVersion)}");
64+
field = value;
65+
}
66+
}
67+
68+
public int MinimumAspNetHttpsCertificateVersion
69+
{
70+
get;
71+
// For testing purposes only
72+
internal set
73+
{
74+
ArgumentOutOfRangeException.ThrowIfGreaterThan(
75+
value,
76+
AspNetHttpsCertificateVersion,
77+
$"{nameof(MinimumAspNetHttpsCertificateVersion)} cannot be greater than {nameof(AspNetHttpsCertificateVersion)}");
78+
field = value;
79+
}
5080
}
5181

5282
public string Subject { get; }
@@ -57,9 +87,16 @@ public CertificateManager() : this(LocalhostHttpsDistinguishedName, CurrentAspNe
5787

5888
// For testing purposes only
5989
internal CertificateManager(string subject, int version)
90+
: this(subject, version, version)
91+
{
92+
}
93+
94+
// For testing purposes only
95+
internal CertificateManager(string subject, int generatedVersion, int minimumVersion)
6096
{
6197
Subject = subject;
62-
AspNetHttpsCertificateVersion = version;
98+
AspNetHttpsCertificateVersion = generatedVersion;
99+
MinimumAspNetHttpsCertificateVersion = minimumVersion;
63100
}
64101

65102
/// <remarks>
@@ -147,30 +184,30 @@ bool HasOid(X509Certificate2 certificate, string oid) =>
147184
certificate.Extensions.OfType<X509Extension>()
148185
.Any(e => string.Equals(oid, e.Oid?.Value, StringComparison.Ordinal));
149186

150-
static byte GetCertificateVersion(X509Certificate2 c)
151-
{
152-
var byteArray = c.Extensions.OfType<X509Extension>()
153-
.Where(e => string.Equals(AspNetHttpsOid, e.Oid?.Value, StringComparison.Ordinal))
154-
.Single()
155-
.RawData;
156-
157-
if ((byteArray.Length == AspNetHttpsOidFriendlyName.Length && byteArray[0] == (byte)'A') || byteArray.Length == 0)
158-
{
159-
// No Version set, default to 0
160-
return 0b0;
161-
}
162-
else
163-
{
164-
// Version is in the only byte of the byte array.
165-
return byteArray[0];
166-
}
167-
}
168-
169187
bool IsValidCertificate(X509Certificate2 certificate, DateTimeOffset currentDate, bool requireExportable) =>
170188
certificate.NotBefore <= currentDate &&
171189
currentDate <= certificate.NotAfter &&
172190
(!requireExportable || IsExportable(certificate)) &&
173-
GetCertificateVersion(certificate) >= AspNetHttpsCertificateVersion;
191+
GetCertificateVersion(certificate) >= MinimumAspNetHttpsCertificateVersion;
192+
}
193+
194+
internal static byte GetCertificateVersion(X509Certificate2 c)
195+
{
196+
var byteArray = c.Extensions.OfType<X509Extension>()
197+
.Where(e => string.Equals(AspNetHttpsOid, e.Oid?.Value, StringComparison.Ordinal))
198+
.Single()
199+
.RawData;
200+
201+
if ((byteArray.Length == AspNetHttpsOidFriendlyName.Length && byteArray[0] == (byte)'A') || byteArray.Length == 0)
202+
{
203+
// No Version set, default to 0
204+
return 0b0;
205+
}
206+
else
207+
{
208+
// Version is in the only byte of the byte array.
209+
return byteArray[0];
210+
}
174211
}
175212

176213
protected virtual void PopulateCertificatesFromStore(X509Store store, List<X509Certificate2> certificates, bool requireExportable)
@@ -487,7 +524,7 @@ public void CleanupHttpsCertificates()
487524
/// <remarks>Implementations may choose to throw, rather than returning <see cref="TrustLevel.None"/>.</remarks>
488525
protected abstract TrustLevel TrustCertificateCore(X509Certificate2 certificate);
489526

490-
protected abstract bool IsExportable(X509Certificate2 c);
527+
internal abstract bool IsExportable(X509Certificate2 c);
491528

492529
protected abstract void RemoveCertificateFromTrustedRoots(X509Certificate2 certificate);
493530

@@ -649,6 +686,8 @@ internal X509Certificate2 CreateAspNetCoreHttpsDevelopmentCertificate(DateTimeOf
649686
var extensions = new List<X509Extension>();
650687
var sanBuilder = new SubjectAlternativeNameBuilder();
651688
sanBuilder.AddDnsName(LocalhostHttpsDnsName);
689+
sanBuilder.AddDnsName(LocalHostDockerHttpsDnsName);
690+
sanBuilder.AddDnsName(ContainersDockerHttpsDnsName);
652691

653692
var keyUsage = new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, critical: true);
654693
var enhancedKeyUsage = new X509EnhancedKeyUsageExtension(

src/Shared/CertificateGeneration/MacOSCertificateManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ private static bool IsCertOnKeychain(string keychain, X509Certificate2 certifica
302302
}
303303

304304
// We don't have a good way of checking on the underlying implementation if it is exportable, so just return true.
305-
protected override bool IsExportable(X509Certificate2 c) => true;
305+
internal override bool IsExportable(X509Certificate2 c) => true;
306306

307307
protected override X509Certificate2 SaveCertificateCore(X509Certificate2 certificate, StoreName storeName, StoreLocation storeLocation)
308308
{

src/Shared/CertificateGeneration/UnixCertificateManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ internal override void CorrectCertificateState(X509Certificate2 candidate)
179179
// This is about correcting storage, not trust.
180180
}
181181

182-
protected override bool IsExportable(X509Certificate2 c) => true;
182+
internal override bool IsExportable(X509Certificate2 c) => true;
183183

184184
protected override TrustLevel TrustCertificateCore(X509Certificate2 certificate)
185185
{

src/Shared/CertificateGeneration/WindowsCertificateManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ internal WindowsCertificateManager(string subject, int version)
2727
{
2828
}
2929

30-
protected override bool IsExportable(X509Certificate2 c)
30+
internal override bool IsExportable(X509Certificate2 c)
3131
{
3232
#if XPLAT
3333
// For the first run experience we don't need to know if the certificate can be exported.

src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ public void EnsureCreateHttpsCertificate_ReturnsExpiredCertificateIfVersionIsInc
388388
ListCertificates();
389389

390390
_manager.AspNetHttpsCertificateVersion = 2;
391+
_manager.MinimumAspNetHttpsCertificateVersion = 2;
391392

392393
var httpsCertificateList = _manager.ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: true);
393394
Assert.Empty(httpsCertificateList);
@@ -400,17 +401,40 @@ public void EnsureCreateHttpsCertificate_ReturnsExpiredCertificateForEmptyVersio
400401

401402
var now = DateTimeOffset.UtcNow;
402403
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
404+
_manager.MinimumAspNetHttpsCertificateVersion = 0;
403405
_manager.AspNetHttpsCertificateVersion = 0;
404406
var creation = _manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, isInteractive: false);
405407
Output.WriteLine(creation.ToString());
406408
ListCertificates();
407409

408410
_manager.AspNetHttpsCertificateVersion = 1;
411+
_manager.MinimumAspNetHttpsCertificateVersion = 1;
409412

410413
var httpsCertificateList = _manager.ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: true);
411414
Assert.Empty(httpsCertificateList);
412415
}
413416

417+
[ConditionalFact]
418+
[SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720", Queues = "All.OSX")]
419+
public void EnsureCreateHttpsCertificate_DoNotOverrideValidOldCertificate()
420+
{
421+
_fixture.CleanupCertificates();
422+
423+
var now = DateTimeOffset.UtcNow;
424+
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
425+
var creation = _manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, isInteractive: false);
426+
Output.WriteLine(creation.ToString());
427+
ListCertificates();
428+
429+
// Simulate a tool with the same min version as the already existing cert but with a more
430+
// recent generation version
431+
_manager.MinimumAspNetHttpsCertificateVersion = 1;
432+
_manager.AspNetHttpsCertificateVersion = 2;
433+
var alreadyExist = _manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, isInteractive: false);
434+
Output.WriteLine(alreadyExist.ToString());
435+
Assert.Equal(EnsureCertificateResult.ValidCertificatePresent, alreadyExist);
436+
}
437+
414438
[ConditionalFact]
415439
[SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/6720", Queues = "All.OSX")]
416440
public void EnsureCreateHttpsCertificate_ReturnsValidIfVersionIsZero()
@@ -419,7 +443,7 @@ public void EnsureCreateHttpsCertificate_ReturnsValidIfVersionIsZero()
419443

420444
var now = DateTimeOffset.UtcNow;
421445
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
422-
_manager.AspNetHttpsCertificateVersion = 0;
446+
_manager.MinimumAspNetHttpsCertificateVersion = 0;
423447
var creation = _manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, isInteractive: false);
424448
Output.WriteLine(creation.ToString());
425449
ListCertificates();
@@ -441,7 +465,7 @@ public void EnsureCreateHttpsCertificate_ReturnValidIfCertIsNewer()
441465
Output.WriteLine(creation.ToString());
442466
ListCertificates();
443467

444-
_manager.AspNetHttpsCertificateVersion = 1;
468+
_manager.MinimumAspNetHttpsCertificateVersion = 1;
445469
var httpsCertificateList = _manager.ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: true);
446470
Assert.NotEmpty(httpsCertificateList);
447471
}
@@ -455,16 +479,24 @@ public void ListCertificates_AlwaysReturnsTheCertificate_WithHighestVersion()
455479
var now = DateTimeOffset.UtcNow;
456480
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
457481
_manager.AspNetHttpsCertificateVersion = 1;
482+
_manager.MinimumAspNetHttpsCertificateVersion = 1;
458483
var creation = _manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, isInteractive: false);
459484
Output.WriteLine(creation.ToString());
460485
ListCertificates();
461486

462487
_manager.AspNetHttpsCertificateVersion = 2;
488+
_manager.MinimumAspNetHttpsCertificateVersion = 2;
463489
creation = _manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, isInteractive: false);
464490
Output.WriteLine(creation.ToString());
465491
ListCertificates();
466492

467-
_manager.AspNetHttpsCertificateVersion = 1;
493+
_manager.AspNetHttpsCertificateVersion = 3;
494+
_manager.MinimumAspNetHttpsCertificateVersion = 3;
495+
creation = _manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, isInteractive: false);
496+
Output.WriteLine(creation.ToString());
497+
ListCertificates();
498+
499+
_manager.MinimumAspNetHttpsCertificateVersion = 2;
468500
var httpsCertificateList = _manager.ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: true);
469501
Assert.Equal(2, httpsCertificateList.Count);
470502

@@ -475,13 +507,13 @@ public void ListCertificates_AlwaysReturnsTheCertificate_WithHighestVersion()
475507
firstCertificate.Extensions.OfType<X509Extension>(),
476508
e => e.Critical == false &&
477509
e.Oid.Value == CertificateManager.AspNetHttpsOid &&
478-
e.RawData[0] == 2);
510+
e.RawData[0] == 3);
479511

480512
Assert.Contains(
481513
secondCertificate.Extensions.OfType<X509Extension>(),
482514
e => e.Critical == false &&
483515
e.Oid.Value == CertificateManager.AspNetHttpsOid &&
484-
e.RawData[0] == 1);
516+
e.RawData[0] == 2);
485517
}
486518

487519
[ConditionalFact]
@@ -532,6 +564,8 @@ public CertFixture()
532564

533565
internal void CleanupCertificates()
534566
{
567+
Manager.MinimumAspNetHttpsCertificateVersion = 1;
568+
Manager.AspNetHttpsCertificateVersion = 1;
535569
Manager.RemoveAllCertificates(StoreName.My, StoreLocation.CurrentUser);
536570
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
537571
{

0 commit comments

Comments
 (0)