Skip to content

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

Merged
merged 1 commit into from
Oct 6, 2017

Conversation

javiercn
Copy link
Member

No description provided.

case CertificatePurpose.All:
matchingCertificates = matchingCertificates
.Where(c => HasOid(c, AspNetHttpsOid) || HasOid(c, AspNetIdentityOid))
.ToList();

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>();

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);

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);

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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh hell yes. Naughty.

@natemcmaster
Copy link

Test fail on linux and macos :-/

@javiercn javiercn requested a review from blowdart September 21, 2017 21:55
}
}

public X509Certificate2 CreateAspNetCoreHttpsDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter)
Copy link
Member Author

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)
{
Copy link
Member Author

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?

@blowdart
Copy link

Adding Levi, because HAHA :D

}
if (isValid)
{
// Ensure the certificate hasn't expired, has a private key and its exportable

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?

Copy link
Member Author

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(

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)

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?

Copy link
Member Author

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 &&

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
Copy link
Member

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?

Copy link
Member Author

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

@Tratcher
Copy link
Member

Tratcher commented Oct 4, 2017

What repos / components will consume this? Or in other words, why is this a shared sources package?

@javiercn
Copy link
Member Author

javiercn commented Oct 4, 2017

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.

@halter73
Copy link
Member

halter73 commented Oct 4, 2017

Do you expect Kestrel to populate the X509Store with a dev cert if one isn't there already?

@javiercn
Copy link
Member Author

javiercn commented Oct 4, 2017

@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();
Copy link
Member Author

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.

@javiercn
Copy link
Member Author

javiercn commented Oct 6, 2017

@natemcmaster Addressed feedback

Copy link

@natemcmaster natemcmaster left a 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

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.

Copy link
Member Author

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.

Copy link
Member

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";

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) != "")

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).

Copy link
Member Author

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

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.

Copy link
Member Author

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#if NETCOREAPP2_0

@natemcmaster
Copy link

Thanks for addressing the feedback. LGTM :shipit:

@javiercn javiercn merged commit d7e26a0 into dev Oct 6, 2017
@natemcmaster natemcmaster deleted the certificates branch October 25, 2018 00:25
natemcmaster pushed a commit that referenced this pull request Nov 5, 2018
@ghost ghost locked as resolved and limited conversation to collaborators May 30, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants