Add versioning to dotnet-dev-certs (#10908)
This commit is contained in:
parent
5b56de966e
commit
fdba8a91f9
|
|
@ -42,6 +42,10 @@ namespace Microsoft.AspNetCore.Certificates.Generation
|
|||
private static readonly string MacOSTrustCertificateCommandLineArguments = "security add-trusted-cert -d -r trustRoot -k " + MacOSSystemKeyChain + " ";
|
||||
private const int UserCancelledErrorCode = 1223;
|
||||
|
||||
// Setting to 0 means we don't append the version byte,
|
||||
// which is what all machines currently have.
|
||||
public int AspNetHttpsCertificateVersion { get; set; } = 1;
|
||||
|
||||
public IList<X509Certificate2> ListCertificates(
|
||||
CertificatePurpose purpose,
|
||||
StoreName storeName,
|
||||
|
|
@ -83,7 +87,8 @@ namespace Microsoft.AspNetCore.Certificates.Generation
|
|||
var validCertificates = matchingCertificates
|
||||
.Where(c => c.NotBefore <= now &&
|
||||
now <= c.NotAfter &&
|
||||
(!requireExportable || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IsExportable(c)))
|
||||
(!requireExportable || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IsExportable(c))
|
||||
&& MatchesVersion(c))
|
||||
.ToArray();
|
||||
|
||||
var invalidCertificates = matchingCertificates.Except(validCertificates);
|
||||
|
|
@ -117,6 +122,25 @@ namespace Microsoft.AspNetCore.Certificates.Generation
|
|||
bool HasOid(X509Certificate2 certificate, string oid) =>
|
||||
certificate.Extensions.OfType<X509Extension>()
|
||||
.Any(e => string.Equals(oid, e.Oid.Value, StringComparison.Ordinal));
|
||||
|
||||
bool MatchesVersion(X509Certificate2 c)
|
||||
{
|
||||
var byteArray = c.Extensions.OfType<X509Extension>()
|
||||
.Where(e => string.Equals(AspNetHttpsOid, e.Oid.Value, StringComparison.Ordinal))
|
||||
.Single()
|
||||
.RawData;
|
||||
|
||||
if ((byteArray.Length == AspNetHttpsOidFriendlyName.Length && byteArray[0] == (byte)'A') || byteArray.Length == 0)
|
||||
{
|
||||
// No Version set, default to 0
|
||||
return 0 >= AspNetHttpsCertificateVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Version is in the only byte of the byte array.
|
||||
return byteArray[0] >= AspNetHttpsCertificateVersion;
|
||||
}
|
||||
}
|
||||
#if !XPLAT
|
||||
bool IsExportable(X509Certificate2 c) =>
|
||||
((c.GetRSAPrivateKey() is RSACryptoServiceProvider rsaPrivateKey &&
|
||||
|
|
@ -171,10 +195,22 @@ namespace Microsoft.AspNetCore.Certificates.Generation
|
|||
pathLengthConstraint: 0,
|
||||
critical: true);
|
||||
|
||||
byte[] bytePayload;
|
||||
|
||||
if (AspNetHttpsCertificateVersion != 0)
|
||||
{
|
||||
bytePayload = new byte[1];
|
||||
bytePayload[0] = (byte)AspNetHttpsCertificateVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
bytePayload = Encoding.ASCII.GetBytes(AspNetHttpsOidFriendlyName);
|
||||
}
|
||||
|
||||
var aspNetHttpsExtension = new X509Extension(
|
||||
new AsnEncodedData(
|
||||
new Oid(AspNetHttpsOid, AspNetHttpsOidFriendlyName),
|
||||
Encoding.ASCII.GetBytes(AspNetHttpsOidFriendlyName)),
|
||||
bytePayload),
|
||||
critical: false);
|
||||
|
||||
extensions.Add(basicConstraints);
|
||||
|
|
@ -633,7 +669,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation
|
|||
}
|
||||
}
|
||||
|
||||
public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate(
|
||||
public DetailedEnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate(
|
||||
DateTimeOffset notBefore,
|
||||
DateTimeOffset notAfter,
|
||||
string path = null,
|
||||
|
|
@ -645,109 +681,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation
|
|||
return EnsureValidCertificateExists(notBefore, notAfter, CertificatePurpose.HTTPS, path, trust, includePrivateKey, password, subject);
|
||||
}
|
||||
|
||||
public EnsureCertificateResult EnsureValidCertificateExists(
|
||||
DateTimeOffset notBefore,
|
||||
DateTimeOffset notAfter,
|
||||
CertificatePurpose purpose,
|
||||
string path = null,
|
||||
bool trust = false,
|
||||
bool includePrivateKey = false,
|
||||
string password = null,
|
||||
string subjectOverride = null)
|
||||
{
|
||||
if (purpose == CertificatePurpose.All)
|
||||
{
|
||||
throw new ArgumentException("The certificate must have a specific purpose.");
|
||||
}
|
||||
|
||||
var certificates = ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: true).Concat(
|
||||
ListCertificates(purpose, StoreName.My, StoreLocation.LocalMachine, isValid: true));
|
||||
|
||||
certificates = subjectOverride == null ? certificates : certificates.Where(c => c.Subject == subjectOverride);
|
||||
|
||||
var result = EnsureCertificateResult.Succeeded;
|
||||
|
||||
X509Certificate2 certificate = null;
|
||||
if (certificates.Count() > 0)
|
||||
{
|
||||
certificate = certificates.FirstOrDefault();
|
||||
result = EnsureCertificateResult.ValidCertificatePresent;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (purpose)
|
||||
{
|
||||
case CertificatePurpose.All:
|
||||
throw new InvalidOperationException("The certificate must have a specific purpose.");
|
||||
case CertificatePurpose.HTTPS:
|
||||
certificate = CreateAspNetCoreHttpsDevelopmentCertificate(notBefore, notAfter, subjectOverride);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("The certificate must have a purpose.");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return EnsureCertificateResult.ErrorCreatingTheCertificate;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
certificate = SaveCertificateInStore(certificate, StoreName.My, StoreLocation.CurrentUser);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return EnsureCertificateResult.ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore;
|
||||
}
|
||||
}
|
||||
if (path != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
ExportCertificate(certificate, path, includePrivateKey, password);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return EnsureCertificateResult.ErrorExportingTheCertificate;
|
||||
}
|
||||
}
|
||||
|
||||
if ((RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) && trust)
|
||||
{
|
||||
try
|
||||
{
|
||||
TrustCertificate(certificate);
|
||||
}
|
||||
catch (UserCancelledTrustException)
|
||||
{
|
||||
return EnsureCertificateResult.UserCancelledTrustStep;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return EnsureCertificateResult.FailedToTrustTheCertificate;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// This is just to avoid breaking changes across repos.
|
||||
// Will be renamed back to EnsureAspNetCoreHttpsDevelopmentCertificate once updates are made elsewhere.
|
||||
public DetailedEnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate2(
|
||||
DateTimeOffset notBefore,
|
||||
DateTimeOffset notAfter,
|
||||
string path = null,
|
||||
bool trust = false,
|
||||
bool includePrivateKey = false,
|
||||
string password = null,
|
||||
string subject = LocalhostHttpsDistinguishedName)
|
||||
{
|
||||
return EnsureValidCertificateExists2(notBefore, notAfter, CertificatePurpose.HTTPS, path, trust, includePrivateKey, password, subject);
|
||||
}
|
||||
|
||||
public DetailedEnsureCertificateResult EnsureValidCertificateExists2(
|
||||
public DetailedEnsureCertificateResult EnsureValidCertificateExists(
|
||||
DateTimeOffset notBefore,
|
||||
DateTimeOffset notAfter,
|
||||
CertificatePurpose purpose,
|
||||
|
|
|
|||
|
|
@ -7,17 +7,20 @@ using System.Linq;
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Testing.xunit;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Certificates.Generation.Tests
|
||||
{
|
||||
public class CertificateManagerTests
|
||||
public class CertificateManagerTests : IClassFixture<CertFixture>
|
||||
{
|
||||
public CertificateManagerTests(ITestOutputHelper output)
|
||||
private CertFixture _fixture;
|
||||
private CertificateManager _manager => _fixture.Manager;
|
||||
|
||||
public CertificateManagerTests(ITestOutputHelper output, CertFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
Output = output;
|
||||
}
|
||||
|
||||
|
|
@ -25,122 +28,31 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
|
|||
|
||||
public ITestOutputHelper Output { get; }
|
||||
|
||||
[Fact(Skip = "True")]
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")]
|
||||
public void EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Arrange
|
||||
_fixture.CleanupCertificates();
|
||||
|
||||
const string CertificateName = nameof(EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer";
|
||||
var manager = new CertificateManager();
|
||||
|
||||
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject);
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject);
|
||||
}
|
||||
|
||||
// Act
|
||||
// Act
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
||||
var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, subject: TestCertificateSubject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(EnsureCertificateResult.Succeeded, result);
|
||||
Assert.True(File.Exists(CertificateName));
|
||||
|
||||
var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName));
|
||||
Assert.NotNull(exportedCertificate);
|
||||
Assert.False(exportedCertificate.HasPrivateKey);
|
||||
|
||||
var httpsCertificates = manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false);
|
||||
var httpsCertificate = Assert.Single(httpsCertificates, c => c.Subject == TestCertificateSubject);
|
||||
Assert.True(httpsCertificate.HasPrivateKey);
|
||||
Assert.Equal(TestCertificateSubject, httpsCertificate.Subject);
|
||||
Assert.Equal(TestCertificateSubject, httpsCertificate.Issuer);
|
||||
Assert.Equal("sha256RSA", httpsCertificate.SignatureAlgorithm.FriendlyName);
|
||||
Assert.Equal("1.2.840.113549.1.1.11", httpsCertificate.SignatureAlgorithm.Value);
|
||||
|
||||
Assert.Equal(now.LocalDateTime, httpsCertificate.NotBefore);
|
||||
Assert.Equal(now.AddYears(1).LocalDateTime, httpsCertificate.NotAfter);
|
||||
Assert.Contains(
|
||||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||
e => e is X509BasicConstraintsExtension basicConstraints &&
|
||||
basicConstraints.Critical == true &&
|
||||
basicConstraints.CertificateAuthority == false &&
|
||||
basicConstraints.HasPathLengthConstraint == false &&
|
||||
basicConstraints.PathLengthConstraint == 0);
|
||||
|
||||
Assert.Contains(
|
||||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||
e => e is X509KeyUsageExtension keyUsage &&
|
||||
keyUsage.Critical == true &&
|
||||
keyUsage.KeyUsages == (X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature));
|
||||
|
||||
Assert.Contains(
|
||||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||
e => e is X509EnhancedKeyUsageExtension enhancedKeyUsage &&
|
||||
enhancedKeyUsage.Critical == true &&
|
||||
enhancedKeyUsage.EnhancedKeyUsages.OfType<Oid>().Single() is Oid keyUsage &&
|
||||
keyUsage.Value == "1.3.6.1.5.5.7.3.1");
|
||||
|
||||
// Subject alternative name
|
||||
Assert.Contains(
|
||||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||
e => e.Critical == true &&
|
||||
e.Oid.Value == "2.5.29.17");
|
||||
|
||||
// ASP.NET HTTPS Development certificate extension
|
||||
Assert.Contains(
|
||||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||
e => e.Critical == false &&
|
||||
e.Oid.Value == "1.3.6.1.4.1.311.84.1.1" &&
|
||||
Encoding.ASCII.GetString(e.RawData) == "ASP.NET Core HTTPS development certificate");
|
||||
|
||||
Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString());
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Output.WriteLine(e.Message);
|
||||
ListCertificates(Output);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")]
|
||||
public void EnsureCreateHttpsCertificate2_CreatesACertificate_WhenThereAreNoHttpsCertificates()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Arrange
|
||||
const string CertificateName = nameof(EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer";
|
||||
var manager = new CertificateManager();
|
||||
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject);
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject);
|
||||
}
|
||||
|
||||
// Act
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
||||
var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate2(now, now.AddYears(1), CertificateName, trust: false, subject: TestCertificateSubject);
|
||||
var result = _manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, subject: TestCertificateSubject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(EnsureCertificateResult.Succeeded, result.ResultCode);
|
||||
Assert.NotNull(result.Diagnostics);
|
||||
Assert.NotEmpty(result.Diagnostics.Messages);
|
||||
Assert.Empty(result.Diagnostics.Exceptions);
|
||||
|
||||
Assert.True(File.Exists(CertificateName));
|
||||
|
||||
var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName));
|
||||
Assert.NotNull(exportedCertificate);
|
||||
Assert.False(exportedCertificate.HasPrivateKey);
|
||||
|
||||
var httpsCertificates = manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false);
|
||||
var httpsCertificates = _manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false);
|
||||
var httpsCertificate = Assert.Single(httpsCertificates, c => c.Subject == TestCertificateSubject);
|
||||
Assert.True(httpsCertificate.HasPrivateKey);
|
||||
Assert.Equal(TestCertificateSubject, httpsCertificate.Subject);
|
||||
|
|
@ -182,7 +94,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
|
|||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||
e => e.Critical == false &&
|
||||
e.Oid.Value == "1.3.6.1.4.1.311.84.1.1" &&
|
||||
Encoding.ASCII.GetString(e.RawData) == "ASP.NET Core HTTPS development certificate");
|
||||
e.RawData[0] == _manager.AspNetHttpsCertificateVersion);
|
||||
|
||||
Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString());
|
||||
|
||||
|
|
@ -211,32 +123,27 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact(Skip = "true")]
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")]
|
||||
public void EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates()
|
||||
{
|
||||
// Arrange
|
||||
const string CertificateName = nameof(EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates) + ".pfx";
|
||||
var certificatePassword = Guid.NewGuid().ToString();
|
||||
|
||||
var manager = new CertificateManager();
|
||||
|
||||
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject);
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject);
|
||||
}
|
||||
_fixture.CleanupCertificates();
|
||||
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
||||
manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
||||
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
||||
|
||||
var httpsCertificate = manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Single(c => c.Subject == TestCertificateSubject);
|
||||
var httpsCertificate = _manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Single(c => c.Subject == TestCertificateSubject);
|
||||
|
||||
// Act
|
||||
var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, includePrivateKey: true, password: certificatePassword, subject: TestCertificateSubject);
|
||||
var result = _manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, includePrivateKey: true, password: certificatePassword, subject: TestCertificateSubject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(EnsureCertificateResult.ValidCertificatePresent, result);
|
||||
Assert.Equal(EnsureCertificateResult.ValidCertificatePresent, result.ResultCode);
|
||||
Assert.True(File.Exists(CertificateName));
|
||||
|
||||
var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName), certificatePassword);
|
||||
|
|
@ -247,39 +154,125 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
|
|||
Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString());
|
||||
}
|
||||
|
||||
[Fact(Skip = "Requires user interaction")]
|
||||
public void EnsureAspNetCoreHttpsDevelopmentCertificate_ReturnsCorrectResult_WhenUserCancelsTrustStepOnWindows()
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")]
|
||||
public void EnsureCreateHttpsCertificate_ReturnsExpiredCertificateIfVersionIsIncorrect()
|
||||
{
|
||||
var manager = new CertificateManager();
|
||||
|
||||
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject);
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject);
|
||||
}
|
||||
_fixture.CleanupCertificates();
|
||||
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
||||
var trustFailed = manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: true, subject: TestCertificateSubject);
|
||||
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
||||
|
||||
Assert.Equal(EnsureCertificateResult.UserCancelledTrustStep, trustFailed);
|
||||
_manager.AspNetHttpsCertificateVersion = 2;
|
||||
|
||||
var httpsCertificateList = _manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
||||
Assert.Empty(httpsCertificateList);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")]
|
||||
public void EnsureCreateHttpsCertificate_ReturnsExpiredCertificateForEmptyVersionField()
|
||||
{
|
||||
_fixture.CleanupCertificates();
|
||||
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
||||
_manager.AspNetHttpsCertificateVersion = 0;
|
||||
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
||||
|
||||
_manager.AspNetHttpsCertificateVersion = 1;
|
||||
|
||||
var httpsCertificateList = _manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
||||
Assert.Empty(httpsCertificateList);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")]
|
||||
public void EnsureCreateHttpsCertificate_ReturnsValidIfVersionIsZero()
|
||||
{
|
||||
_fixture.CleanupCertificates();
|
||||
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
||||
_manager.AspNetHttpsCertificateVersion = 0;
|
||||
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
||||
|
||||
var httpsCertificateList = _manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
||||
Assert.NotEmpty(httpsCertificateList);
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")]
|
||||
public void EnsureCreateHttpsCertificate_ReturnValidIfCertIsNewer()
|
||||
{
|
||||
_fixture.CleanupCertificates();
|
||||
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
||||
_manager.AspNetHttpsCertificateVersion = 2;
|
||||
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject);
|
||||
|
||||
_manager.AspNetHttpsCertificateVersion = 1;
|
||||
var httpsCertificateList = _manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
||||
Assert.NotEmpty(httpsCertificateList);
|
||||
}
|
||||
|
||||
[Fact(Skip = "Requires user interaction")]
|
||||
public void EnsureAspNetCoreHttpsDevelopmentCertificate_ReturnsCorrectResult_WhenUserCancelsTrustStepOnWindows()
|
||||
{
|
||||
_fixture.CleanupCertificates();
|
||||
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
||||
var trustFailed = _manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: true, subject: TestCertificateSubject);
|
||||
|
||||
Assert.Equal(EnsureCertificateResult.UserCancelledTrustStep, trustFailed.ResultCode);
|
||||
}
|
||||
|
||||
[Fact(Skip = "Requires user interaction")]
|
||||
public void EnsureAspNetCoreHttpsDevelopmentCertificate_CanRemoveCertificates()
|
||||
{
|
||||
var manager = new CertificateManager();
|
||||
_fixture.CleanupCertificates();
|
||||
|
||||
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset);
|
||||
manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: true, subject: TestCertificateSubject);
|
||||
_manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: true, subject: TestCertificateSubject);
|
||||
|
||||
manager.CleanupHttpsCertificates(TestCertificateSubject);
|
||||
_manager.CleanupHttpsCertificates(TestCertificateSubject);
|
||||
|
||||
Assert.Empty(manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject));
|
||||
Assert.Empty(_manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject));
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
Assert.Empty(manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject));
|
||||
Assert.Empty(_manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CertFixture : IDisposable
|
||||
{
|
||||
public const string TestCertificateSubject = "CN=aspnet.test";
|
||||
|
||||
public CertFixture()
|
||||
{
|
||||
Manager = new CertificateManager();
|
||||
|
||||
CleanupCertificates();
|
||||
}
|
||||
|
||||
internal CertificateManager Manager { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CleanupCertificates();
|
||||
}
|
||||
|
||||
internal void CleanupCertificates()
|
||||
{
|
||||
Manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject);
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
Manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-watch", "dotnet-watc
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-watch.Tests", "dotnet-watch\test\dotnet-watch.Tests.csproj", "{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-dev-certs", "dotnet-dev-certs\src\dotnet-dev-certs.csproj", "{0D6D5693-7E0C-4FE8-B4AA-21207B2650AA}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DeveloperCertificates.XPlat", "FirstRunCertGenerator\src\Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj", "{7BBDBDA2-299F-4C36-8338-23C525901DE0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DeveloperCertificates.XPlat.Tests", "FirstRunCertGenerator\test\Microsoft.AspNetCore.DeveloperCertificates.XPlat.Tests.csproj", "{1EC6FA27-40A5-433F-8CA1-636E7ED8863E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -21,6 +27,18 @@ Global
|
|||
{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0D6D5693-7E0C-4FE8-B4AA-21207B2650AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0D6D5693-7E0C-4FE8-B4AA-21207B2650AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0D6D5693-7E0C-4FE8-B4AA-21207B2650AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0D6D5693-7E0C-4FE8-B4AA-21207B2650AA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7BBDBDA2-299F-4C36-8338-23C525901DE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7BBDBDA2-299F-4C36-8338-23C525901DE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7BBDBDA2-299F-4C36-8338-23C525901DE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7BBDBDA2-299F-4C36-8338-23C525901DE0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1EC6FA27-40A5-433F-8CA1-636E7ED8863E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1EC6FA27-40A5-433F-8CA1-636E7ED8863E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1EC6FA27-40A5-433F-8CA1-636E7ED8863E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1EC6FA27-40A5-433F-8CA1-636E7ED8863E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ namespace Microsoft.AspNetCore.DeveloperCertificates.Tools
|
|||
(check.HasValue() && (exportPath.HasValue() || password.HasValue() || clean.HasValue())))
|
||||
{
|
||||
reporter.Error(@"Incompatible set of flags. Sample usages
|
||||
'dotnet dev-certs https'
|
||||
'dotnet dev-certs https --clean'
|
||||
'dotnet dev-certs https --check --trust'
|
||||
'dotnet dev-certs https -ep ./certificate.pfx -p password --trust'");
|
||||
|
|
@ -133,7 +134,7 @@ namespace Microsoft.AspNetCore.DeveloperCertificates.Tools
|
|||
}
|
||||
|
||||
manager.CleanupHttpsCertificates();
|
||||
reporter.Verbose("HTTPS development certificates successfully removed from the machine.");
|
||||
reporter.Output("HTTPS development certificates successfully removed from the machine.");
|
||||
return Success;
|
||||
}
|
||||
catch(Exception e)
|
||||
|
|
@ -152,12 +153,12 @@ namespace Microsoft.AspNetCore.DeveloperCertificates.Tools
|
|||
var certificates = certificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
|
||||
if (certificates.Count == 0)
|
||||
{
|
||||
reporter.Verbose("No valid certificate found.");
|
||||
reporter.Output("No valid certificate found.");
|
||||
return ErrorNoValidCertificateFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
reporter.Verbose("A valid certificate was found.");
|
||||
reporter.Output("A valid certificate was found.");
|
||||
}
|
||||
|
||||
if (trust != null && trust.HasValue())
|
||||
|
|
@ -166,13 +167,13 @@ namespace Microsoft.AspNetCore.DeveloperCertificates.Tools
|
|||
var trustedCertificates = certificateManager.ListCertificates(CertificatePurpose.HTTPS, store, StoreLocation.CurrentUser, isValid: true);
|
||||
if (!certificates.Any(c => certificateManager.IsTrusted(c)))
|
||||
{
|
||||
reporter.Verbose($@"The following certificates were found, but none of them is trusted:
|
||||
reporter.Output($@"The following certificates were found, but none of them is trusted:
|
||||
{string.Join(Environment.NewLine, certificates.Select(c => $"{c.Subject} - {c.Thumbprint}"))}");
|
||||
return ErrorCertificateNotTrusted;
|
||||
}
|
||||
else
|
||||
{
|
||||
reporter.Verbose("A trusted certificate was found.");
|
||||
reporter.Output("A trusted certificate was found.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -207,7 +208,9 @@ namespace Microsoft.AspNetCore.DeveloperCertificates.Tools
|
|||
password.HasValue(),
|
||||
password.Value());
|
||||
|
||||
switch (result)
|
||||
reporter.Verbose(string.Join(Environment.NewLine, result.Diagnostics.Messages));
|
||||
|
||||
switch (result.ResultCode)
|
||||
{
|
||||
case EnsureCertificateResult.Succeeded:
|
||||
reporter.Output("The HTTPS developer certificate was generated successfully.");
|
||||
|
|
|
|||
Loading…
Reference in New Issue