OpenCertServer is a certificate server based on the Enrollment over Secure Transport profile (RFC 7030).
This profile, called Enrollment over Secure Transport (EST), describes a simple, yet functional, certificate management protocol targeting Public Key Infrastructure (PKI) clients that need to acquire client certificates and associated Certification Authority (CA) certificates. It also supports client-generated public/private key pairs as well as key pairs generated by the CA.
The project has been expanded to include support for the ACME protocol (RFC 8555) based on the following projects:
The projects have been migrated because they have not been updated in a long time, so to reduce the risk of being able to make the necessary API changes.
The project is licensed under the MIT license.
Both integrated projects are MIT licensed and the license terms remain the same.
In order to add the endpoints defined in the EST profile, you need to add the necessary services and configure your web application as described below.
The exact configuration of the authentication and authorization will depend on your local authorization rules.
The EST profile requires certificate authentication, and must be configured as an authentication scheme.
The certificate server can be customized by setting the environment variables defined below. In addition to the application specific variables below, the standard ASP.NET environments can also be passed.
Note that some environment variables use double underscore __
. This is to ensure compatibility with the .NET conversion from environment variable to hierarchical configuration value.
Environment Variable | Type | Description |
---|---|---|
RSA_PEM | string | (optional) Sets the path to the CA RSA certificate in PEM format. If the value is missing the application cannot start. |
RSA_KEY | string | (optional) Set the path to the CA RSA key file. |
ECDSA_PEM | string | (optional) Sets the path to the CA ECDSA certificate in PEM format. If the value is missing the application cannot start. |
ECDSA_KEY | string | (optional) Set the path to the CA RSA key file. |
WEB_PEM | string | (optional) Sets the path to the PEM certificate to use to secure the web server. |
WEB_KEY | string | (optional) Set the path to the web server's PEM key. |
public IServiceCollection ConfigureServices(IServiceCollection sc)
{
sc.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme);
sc.ConfigureOptions<ConfigureCertificateAuthenticationOptions>();
sc.AddEstServer(rsaPrivate, ecdsaPrivate);
}
In the example above rsaPrivate
and ecdsaPrivate
are certificate authority root certificates, for handling certificate requests for RSA and ECDSA algorithms, respectively.
public void Configure(IApplicationBuilder app)
{
app.UseEstServer();
}
The application builder configuration internally registers: CertificateForwarding
, Routing
, Authentication
, Authorization
and Endpoints
, in that order.
You can also have a look at the tests to see an example setup.
When the server is configured, the following 3 endpoints are available:
/.well-known/est/cacerts
(GET): This endpoint provides the server public key certificates.
/.well-known/est/simpleenroll
(POST): This endpoint allows a user to enroll and receive a certificate based on a certificate request.
/.well-known/est/simplereenroll
(POST): This endpoint provides a renewed certificate based on the provides client certificate. This endpoint requires a client certificate which will be used both to authenticate and to process the renewal request.
Add the following code to your Startup
class' ConfigureServices
method with real values instead of the sample values:
Note that you can set either TimeUntilExpiryBeforeRenewal
, TimeAfterIssueDateBeforeRenewal
or both, but at least one of them has to be specified.
//the following line adds the automatic renewal service.
services.AddAcmeClient(new LetsEncryptOptions() // see below
{
Email = "some-email@github.com", //LetsEncrypt will send you an e-mail here when the certificate is about to expire
UseStaging = false, //switch to true for testing
Domains = new[] { DomainToUse },
TimeUntilExpiryBeforeRenewal = TimeSpan.FromDays(30), //renew automatically 30 days before expiry
TimeAfterIssueDateBeforeRenewal = TimeSpan.FromDays(7), //renew automatically 7 days after the last certificate was issued
CertificateSigningRequest = new CsrInfo() //these are your certificate details
{
CountryName = "Denmark",
Locality = "DK",
Organization = "Fluffy Spoon",
OrganizationUnit = "Hat department",
State = "DK"
}
});
The `LetEncryptOptions` sealed class is a sub-class of `AcmeOptions` which is configured to use the LetsEncrypt endpoints. To use a custom ACME server, create your own options sealed class which inherits from `AcmeOptions`.
//the following line tells the library to persist the certificate to a file, so that if the server restarts, the certificate can be re-used without generating a new one.
services.AddAcmeFileCertificatePersistence();
//the following line tells the library to persist challenges in-memory. challenges are the "/.well-known" URL codes that LetsEncrypt will call.
services.AddAcmeMemoryChallengePersistence();
Inject the middleware in the Startup
class' Configure
method as such:
public void Configure()
{
app.UseAcmeClient();
}
Call UseUrls with http://* and https://* in Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseUrls(new string[] { "http://*", "https://*" });
webBuilder.UseStartup<Startup>();
});
Your application now supports SSL via LetsEncrypt, even from the first HTTPS request. It will even renew your certificate automatically in the background.
Persistence tells the middleware how to persist and retrieve the certificate, so that if the server restarts, the certificate can be re-used without generating a new one.
A certificate has a key to distinguish between certificates, since there is both an account certificate and a site certificate that needs to be stored.
services.AddAcmeFileCertificatePersistence();
services.AddAcmeFileChallengePersistence();
services.AddAcmeCertificatePersistence(/* your own ILetsEncryptPersistence implementation */);
services.AddAcmeChallengePersistence(/* your own ILetsEncryptPersistence implementation */);
//you can also customize persistence via delegates.
services.AddAcmeCertificatePersistence(
async (key, bytes) => File.WriteAllBytes("certificate_" + key, bytes),
async (key) => File.ReadAllBytes("certificate_" + key, bytes));
//the same can be done for challenges, with different arguments.
services.AddAcmeChallengePersistence(
async (challenges) => ... /* Do something to serialize the collection of challenges and store it */,
async () => ... /* Retrieve the stored collection of challenges */,
async (challenges) => ... /* Delete the specified challenges */);
To build the project, run the build script (build.ps1 on Windows, build.sh on Linux/Mac). This will generate a set of nuget packages which can be used to integrate OpenCertServer into an ASP.NET Core server project.
When reporting issues and bugs, please provide a clear set of steps to reproduce the issue. The best way is to provide a failing test case as a pull request.
If that is not possible, please provide a set of steps which allow the bug to be reliably reproduced. These steps must also reproduce the issue on a computer that is not your own.
All contributions are appreciated. Please provide them as an issue with an accompanying pull request.
This is an open source project. Please respect the license terms and the fact that issues and contributions may not be handled as fast as you may wish. The best way to get your contribution adopted is to make it easy to pull into the code base.