Reputation: 500
Here's what i'm trying to achieve. We're developing a microservices app on Kubernetes. One of the microservices is IdentityServer instance. Initially i want to test the solution locally on Docker to make sure it works. For this purpose, i want to paste the certificate to appsettings.json. Eventually this value will be replaced by a Kubernetes secret. In my startup class this is how i'm trying to load my certificate:
services.AddIdentityServer()
.AddSigningCredential(GetIdentityServerCertificate())
.AddConfigurationStore(...
private X509Certificate2 GetIdentityServerCertificate()
{
var clientSecret = Configuration["Certificate"];
var pfxBytes = Convert.FromBase64String(clientSecret);
var certificate = new X509Certificate2(pfxBytes);
return certificate;
}
The certificate is generated by me using openssl:
openssl req –newkey rsa:2048 –nodes –keyout XXXXX.key –x509 –days 365 –out XXXXX.cer
openssl pkcs12 –export –in XXXX.cer –inkey XXXX.key –out XXXX.pfx
Then i get the certificate by using:
openssl pkcs12 -in XXXX.pfx -info -nokeys
-----BEGIN CERTIFICATE-----
i take this content and paste into appconfig.json
-----END CERTIFICATE-----
When i debug it, the result is: System.InvalidOperationException: 'X509 certificate does not have a private key.'
Upvotes: 3
Views: 6088
Reputation: 818
Here is an example that works in .net core:
Use openssl tool. Open your terminal and type the following commands:
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
It will result in creating 2 files.
There’s one more thing before we can actually use our RSA keys within .NET Core application. We need to convert them into XML. Use a "RSA PEM to XML Converter". It can be done here: Online rsa key converter Before copying XML into private-rsa-key.xml and public-rsa-key.xml files, format them using: XML Formatter
The private key is required only for the service responsible for generating the token. This must not be shared outside of this project.
Startup.cs :
public class Startup
{
public IConfiguration Configuration { get; }
private SigningCredentials _signingCredentials;
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
// other code
services
.AddIdentityServer()
.AddSigningCredential(_signingCredentials)
.AddInMemoryApiResources(_identityConfig.GetApiResources())
.AddInMemoryClients(_identityConfig.GetClients());
// other code
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc();
app.UseIdentityServer();
}
private void InitializeRsaKey()
{
try
{
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(2048);
var rsaParametersPrivate = RSAExtensions.RSAParametersFromXmlFile(Configuration.GetSection("JwtSettings:rsaPrivateKeyXml").Value);
rsaProvider.ImportParameters(rsaParametersPrivate);
var securityKey = new RsaSecurityKey(rsaProvider);
_signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.RsaSha256);
}
catch(Exception ex)
{
throw new Exception("Identity Server RSA Key initialization failed. " + ex.ToString());
}
}
}
The RSAExtensions class:
public static class RSAExtensions
{
/// <summary>
/// Gets RSA Parameters from XML file.
/// </summary>
/// <param name="xmlFilePath">The XML file path.</param>
/// <returns>RSAParameters.</returns>
/// <exception cref="Exception">Invalid XML RSA key.</exception>
public static RSAParameters RSAParametersFromXmlFile(string xmlFilePath)
{
RSAParameters parameters = new RSAParameters();
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(File.ReadAllText(xmlFilePath));
if (xmlDoc.DocumentElement.Name.Equals("RSAKeyValue"))
{
foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes)
{
switch (node.Name)
{
case "Modulus": parameters.Modulus = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
case "Exponent": parameters.Exponent = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
case "P": parameters.P = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
case "Q": parameters.Q = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
case "DP": parameters.DP = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
case "DQ": parameters.DQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
case "InverseQ": parameters.InverseQ = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
case "D": parameters.D = (string.IsNullOrEmpty(node.InnerText) ? null : Convert.FromBase64String(node.InnerText)); break;
}
}
}
else
{
throw new Exception("Invalid XML RSA key.");
}
return parameters;
}
}
In appsettings.json add the following:
"JwtSettings": {
"rsaPrivateKeyXml": "RSAKeys/private-rsa-key.xml",
},
I don't know if you can store raw XML in appsettings.json. If not you can write a helper method to convert pem to xml and store the raw pem content inside a configuration variable.
I found a sample project in GitHub that may help you on the conversion: Rsa-Dotnet-Core
Upvotes: 2