Add HTTPS developer certificate management tool

This commit is contained in:
Javier Calvarro Nelson 2017-10-09 13:29:57 -07:00
parent c4788107f5
commit ff0f112d7b
12 changed files with 190 additions and 489 deletions

View File

@ -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}

View File

@ -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"
]
}
}
},

View File

@ -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<string> 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<X509Certificate2>()
.Where(c => c.NotBefore <= current && current <= c.NotAfter && c.HasPrivateKey)
.FirstOrDefault();
store.Close();
return found;
};
}
}
}

View File

@ -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<string> { "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);
}
}

View File

@ -1,18 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<Description>MSBuild target for generating HTTPS certificates for development cross-platform.</Description>
<BuildOutputTargetFolder>tools</BuildOutputTargetFolder>
<PackageTags>asp.net;ssl;certificates</PackageTags>
</PropertyGroup>
<ItemGroup>
<Content Include="build\**\*.targets" Pack="true" PackagePath="%(Identity)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@ -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
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.CertificateGeneration.Task" Version="2.0.0" />
</ItemGroup>
```
### 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.

View File

@ -1,20 +0,0 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--
********************************************************************************************
Target: GenerateSSLCertificate
Generates an SSL certificate to use for development in ASP.NET Core applications
********************************************************************************************
-->
<PropertyGroup>
<_SSLCertificateGenerationTaskAssembly>$(MSBuildThisFileDirectory)..\tools\netcoreapp2.0\Microsoft.AspNetCore.CertificateGeneration.Task.dll</_SSLCertificateGenerationTaskAssembly>
</PropertyGroup>
<UsingTask TaskName="Microsoft.AspNetCore.CertificateGeneration.Task.GenerateSSLCertificateTask"
AssemblyFile="$(_SSLCertificateGenerationTaskAssembly)" />
<Target Name="GenerateSSLCertificate">
<GenerateSSLCertificateTask Force="$(ForceGenerateSSLCertificate)" />
</Target>
</Project>

View File

@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<AssemblyName>dotnet-developercertificates</AssemblyName>
<OutputType>exe</OutputType>
<Description>Command line tool to generate certificates used in ASP.NET Core during development.</Description>
<PackageId>Microsoft.AspNetCore.DeveloperCertificates.Tools</PackageId>
<PackageTags>dotnet;developercertificates</PackageTags>
<PackageType>DotnetCliTool</PackageType>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\shared\CliContext.cs" Link="CliContext.cs" />
<Compile Include="..\..\shared\CommandLineApplicationExtensions.cs" Link="CommandLineApplicationExtensions.cs" />
<Compile Include="..\..\shared\ConsoleReporter.cs" Link="ConsoleReporter.cs" />
<Compile Include="..\..\shared\DebugHelper.cs" Link="DebugHelper.cs" />
<Compile Include="..\..\shared\Ensure.cs" Link="Ensure.cs" />
<Compile Include="..\..\shared\IConsole.cs" Link="IConsole.cs" />
<Compile Include="..\..\shared\IReporter.cs" Link="IReporter.cs" />
<Compile Include="..\..\shared\NullReporter.cs" Link="NullReporter.cs" />
<Compile Include="..\..\shared\PhysicalConsole.cs" Link="PhysicalConsole.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="prefercliruntime" PackagePath="\prefercliruntime" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Certificates.Generation.Sources" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" PrivateAssets="All" />
<PackageReference Include="System.Security.Cryptography.Cng" />
</ItemGroup>
</Project>

View File

@ -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 <<certificate>>'" +
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;
}
}
}
}

View File

@ -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<string> Messages { get; set; } = new List<string>();
protected override void LogMessage(string message) => Messages.Add(message);
}
}
}

View File

@ -1,15 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.CertificateGeneration.Task\Microsoft.AspNetCore.CertificateGeneration.Task.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" />
</ItemGroup>
</Project>