-
Notifications
You must be signed in to change notification settings - Fork 810
Add a shared sources library with code to generate certificates for development #263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
case CertificatePurpose.All: | ||
matchingCertificates = matchingCertificates | ||
.Where(c => HasOid(c, AspNetHttpsOid) || HasOid(c, AspNetIdentityOid)) | ||
.ToList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove the extra .ToList() calls to avoid multiple iterations. The final ToList at the end of this method should be sufficient.
StoreLocation location, | ||
bool isValid) | ||
{ | ||
var certificates = new List<X509Certificate2>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless I'm missing something, it seems like you could simplify by calling. Not sure why you need this list yet.
var certificates = store.Certificates.OfType<X509Certificate2>().ToList();
var bytes = certificate.Export(X509ContentType.Pkcs12, password); | ||
try | ||
{ | ||
File.WriteAllBytes(path, bytes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding Directory.CreateDirectory for the parent folder
const string CertificateName = nameof(EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer"; | ||
var manager = new CertificateManager(); | ||
|
||
manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😬 what if I have certs I want to keep? IMO it would be better to track certs you've made in the test run and ensure they are cleaned up at the end of the test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh hell yes. Naughty.
Test fail on linux and macos :-/ |
} | ||
} | ||
|
||
public X509Certificate2 CreateAspNetCoreHttpsDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@blowdart Can you review the crypto?
} | ||
|
||
public X509Certificate2 CreateApplicationTokenSigningDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@blowdart Can you review the crypto?
Adding Levi, because HAHA :D |
} | ||
if (isValid) | ||
{ | ||
// Ensure the certificate hasn't expired, has a private key and its exportable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why check for exportable? What if someone doesn't want autogenerated and creates their own non-exportable key?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non exportable certificates can't be used by docker
return EnsureCertificate(notBefore, notAfter, CertificatePurpose.Signing, path, trust, includePrivateKey, password); | ||
} | ||
|
||
public EnsureCertificateResult EnsureCertificate( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Naming. EnsureCertificateWhat? IsValid?
return EnsureCertificateResult.ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore; | ||
} | ||
} | ||
if (path != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is exporting the key a requirement for being valid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to export the key into a PFX for container/unix scenarios, we don't want to take into account any certificate that doesn't cover all our scenarios (has not expired, has a private key and the key is exportable)
The idea is that tooling will run a single command/gesture and ensure there is a valid certificate (not expired, with an exportable private key) that can be used to https and token signing. For containers scenarios the certificate will be exported into a pfx file somewhere on disk and that folder will be mapped so that it can get pick up by kestrel
matchingCertificates = matchingCertificates | ||
.Where(c => c.NotBefore <= now && | ||
now <= c.NotAfter && | ||
((c.GetRSAPrivateKey() is RSACryptoServiceProvider rsaPrivateKey && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a flag to make the exportability check optional (and instead just check that the cert has a private key)?
|
||
namespace Microsoft.Extensions.Certificates.Generation | ||
{ | ||
internal class CertificateManager |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this shared source instead of a traditional library?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to expose public API for this. It's a complete implementation detail
What repos / components will consume this? Or in other words, why is this a shared sources package? |
DotNetTools, Identity and Kestrel. Its a shared sources package because this is an implementation detail. We don't want to have public API or an actual package just for sharing a couple of methods but we want the crypto to be done in a single place. |
Do you expect Kestrel to populate the X509Store with a dev cert if one isn't there already? |
@halter73 Nope. Tooling does that. Kestrel should throw an exception if it can't find a valid certificate to use, with an error message pointing to what command line gesture to perform to create it and a forwarding link pointing to more detailed documentation. |
(!requireExportable || IsExportable(c))); | ||
} | ||
|
||
matchingCertificates = matchingCertificates.ToList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to force the enumeration here or else disposing the certificates later causes issues.
@natemcmaster Addressed feedback |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple more nits about namespaces and project layout, but otherwise the certificate management part looks fine. Almost ready to approve this.
using System.Security.Cryptography.X509Certificates; | ||
using System.Text; | ||
|
||
namespace Microsoft.Extensions.Certificates.Generation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: seems like this should be Microsoft.AspNetCore.Certificates.Generation
given that all the constants below say "aspnetcore ...." in them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't depend on any ASP.NET specific library, which I believe is the main driving factor for deciding between AspNetCore and Extensions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nate's right, this is an ASP.NET component because it is all about ASP.NET Certificates as shown by the constants below.
internal class CertificateManager | ||
{ | ||
public const string AspNetHttpsOid = "1.3.6.1.4.1.311.84.1.1"; | ||
public const string AspNetHttpsOidFriendlyName = "Asp.Net Core HTTPS development certificate"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: capitalization. "ASP.NET"
|
||
public void ExportCertificate(X509Certificate2 certificate, string path, bool includePrivateKey, string password) | ||
{ | ||
if (Path.GetDirectoryName(path) != "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be an error if string.IsNullOrEmpty(Path.GetDirectoryName(path))
or !Path.IsRooted(path)
? Seems like it would be better to require absolute paths, otherwise you get output relative to Directory.GetCurrentDirectory(), which changes based on how you invoke your process (IIS, IIS Express, command-line, VS, etc).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to put all those semantics on the actual exe we will build. I want to keep the library flexible
Common.sln
Outdated
@@ -52,6 +52,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{D2D23857 | |||
build\repo.props = build\repo.props | |||
EndProjectSection | |||
EndProject | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Extensions.Certificates.Generation.Tests", "test\Microsoft.Extensions.Certificates.Generation.Tests\Microsoft.Extensions.Certificates.Generation.Tests.csproj", "{02774467-AA73-4283-A46D-635F1FB0B90D}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can you condense this into the existing test project, Microsoft.Extensions.Internal.Test? Creating an new test assembly for one test class seems like a overkill...plus it makes tests a lot slower as test assemblies can't run in parallel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is netcoreapp2.0 only, so its kind of painful to be moving it to the internal project. Build time is not a problem in common and I rather keep it simple
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#if NETCOREAPP2_0
Thanks for addressing the feedback. LGTM |
No description provided.