Add versioning to dotnet-dev-certs (#10908)

This commit is contained in:
Justin Kotalik 2019-06-05 22:04:27 -07:00 committed by GitHub
parent 5b56de966e
commit fdba8a91f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 190 additions and 242 deletions

View File

@ -42,6 +42,10 @@ namespace Microsoft.AspNetCore.Certificates.Generation
private static readonly string MacOSTrustCertificateCommandLineArguments = "security add-trusted-cert -d -r trustRoot -k " + MacOSSystemKeyChain + " "; private static readonly string MacOSTrustCertificateCommandLineArguments = "security add-trusted-cert -d -r trustRoot -k " + MacOSSystemKeyChain + " ";
private const int UserCancelledErrorCode = 1223; 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( public IList<X509Certificate2> ListCertificates(
CertificatePurpose purpose, CertificatePurpose purpose,
StoreName storeName, StoreName storeName,
@ -83,7 +87,8 @@ namespace Microsoft.AspNetCore.Certificates.Generation
var validCertificates = matchingCertificates var validCertificates = matchingCertificates
.Where(c => c.NotBefore <= now && .Where(c => c.NotBefore <= now &&
now <= c.NotAfter && now <= c.NotAfter &&
(!requireExportable || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IsExportable(c))) (!requireExportable || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IsExportable(c))
&& MatchesVersion(c))
.ToArray(); .ToArray();
var invalidCertificates = matchingCertificates.Except(validCertificates); var invalidCertificates = matchingCertificates.Except(validCertificates);
@ -117,6 +122,25 @@ namespace Microsoft.AspNetCore.Certificates.Generation
bool HasOid(X509Certificate2 certificate, string oid) => bool HasOid(X509Certificate2 certificate, string oid) =>
certificate.Extensions.OfType<X509Extension>() certificate.Extensions.OfType<X509Extension>()
.Any(e => string.Equals(oid, e.Oid.Value, StringComparison.Ordinal)); .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 #if !XPLAT
bool IsExportable(X509Certificate2 c) => bool IsExportable(X509Certificate2 c) =>
((c.GetRSAPrivateKey() is RSACryptoServiceProvider rsaPrivateKey && ((c.GetRSAPrivateKey() is RSACryptoServiceProvider rsaPrivateKey &&
@ -171,10 +195,22 @@ namespace Microsoft.AspNetCore.Certificates.Generation
pathLengthConstraint: 0, pathLengthConstraint: 0,
critical: true); 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( var aspNetHttpsExtension = new X509Extension(
new AsnEncodedData( new AsnEncodedData(
new Oid(AspNetHttpsOid, AspNetHttpsOidFriendlyName), new Oid(AspNetHttpsOid, AspNetHttpsOidFriendlyName),
Encoding.ASCII.GetBytes(AspNetHttpsOidFriendlyName)), bytePayload),
critical: false); critical: false);
extensions.Add(basicConstraints); extensions.Add(basicConstraints);
@ -633,7 +669,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation
} }
} }
public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate( public DetailedEnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate(
DateTimeOffset notBefore, DateTimeOffset notBefore,
DateTimeOffset notAfter, DateTimeOffset notAfter,
string path = null, string path = null,
@ -645,109 +681,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation
return EnsureValidCertificateExists(notBefore, notAfter, CertificatePurpose.HTTPS, path, trust, includePrivateKey, password, subject); return EnsureValidCertificateExists(notBefore, notAfter, CertificatePurpose.HTTPS, path, trust, includePrivateKey, password, subject);
} }
public EnsureCertificateResult EnsureValidCertificateExists( public DetailedEnsureCertificateResult 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(
DateTimeOffset notBefore, DateTimeOffset notBefore,
DateTimeOffset notAfter, DateTimeOffset notAfter,
CertificatePurpose purpose, CertificatePurpose purpose,

View File

@ -7,17 +7,20 @@ using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text;
using Microsoft.AspNetCore.Testing.xunit; using Microsoft.AspNetCore.Testing.xunit;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Certificates.Generation.Tests 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; Output = output;
} }
@ -25,122 +28,31 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
public ITestOutputHelper Output { get; } public ITestOutputHelper Output { get; }
[Fact(Skip = "True")] [ConditionalFact]
[SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")]
public void EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates() public void EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates()
{ {
try try
{ {
// Arrange // Arrange
_fixture.CleanupCertificates();
const string CertificateName = nameof(EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer"; const string CertificateName = nameof(EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer";
var manager = new CertificateManager();
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); // Act
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject);
}
// Act
DateTimeOffset now = DateTimeOffset.UtcNow; DateTimeOffset now = DateTimeOffset.UtcNow;
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); 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); 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);
// Assert // Assert
Assert.Equal(EnsureCertificateResult.Succeeded, result.ResultCode); 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)); Assert.True(File.Exists(CertificateName));
var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName)); var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName));
Assert.NotNull(exportedCertificate); Assert.NotNull(exportedCertificate);
Assert.False(exportedCertificate.HasPrivateKey); 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); var httpsCertificate = Assert.Single(httpsCertificates, c => c.Subject == TestCertificateSubject);
Assert.True(httpsCertificate.HasPrivateKey); Assert.True(httpsCertificate.HasPrivateKey);
Assert.Equal(TestCertificateSubject, httpsCertificate.Subject); Assert.Equal(TestCertificateSubject, httpsCertificate.Subject);
@ -182,7 +94,7 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
httpsCertificate.Extensions.OfType<X509Extension>(), httpsCertificate.Extensions.OfType<X509Extension>(),
e => e.Critical == false && e => e.Critical == false &&
e.Oid.Value == "1.3.6.1.4.1.311.84.1.1" && 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()); 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() public void EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates()
{ {
// Arrange // Arrange
const string CertificateName = nameof(EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates) + ".pfx"; const string CertificateName = nameof(EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates) + ".pfx";
var certificatePassword = Guid.NewGuid().ToString(); var certificatePassword = Guid.NewGuid().ToString();
var manager = new CertificateManager(); _fixture.CleanupCertificates();
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject);
}
DateTimeOffset now = DateTimeOffset.UtcNow; DateTimeOffset now = DateTimeOffset.UtcNow;
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); 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 // 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
Assert.Equal(EnsureCertificateResult.ValidCertificatePresent, result); Assert.Equal(EnsureCertificateResult.ValidCertificatePresent, result.ResultCode);
Assert.True(File.Exists(CertificateName)); Assert.True(File.Exists(CertificateName));
var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName), certificatePassword); var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName), certificatePassword);
@ -247,39 +154,125 @@ namespace Microsoft.AspNetCore.Certificates.Generation.Tests
Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString()); Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString());
} }
[Fact(Skip = "Requires user interaction")] [ConditionalFact]
public void EnsureAspNetCoreHttpsDevelopmentCertificate_ReturnsCorrectResult_WhenUserCancelsTrustStepOnWindows() [SkipOnHelix("https://github.com/aspnet/AspNetCore/issues/6721")]
public void EnsureCreateHttpsCertificate_ReturnsExpiredCertificateIfVersionIsIncorrect()
{ {
var manager = new CertificateManager(); _fixture.CleanupCertificates();
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject);
}
DateTimeOffset now = DateTimeOffset.UtcNow; DateTimeOffset now = DateTimeOffset.UtcNow;
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); 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")] [Fact(Skip = "Requires user interaction")]
public void EnsureAspNetCoreHttpsDevelopmentCertificate_CanRemoveCertificates() public void EnsureAspNetCoreHttpsDevelopmentCertificate_CanRemoveCertificates()
{ {
var manager = new CertificateManager(); _fixture.CleanupCertificates();
DateTimeOffset now = DateTimeOffset.UtcNow; DateTimeOffset now = DateTimeOffset.UtcNow;
now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); 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)) 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);
} }
} }
} }

View File

@ -7,6 +7,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-watch", "dotnet-watc
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-watch.Tests", "dotnet-watch\test\dotnet-watch.Tests.csproj", "{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-watch.Tests", "dotnet-watch\test\dotnet-watch.Tests.csproj", "{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{63F7E822-D1E2-4C41-8ABF-60B9E3A9C54C}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -81,6 +81,7 @@ namespace Microsoft.AspNetCore.DeveloperCertificates.Tools
(check.HasValue() && (exportPath.HasValue() || password.HasValue() || clean.HasValue()))) (check.HasValue() && (exportPath.HasValue() || password.HasValue() || clean.HasValue())))
{ {
reporter.Error(@"Incompatible set of flags. Sample usages reporter.Error(@"Incompatible set of flags. Sample usages
'dotnet dev-certs https'
'dotnet dev-certs https --clean' 'dotnet dev-certs https --clean'
'dotnet dev-certs https --check --trust' 'dotnet dev-certs https --check --trust'
'dotnet dev-certs https -ep ./certificate.pfx -p password --trust'"); 'dotnet dev-certs https -ep ./certificate.pfx -p password --trust'");
@ -133,7 +134,7 @@ namespace Microsoft.AspNetCore.DeveloperCertificates.Tools
} }
manager.CleanupHttpsCertificates(); manager.CleanupHttpsCertificates();
reporter.Verbose("HTTPS development certificates successfully removed from the machine."); reporter.Output("HTTPS development certificates successfully removed from the machine.");
return Success; return Success;
} }
catch(Exception e) catch(Exception e)
@ -152,12 +153,12 @@ namespace Microsoft.AspNetCore.DeveloperCertificates.Tools
var certificates = certificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true); var certificates = certificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true);
if (certificates.Count == 0) if (certificates.Count == 0)
{ {
reporter.Verbose("No valid certificate found."); reporter.Output("No valid certificate found.");
return ErrorNoValidCertificateFound; return ErrorNoValidCertificateFound;
} }
else else
{ {
reporter.Verbose("A valid certificate was found."); reporter.Output("A valid certificate was found.");
} }
if (trust != null && trust.HasValue()) 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); var trustedCertificates = certificateManager.ListCertificates(CertificatePurpose.HTTPS, store, StoreLocation.CurrentUser, isValid: true);
if (!certificates.Any(c => certificateManager.IsTrusted(c))) 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}"))}"); {string.Join(Environment.NewLine, certificates.Select(c => $"{c.Subject} - {c.Thumbprint}"))}");
return ErrorCertificateNotTrusted; return ErrorCertificateNotTrusted;
} }
else 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.HasValue(),
password.Value()); password.Value());
switch (result) reporter.Verbose(string.Join(Environment.NewLine, result.Diagnostics.Messages));
switch (result.ResultCode)
{ {
case EnsureCertificateResult.Succeeded: case EnsureCertificateResult.Succeeded:
reporter.Output("The HTTPS developer certificate was generated successfully."); reporter.Output("The HTTPS developer certificate was generated successfully.");