Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Commit 7ac72dd

Browse files
committed
Add task for generating ssl certificates with MSBuild
1 parent 0e3d091 commit 7ac72dd

10 files changed

+498
-1
lines changed

DotNetTools.sln

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Microsoft Visual Studio Solution File, Format Version 12.00
22
# Visual Studio 15
3-
VisualStudioVersion = 15.0.26210.0
3+
VisualStudioVersion = 15.0.26510.0
44
MinimumVisualStudioVersion = 10.0.40219.1
55
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{66517987-2A5A-4330-B130-207039378FD4}"
66
EndProject
@@ -24,6 +24,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.Watcher.To
2424
EndProject
2525
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}"
2626
EndProject
27+
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}"
28+
EndProject
29+
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}"
30+
EndProject
2731
Global
2832
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2933
Debug|Any CPU = Debug|Any CPU
@@ -54,6 +58,14 @@ Global
5458
{53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
5559
{53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
5660
{53F3B53D-303A-4DAA-9C38-4F55195FA5B9}.Release|Any CPU.Build.0 = Release|Any CPU
61+
{7B293291-26F4-47F0-9C2F-E396F35A4280}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
62+
{7B293291-26F4-47F0-9C2F-E396F35A4280}.Debug|Any CPU.Build.0 = Debug|Any CPU
63+
{7B293291-26F4-47F0-9C2F-E396F35A4280}.Release|Any CPU.ActiveCfg = Release|Any CPU
64+
{7B293291-26F4-47F0-9C2F-E396F35A4280}.Release|Any CPU.Build.0 = Release|Any CPU
65+
{3A7EF01A-073B-4123-850D-DFA4701EBE5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
66+
{3A7EF01A-073B-4123-850D-DFA4701EBE5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
67+
{3A7EF01A-073B-4123-850D-DFA4701EBE5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
68+
{3A7EF01A-073B-4123-850D-DFA4701EBE5B}.Release|Any CPU.Build.0 = Release|Any CPU
5769
EndGlobalSection
5870
GlobalSection(SolutionProperties) = preSolution
5971
HideSolutionNode = FALSE
@@ -65,5 +77,7 @@ Global
6577
{7B331122-83B1-4F08-A119-DC846959844C} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134}
6678
{8A2E6961-6B12-4A8E-8215-3E7301D52EAC} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134}
6779
{53F3B53D-303A-4DAA-9C38-4F55195FA5B9} = {66517987-2A5A-4330-B130-207039378FD4}
80+
{7B293291-26F4-47F0-9C2F-E396F35A4280} = {66517987-2A5A-4330-B130-207039378FD4}
81+
{3A7EF01A-073B-4123-850D-DFA4701EBE5B} = {F5B382BC-258F-46E1-AC3D-10E5CCD55134}
6882
EndGlobalSection
6983
EndGlobal

NuGetPackageVerifier.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@
1818
"packageTypes": [
1919
"DotnetCliTool"
2020
]
21+
},
22+
"Microsoft.AspNetCore.CertificateGeneration.Task": {
23+
"exclusions":{
24+
"BUILD_ITEMS_FRAMEWORK": {
25+
"*": "This is an MSBuild task intended to run through dotnet msbuild /t:Target independently of whether your project targets full framework or .net core."
26+
}
27+
}
2128
}
2229
}
2330
},

build/dependencies.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
<CoreFxVersion>4.3.0</CoreFxVersion>
1313
<InternalAspNetCoreSdkVersion>2.1.0-*</InternalAspNetCoreSdkVersion>
14+
<MsBuildPackageVersions>15.1.1012</MsBuildPackageVersions>
1415
<NETStandardImplicitPackageVersion>$(BundledNETStandardPackageVersion)</NETStandardImplicitPackageVersion>
1516
<TestSdkVersion>15.3.0-*</TestSdkVersion>
1617
<XunitVersion>2.3.0-beta2-*</XunitVersion>
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Runtime.InteropServices;
8+
using System.Security.Cryptography;
9+
using System.Security.Cryptography.X509Certificates;
10+
11+
namespace Microsoft.AspNetCore.CertificateGeneration.Task
12+
{
13+
internal static class CertificateManager
14+
{
15+
public static X509Certificate2 GenerateSSLCertificate(
16+
string subjectName,
17+
IEnumerable<string> subjectAlternativeName,
18+
string friendlyName,
19+
DateTimeOffset notBefore,
20+
DateTimeOffset expires,
21+
StoreName storeName,
22+
StoreLocation storeLocation)
23+
{
24+
using (var rsa = RSA.Create(2048))
25+
{
26+
var signingRequest = new CertificateRequest(
27+
new X500DistinguishedName(subjectName), rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
28+
29+
var enhancedKeyUsage = new OidCollection();
30+
enhancedKeyUsage.Add(new Oid("1.3.6.1.5.5.7.3.1", "Server Authentication"));
31+
signingRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(enhancedKeyUsage, critical: true));
32+
signingRequest.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true));
33+
signingRequest.CertificateExtensions.Add(
34+
new X509BasicConstraintsExtension(
35+
certificateAuthority: false,
36+
hasPathLengthConstraint: false,
37+
pathLengthConstraint: 0,
38+
critical: true));
39+
40+
var sanBuilder = new SubjectAlternativeNameBuilder();
41+
foreach (var alternativeName in subjectAlternativeName)
42+
{
43+
sanBuilder.AddDnsName(alternativeName);
44+
}
45+
signingRequest.CertificateExtensions.Add(sanBuilder.Build());
46+
47+
var certificate = signingRequest.CreateSelfSigned(notBefore, expires);
48+
49+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
50+
{
51+
certificate.FriendlyName = friendlyName;
52+
}
53+
54+
SaveCertificate(storeName, storeLocation, certificate);
55+
56+
return certificate;
57+
}
58+
}
59+
60+
private static void SaveCertificate(StoreName storeName, StoreLocation storeLocation, X509Certificate2 certificate)
61+
{
62+
// We need to take this step so that the key gets persisted.
63+
var imported = certificate;
64+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
65+
{
66+
var export = certificate.Export(X509ContentType.Pkcs12, "");
67+
imported = new X509Certificate2(export, "", X509KeyStorageFlags.PersistKeySet);
68+
Array.Clear(export, 0, export.Length);
69+
}
70+
71+
using (var store = new X509Store(storeName, storeLocation))
72+
{
73+
store.Open(OpenFlags.ReadWrite);
74+
store.Add(imported);
75+
store.Close();
76+
};
77+
}
78+
79+
public static X509Certificate2 FindCertificate(string subjectValue, StoreName storeName, StoreLocation storeLocation)
80+
{
81+
using (var store = new X509Store(storeName, storeLocation))
82+
{
83+
store.Open(OpenFlags.ReadOnly);
84+
var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, subjectValue, validOnly: false);
85+
var current = DateTimeOffset.UtcNow;
86+
87+
var found = certificates.OfType<X509Certificate2>()
88+
.Where(c => c.NotBefore <= current && current <= c.NotAfter && c.HasPrivateKey)
89+
.FirstOrDefault();
90+
store.Close();
91+
92+
return found;
93+
};
94+
}
95+
}
96+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Security.Cryptography.X509Certificates;
7+
using Microsoft.Build.Framework;
8+
9+
namespace Microsoft.AspNetCore.CertificateGeneration.Task
10+
{
11+
public class GenerateSSLCertificateTask : Build.Utilities.Task
12+
{
13+
public bool Force { get; set; }
14+
15+
protected string Subject { get; set; } = "CN=localhost";
16+
17+
public override bool Execute()
18+
{
19+
var subjectValue = Subject;
20+
var sansValue = new List<string> { "localhost" };
21+
var friendlyNameValue = "ASP.NET Core HTTPS development certificate";
22+
var notBeforeValue = DateTime.UtcNow;
23+
var expiresValue = DateTime.UtcNow.AddYears(1);
24+
var storeNameValue = StoreName.My;
25+
var storeLocationValue = StoreLocation.CurrentUser;
26+
27+
var cert = CertificateManager.FindCertificate(subjectValue, storeNameValue, storeLocationValue);
28+
29+
if (cert != null && !Force)
30+
{
31+
LogMessage($"A certificate with subject name '{Subject}' already exists. Skipping certificate generation.");
32+
return true;
33+
}
34+
35+
var generated = CertificateManager.GenerateSSLCertificate(subjectValue, sansValue, friendlyNameValue, notBeforeValue, expiresValue, storeNameValue, storeLocationValue);
36+
LogMessage($"Generated certificate {generated.SubjectName.Name} - {generated.Thumbprint} - {generated.FriendlyName}");
37+
38+
return true;
39+
}
40+
41+
protected virtual void LogMessage(string message) => Log.LogMessage(MessageImportance.High, message);
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<Import Project="..\..\build\common.props" />
4+
5+
<PropertyGroup>
6+
<TargetFramework>netcoreapp2.0</TargetFramework>
7+
<Description>MSBuild target for generating HTTPS certificates for development cross-platform.</Description>
8+
<BuildOutputTargetFolder>tools</BuildOutputTargetFolder>
9+
<PackageTags>asp.net;ssl;certificates</PackageTags>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<Content Include="build\**\*.targets" Pack="true" PackagePath="%(Identity)" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(MsBuildPackageVersions)" />
18+
</ItemGroup>
19+
20+
</Project>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
Microsoft.AspNetCore.CertificateGeneration.Task
2+
===============================================
3+
Microsoft.AspNetCore.CertificateGeneration.Task is an MSBuild task to generate SSL certificates
4+
for use in ASP.NET Core for development purposes.
5+
6+
### How To Install
7+
8+
Install `Microsoft.AspNetCore.CertificateGeneration.Task` as a `PackageReference` to your project.
9+
10+
```xml
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.AspNetCore.CertificateGeneration.Task" Version="2.0.0" />
13+
</ItemGroup>
14+
```
15+
16+
### How To Use
17+
18+
The command must be executed in the directory that contains the project with the reference to the package.
19+
20+
Usage: dotnet msbuild /t:GenerateSSLCertificate [/p:ForceGenerateSSLCertificate=true]
21+
22+
### Testing scenarios
23+
24+
On a machine without an SSL certificate generated by this task. Create a netcoreapp2.0 mvc application using
25+
26+
```
27+
dotnet new mvc
28+
```
29+
30+
Then try to run the app using:
31+
32+
```
33+
dotnet run
34+
```
35+
36+
When the application fails to run due to a missing SSL certificate. Run:
37+
38+
```
39+
dotnet msbuild /t:GenerateSSLCertificate
40+
```
41+
42+
Run the application again using:
43+
44+
```
45+
dotnet run
46+
```
47+
48+
The application should run successfully. You will still have to trust the certificate as a separate step.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
2+
3+
<!--
4+
********************************************************************************************
5+
Target: GenerateSSLCertificate
6+
7+
Generates an SSL certificate to use for development in ASP.NET Core applications
8+
********************************************************************************************
9+
-->
10+
<PropertyGroup>
11+
<_SSLCertificateGenerationTaskAssembly>$(MSBuildThisFileDirectory)..\tools\netcoreapp2.0\Microsoft.AspNetCore.CertificateGeneration.Task.dll</_SSLCertificateGenerationTaskAssembly>
12+
</PropertyGroup>
13+
14+
<UsingTask TaskName="Microsoft.AspNetCore.CertificateGeneration.Task.GenerateSSLCertificateTask"
15+
AssemblyFile="$(_SSLCertificateGenerationTaskAssembly)" />
16+
17+
<Target Name="GenerateSSLCertificate">
18+
<GenerateSSLCertificateTask Force="$(ForceGenerateSSLCertificate)" />
19+
</Target>
20+
</Project>

0 commit comments

Comments
 (0)