diff --git a/DotNetTools.sln b/DotNetTools.sln index 8e317e7fc0..d3c6048370 100644 --- a/DotNetTools.sln +++ b/DotNetTools.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26815.3 +VisualStudioVersion = 15.0.26927.1 MinimumVisualStudioVersion = 15.0.26730.03 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{66517987-2A5A-4330-B130-207039378FD4}" ProjectSection(SolutionItems) = preProject @@ -44,9 +44,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Watcher.To EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Caching.SqlConfig.Tools", "src\Microsoft.Extensions.Caching.SqlConfig.Tools\Microsoft.Extensions.Caching.SqlConfig.Tools.csproj", "{53F3B53D-303A-4DAA-9C38-4F55195FA5B9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.CertificateGeneration.Task", "src\Microsoft.AspNetCore.CertificateGeneration.Task\Microsoft.AspNetCore.CertificateGeneration.Task.csproj", "{7B293291-26F4-47F0-9C2F-E396F35A4280}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.CertificateGeneration.Task.Tests", "test\Microsoft.AspNetcore.CertificateGeneration.Task.Tests\Microsoft.AspNetCore.CertificateGeneration.Task.Tests.csproj", "{3A7EF01A-073B-4123-850D-DFA4701EBE5B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.DeveloperCertificates.Tools", "src\Microsoft.AspNetCore.DeveloperCertificates.Tools\Microsoft.AspNetCore.DeveloperCertificates.Tools.csproj", "{4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -78,14 +76,10 @@ Global {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.Release|Any CPU.Build.0 = Release|Any CPU - {7B293291-26F4-47F0-9C2F-E396F35A4280}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7B293291-26F4-47F0-9C2F-E396F35A4280}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7B293291-26F4-47F0-9C2F-E396F35A4280}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7B293291-26F4-47F0-9C2F-E396F35A4280}.Release|Any CPU.Build.0 = Release|Any CPU - {3A7EF01A-073B-4123-850D-DFA4701EBE5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3A7EF01A-073B-4123-850D-DFA4701EBE5B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3A7EF01A-073B-4123-850D-DFA4701EBE5B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3A7EF01A-073B-4123-850D-DFA4701EBE5B}.Release|Any CPU.Build.0 = Release|Any CPU + {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -97,8 +91,7 @@ Global {7B331122-83B1-4F08-A119-DC846959844C} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134} {8A2E6961-6B12-4A8E-8215-3E7301D52EAC} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134} {53F3B53D-303A-4DAA-9C38-4F55195FA5B9} = {66517987-2A5A-4330-B130-207039378FD4} - {7B293291-26F4-47F0-9C2F-E396F35A4280} = {66517987-2A5A-4330-B130-207039378FD4} - {3A7EF01A-073B-4123-850D-DFA4701EBE5B} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134} + {4FED5119-EE5C-4753-88A4-D61BDEB4D6C8} = {66517987-2A5A-4330-B130-207039378FD4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {57C07F14-2EAC-44FF-A277-B9221B4B2BF7} diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index 214ca77a9c..d5e919176f 100644 --- a/NuGetPackageVerifier.json +++ b/NuGetPackageVerifier.json @@ -19,12 +19,10 @@ "DotnetCliTool" ] }, - "Microsoft.AspNetCore.CertificateGeneration.Task": { - "exclusions":{ - "BUILD_ITEMS_FRAMEWORK": { - "*": "This is an MSBuild task intended to run through dotnet msbuild /t:Target independently of whether your project targets full framework or .net core." - } - } + "Microsoft.AspNetCore.DeveloperCertificates.Tools": { + "packageTypes": [ + "DotnetCliTool" + ] } } }, diff --git a/src/Microsoft.AspNetCore.CertificateGeneration.Task/CertificateManager.cs b/src/Microsoft.AspNetCore.CertificateGeneration.Task/CertificateManager.cs deleted file mode 100644 index e53c6b8743..0000000000 --- a/src/Microsoft.AspNetCore.CertificateGeneration.Task/CertificateManager.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; - -namespace Microsoft.AspNetCore.CertificateGeneration.Task -{ - internal static class CertificateManager - { - public static X509Certificate2 GenerateSSLCertificate( - string subjectName, - IEnumerable subjectAlternativeName, - string friendlyName, - DateTimeOffset notBefore, - DateTimeOffset expires, - StoreName storeName, - StoreLocation storeLocation) - { - using (var rsa = RSA.Create(2048)) - { - var signingRequest = new CertificateRequest( - new X500DistinguishedName(subjectName), rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - - var enhancedKeyUsage = new OidCollection(); - enhancedKeyUsage.Add(new Oid("1.3.6.1.5.5.7.3.1", "Server Authentication")); - signingRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(enhancedKeyUsage, critical: true)); - signingRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true)); - signingRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension( - certificateAuthority: false, - hasPathLengthConstraint: false, - pathLengthConstraint: 0, - critical: true)); - - var sanBuilder = new SubjectAlternativeNameBuilder(); - foreach (var alternativeName in subjectAlternativeName) - { - sanBuilder.AddDnsName(alternativeName); - } - signingRequest.CertificateExtensions.Add(sanBuilder.Build()); - - var certificate = signingRequest.CreateSelfSigned(notBefore, expires); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - certificate.FriendlyName = friendlyName; - } - - SaveCertificate(storeName, storeLocation, certificate); - - return certificate; - } - } - - private static void SaveCertificate(StoreName storeName, StoreLocation storeLocation, X509Certificate2 certificate) - { - // We need to take this step so that the key gets persisted. - var imported = certificate; - if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - var export = certificate.Export(X509ContentType.Pkcs12, ""); - imported = new X509Certificate2(export, "", X509KeyStorageFlags.PersistKeySet); - Array.Clear(export, 0, export.Length); - } - - using (var store = new X509Store(storeName, storeLocation)) - { - store.Open(OpenFlags.ReadWrite); - store.Add(imported); - store.Close(); - }; - } - - public static X509Certificate2 FindCertificate(string subjectValue, StoreName storeName, StoreLocation storeLocation) - { - using (var store = new X509Store(storeName, storeLocation)) - { - store.Open(OpenFlags.ReadOnly); - var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, subjectValue, validOnly: false); - var current = DateTimeOffset.UtcNow; - - var found = certificates.OfType() - .Where(c => c.NotBefore <= current && current <= c.NotAfter && c.HasPrivateKey) - .FirstOrDefault(); - store.Close(); - - return found; - }; - } - } -} diff --git a/src/Microsoft.AspNetCore.CertificateGeneration.Task/GenerateSSLCertificateTask.cs b/src/Microsoft.AspNetCore.CertificateGeneration.Task/GenerateSSLCertificateTask.cs deleted file mode 100644 index 30ccf8bacb..0000000000 --- a/src/Microsoft.AspNetCore.CertificateGeneration.Task/GenerateSSLCertificateTask.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; -using Microsoft.Build.Framework; - -namespace Microsoft.AspNetCore.CertificateGeneration.Task -{ - public class GenerateSSLCertificateTask : Build.Utilities.Task - { - public bool Force { get; set; } - - protected string Subject { get; set; } = "CN=localhost"; - - public override bool Execute() - { - var subjectValue = Subject; - var sansValue = new List { "localhost" }; - var friendlyNameValue = "ASP.NET Core HTTPS development certificate"; - var notBeforeValue = DateTime.UtcNow; - var expiresValue = DateTime.UtcNow.AddYears(1); - var storeNameValue = StoreName.My; - var storeLocationValue = StoreLocation.CurrentUser; - - var cert = CertificateManager.FindCertificate(subjectValue, storeNameValue, storeLocationValue); - - if (cert != null && !Force) - { - LogMessage($"A certificate with subject name '{Subject}' already exists. Skipping certificate generation."); - return true; - } - - var generated = CertificateManager.GenerateSSLCertificate(subjectValue, sansValue, friendlyNameValue, notBeforeValue, expiresValue, storeNameValue, storeLocationValue); - LogMessage($"Generated certificate {generated.SubjectName.Name} - {generated.Thumbprint} - {generated.FriendlyName}"); - - return true; - } - - protected virtual void LogMessage(string message) => Log.LogMessage(MessageImportance.High, message); - } -} diff --git a/src/Microsoft.AspNetCore.CertificateGeneration.Task/Microsoft.AspNetCore.CertificateGeneration.Task.csproj b/src/Microsoft.AspNetCore.CertificateGeneration.Task/Microsoft.AspNetCore.CertificateGeneration.Task.csproj deleted file mode 100644 index 5c1dc7f13d..0000000000 --- a/src/Microsoft.AspNetCore.CertificateGeneration.Task/Microsoft.AspNetCore.CertificateGeneration.Task.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - netcoreapp2.0 - MSBuild target for generating HTTPS certificates for development cross-platform. - tools - asp.net;ssl;certificates - - - - - - - - - - - diff --git a/src/Microsoft.AspNetCore.CertificateGeneration.Task/README.md b/src/Microsoft.AspNetCore.CertificateGeneration.Task/README.md deleted file mode 100644 index 4dfea325ab..0000000000 --- a/src/Microsoft.AspNetCore.CertificateGeneration.Task/README.md +++ /dev/null @@ -1,48 +0,0 @@ -Microsoft.AspNetCore.CertificateGeneration.Task -=============================================== -Microsoft.AspNetCore.CertificateGeneration.Task is an MSBuild task to generate SSL certificates -for use in ASP.NET Core for development purposes. - -### How To Install - -Install `Microsoft.AspNetCore.CertificateGeneration.Task` as a `PackageReference` to your project. - -```xml - - - -``` - -### How To Use - -The command must be executed in the directory that contains the project with the reference to the package. - - Usage: dotnet msbuild /t:GenerateSSLCertificate [/p:ForceGenerateSSLCertificate=true] - -### Testing scenarios - -On a machine without an SSL certificate generated by this task. Create a netcoreapp2.0 mvc application using - -``` -dotnet new mvc -``` - -Then try to run the app using: - -``` -dotnet run -``` - -When the application fails to run due to a missing SSL certificate. Run: - -``` -dotnet msbuild /t:GenerateSSLCertificate -``` - -Run the application again using: - -``` -dotnet run -``` - -The application should run successfully. You will still have to trust the certificate as a separate step. diff --git a/src/Microsoft.AspNetCore.CertificateGeneration.Task/build/Microsoft.AspNetCore.CertificateGeneration.Task.targets b/src/Microsoft.AspNetCore.CertificateGeneration.Task/build/Microsoft.AspNetCore.CertificateGeneration.Task.targets deleted file mode 100644 index 1deeb55dbb..0000000000 --- a/src/Microsoft.AspNetCore.CertificateGeneration.Task/build/Microsoft.AspNetCore.CertificateGeneration.Task.targets +++ /dev/null @@ -1,20 +0,0 @@ - - - - - <_SSLCertificateGenerationTaskAssembly>$(MSBuildThisFileDirectory)..\tools\netcoreapp2.0\Microsoft.AspNetCore.CertificateGeneration.Task.dll - - - - - - - - diff --git a/src/Microsoft.AspNetCore.DeveloperCertificates.Tools/Microsoft.AspNetCore.DeveloperCertificates.Tools.csproj b/src/Microsoft.AspNetCore.DeveloperCertificates.Tools/Microsoft.AspNetCore.DeveloperCertificates.Tools.csproj new file mode 100644 index 0000000000..438e88f121 --- /dev/null +++ b/src/Microsoft.AspNetCore.DeveloperCertificates.Tools/Microsoft.AspNetCore.DeveloperCertificates.Tools.csproj @@ -0,0 +1,35 @@ + + + + netcoreapp2.0 + dotnet-developercertificates + exe + Command line tool to generate certificates used in ASP.NET Core during development. + Microsoft.AspNetCore.DeveloperCertificates.Tools + dotnet;developercertificates + DotnetCliTool + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.AspNetCore.DeveloperCertificates.Tools/Program.cs b/src/Microsoft.AspNetCore.DeveloperCertificates.Tools/Program.cs new file mode 100644 index 0000000000..f07f7bbe3e --- /dev/null +++ b/src/Microsoft.AspNetCore.DeveloperCertificates.Tools/Program.cs @@ -0,0 +1,144 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography.X509Certificates; +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Certificates.Generation; +using Microsoft.Extensions.CommandLineUtils; +using Microsoft.Extensions.Tools.Internal; + +namespace Microsoft.AspNetCore.DeveloperCertificates.Tools +{ + class Program + { + private const int CriticalError = -1; + private const int Success = 0; + private const int ErrorCreatingTheCertificate = 1; + private const int ErrorSavingTheCertificate = 2; + private const int ErrorExportingTheCertificate = 3; + private const int ErrorTrustingTheCertificate = 4; + + public static readonly TimeSpan HttpsCertificateValidity = TimeSpan.FromDays(365); + + public static int Main(string[] args) + { + try + { + var app = new CommandLineApplication + { + Name = "dotnet-developercertificates" + }; + + app.Command("https", c => + { + var exportPath = c.Option("-ep|--export-path", + "Full path to the exported certificate", + CommandOptionType.SingleValue); + + var password = c.Option("-p|--password", + "Password to use when exporting the certificate with the private key into a pfx file", + CommandOptionType.SingleValue); + + CommandOption trust = null; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + trust = c.Option("-t|--trust", + "Trust the certificate on the current platform", + CommandOptionType.NoValue); + } + + var verbose = c.Option("-v|--verbose", + "Display more debug information.", + CommandOptionType.NoValue); + + var quiet = c.Option("-q|--quiet", + "Display warnings and errors only.", + CommandOptionType.NoValue); + + c.HelpOption("-h|--help"); + + c.OnExecute(() => + { + var reporter = new ConsoleReporter(PhysicalConsole.Singleton, verbose.HasValue(), quiet.HasValue()); + return EnsureHttpsCertificate(exportPath, password, trust, reporter); + }); + }); + + app.HelpOption("-h|--help"); + + app.OnExecute(() => + { + app.ShowHelp(); + return Success; + }); + + return app.Execute(args); + } + catch + { + return CriticalError; + } + } + + private static int EnsureHttpsCertificate(CommandOption exportPath, CommandOption password, CommandOption trust, IReporter reporter) + { + var now = DateTimeOffset.Now; + var manager = new CertificateManager(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && trust?.HasValue() == true) + { + reporter.Warn("Trusting the HTTPS development certificate was requested. If the certificate is not " + + "already trusted we will run the following command:" + Environment.NewLine + + "'sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain <>'" + + Environment.NewLine + "This command might prompt you for your password to install the certificate " + + "on the system keychain."); + } + + var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate( + now, + now.Add(HttpsCertificateValidity), + exportPath.Value(), + trust == null ? false : trust.HasValue(), + password.HasValue(), + password.Value()); + + switch (result) + { + case EnsureCertificateResult.Succeeded: + reporter.Output("The HTTPS developer certificate was generated successfully."); + if (exportPath.Value() != null) + { + reporter.Verbose($"The certificate was exported to {Path.GetFullPath(exportPath.Value())}"); + } + return Success; + case EnsureCertificateResult.ValidCertificatePresent: + reporter.Output("A valid HTTPS certificate is already present."); + if (exportPath.Value() != null) + { + reporter.Verbose($"The certificate was exported to {Path.GetFullPath(exportPath.Value())}"); + } + return Success; + case EnsureCertificateResult.ErrorCreatingTheCertificate: + reporter.Error("There was an error creating the HTTPS developer certificate."); + return ErrorCreatingTheCertificate; + case EnsureCertificateResult.ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore: + reporter.Error("There was an error saving the HTTPS developer certificate to the current user personal certificate store."); + return ErrorSavingTheCertificate; + case EnsureCertificateResult.ErrorExportingTheCertificate: + reporter.Warn("There was an error exporting HTTPS developer certificate to a file."); + return ErrorExportingTheCertificate; + case EnsureCertificateResult.FailedToTrustTheCertificate: + reporter.Warn("There was an error trusting HTTPS developer certificate."); + return ErrorTrustingTheCertificate; + default: + reporter.Error("Something went wrong. The HTTPS developer certificate could not be created."); + return CriticalError; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.DeveloperCertificates.Tools/prefercliruntime b/src/Microsoft.AspNetCore.DeveloperCertificates.Tools/prefercliruntime new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/Microsoft.AspNetcore.CertificateGeneration.Task.Tests/GenerateSSLCertificateTaskTest.cs b/test/Microsoft.AspNetcore.CertificateGeneration.Task.Tests/GenerateSSLCertificateTaskTest.cs deleted file mode 100644 index 3ed7ef69db..0000000000 --- a/test/Microsoft.AspNetcore.CertificateGeneration.Task.Tests/GenerateSSLCertificateTaskTest.cs +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using Xunit; - -namespace Microsoft.AspNetCore.CertificateGeneration.Task -{ - public class GenerateSSLCertificateTaskTest : IDisposable - { - private const string TestSubject = "CN=test.ssl.localhost"; - - [Fact] - public void GenerateSSLCertificateTaskTest_CreatesCertificate_IfNoCertificateIsFound() - { - // Arrange - EnsureCleanUp(); - var task = new TestGenerateSSLCertificateTask(); - - // Act - var result = task.Execute(); - - // Assert - Assert.True(result); - var certificates = GetTestCertificates(); - Assert.Single(certificates); - Assert.Single(task.Messages); - Assert.StartsWith($"Generated certificate {TestSubject}", task.Messages[0]); - } - - [Fact] - public void GenerateSSLCertificateTaskTest_CreatesCertificate_IfFoundCertificateHasExpired() - { - // Arrange - EnsureCleanUp(); - CreateCertificate(notBefore: DateTimeOffset.UtcNow.AddYears(-2), expires: DateTimeOffset.UtcNow.AddYears(-1)); - - var task = new TestGenerateSSLCertificateTask(); - - // Act - var result = task.Execute(); - - // Assert - Assert.True(result); - var certificates = GetTestCertificates(); - Assert.Equal(2, certificates.Count); - Assert.Single(task.Messages); - Assert.StartsWith($"Generated certificate {TestSubject}", task.Messages[0]); - } - - [Fact] - public void GenerateSSLCertificateTaskTest_CreatesCertificate_IfFoundCertificateIsNotYetValid() - { - // Arrange - EnsureCleanUp(); - CreateCertificate(notBefore: DateTimeOffset.UtcNow.AddYears(1), expires: DateTimeOffset.UtcNow.AddYears(2)); - - var task = new TestGenerateSSLCertificateTask(); - - // Act - var result = task.Execute(); - - // Assert - Assert.True(result); - var certificates = GetTestCertificates(); - Assert.Equal(2, certificates.Count); - Assert.Equal(1, task.Messages.Count); - Assert.StartsWith($"Generated certificate {TestSubject}", task.Messages[0]); - } - - [Fact] - public void GenerateSSLCertificateTaskTest_CreatesCertificate_IfFoundCertificateDoesNotHavePrivateKeys() - { - // Arrange - EnsureCleanUp(); - CreateCertificate(savePrivateKey: false); - var task = new TestGenerateSSLCertificateTask(); - - // Act - var result = task.Execute(); - - // Assert - Assert.True(result); - var certificates = GetTestCertificates(); - Assert.Equal(2, certificates.Count); - Assert.Single(task.Messages); - Assert.StartsWith($"Generated certificate {TestSubject}", task.Messages[0]); - } - - [Fact] - public void GenerateSSLCertificateTaskTest_DoesNothing_IfValidCertificateIsFound() - { - // Arrange - EnsureCleanUp(); - CreateCertificate(); - var task = new TestGenerateSSLCertificateTask(); - - // Act - var result = task.Execute(); - - // Assert - Assert.True(result); - var certificates = GetTestCertificates(); - Assert.Single(certificates); - Assert.Single(task.Messages); - Assert.Equal($"A certificate with subject name '{TestSubject}' already exists. Skipping certificate generation.", task.Messages[0]); - } - - [Fact] - public void GenerateSSLCertificateTaskTest_CreatesACertificateWhenThereIsAlreadyAValidCertificate_IfForceIsSpecified() - { - // Arrange - EnsureCleanUp(); - CreateCertificate(); - var task = new TestGenerateSSLCertificateTask() { Force = true }; - - // Act - var result = task.Execute(); - - // Assert - Assert.True(result); - var certificates = GetTestCertificates(); - Assert.Equal(2, certificates.Count); - Assert.Single(task.Messages); - Assert.StartsWith($"Generated certificate {TestSubject}", task.Messages[0]); - } - - public X509CertificateCollection GetTestCertificates() - { - using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) - { - store.Open(OpenFlags.ReadWrite); - var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, TestSubject, validOnly: false); - store.Close(); - - return certificates; - } - } - - private void EnsureCleanUp() - { - using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) - { - store.Open(OpenFlags.ReadWrite); - var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, TestSubject, validOnly: false); - store.RemoveRange(certificates); - store.Close(); - } - } - - public void Dispose() - { - EnsureCleanUp(); - } - - private void CreateCertificate( - DateTimeOffset notBefore = default(DateTimeOffset), - DateTimeOffset expires = default(DateTimeOffset), - bool savePrivateKey = true) - { - using (var rsa = RSA.Create(2048)) - { - var signingRequest = new CertificateRequest( - new X500DistinguishedName(TestSubject), rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - - var enhancedKeyUsage = new OidCollection - { - new Oid("1.3.6.1.5.5.7.3.1", "Server Authentication") - }; - signingRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(enhancedKeyUsage, critical: true)); - signingRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true)); - signingRequest.CertificateExtensions.Add( - new X509BasicConstraintsExtension( - certificateAuthority: false, - hasPathLengthConstraint: false, - pathLengthConstraint: 0, - critical: true)); - - var sanBuilder = new SubjectAlternativeNameBuilder(); - sanBuilder.AddDnsName(TestSubject.Replace("CN=", "")); - signingRequest.CertificateExtensions.Add(sanBuilder.Build()); - - var certificate = signingRequest.CreateSelfSigned( - notBefore == default(DateTimeOffset) ? DateTimeOffset.Now : notBefore, - expires == default(DateTimeOffset) ? DateTimeOffset.Now.AddYears(1) : expires); - - - var imported = certificate; - if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && savePrivateKey) - { - var export = certificate.Export(X509ContentType.Pkcs12, ""); - - imported = new X509Certificate2(export, "", X509KeyStorageFlags.PersistKeySet); - Array.Clear(export, 0, export.Length); - } - else if (!savePrivateKey) - { - var export = certificate.Export(X509ContentType.Cert, ""); - - imported = new X509Certificate2(export, "", X509KeyStorageFlags.PersistKeySet); - Array.Clear(export, 0, export.Length); - } - - using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) - { - store.Open(OpenFlags.ReadWrite); - store.Add(imported); - store.Close(); - }; - } - } - - private class TestGenerateSSLCertificateTask : GenerateSSLCertificateTask - { - public TestGenerateSSLCertificateTask() - { - Subject = TestSubject; - } - - public IList Messages { get; set; } = new List(); - - protected override void LogMessage(string message) => Messages.Add(message); - } - } -} diff --git a/test/Microsoft.AspNetcore.CertificateGeneration.Task.Tests/Microsoft.AspNetCore.CertificateGeneration.Task.Tests.csproj b/test/Microsoft.AspNetcore.CertificateGeneration.Task.Tests/Microsoft.AspNetCore.CertificateGeneration.Task.Tests.csproj deleted file mode 100644 index a09751f552..0000000000 --- a/test/Microsoft.AspNetcore.CertificateGeneration.Task.Tests/Microsoft.AspNetCore.CertificateGeneration.Task.Tests.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - netcoreapp2.0 - - - - - - - - - - -