Skip to content
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

Question on Certificate.pfx and Azure #976

Closed
shengjing2019 opened this issue Jun 12, 2020 · 20 comments
Closed

Question on Certificate.pfx and Azure #976

shengjing2019 opened this issue Jun 12, 2020 · 20 comments
Labels

Comments

@shengjing2019
Copy link

I have an ASP.NET Core MVC app running on Azure App Service and the site is using HTTPS and I'd like to use token authentication to protect some of the pages. I'm very new to token auth and openiddict, I read that a JWT token needs to be signed on the server and I see in your MVC sample, there is a Certificate.pfx provided. Should I check this file into the source control and share it with other developers? And could I deploy it as is with my code to Azure or do I need to create a new .pfx file using some code on the fly or manually put it somewhere on the server file system or Azure key vault? I see there is the following code for development purpose, is there an equivalent code snippet for production?

options.AddDevelopmentEncryptionCertificate()
       .AddDevelopmentSigningCertificate();

I'd really appreciate any help and pointers on this. Thank you.

@kevinchalet
Copy link
Member

Should I check this file into the source control and share it with other developers?

Don't reuse the certificate sample, it would make your application vulnerable.

You have multiple options when it comes to generating/storing certificates/raw keys on Azure:

  • You can generate it locally and upload it via the portal. Set the WEBSITE_LOAD_CERTIFICATES environment variable and use Add*Certificate(string thumbprint) to load it from the X.509 store. It's the best compromise.

  • You can generate it locally and store it as a .pfx in your repository. For that, use the Add*Certificate() overload accepting an Assembly and a name to load it from the embedded resources. It's the least interesting option.

  • You can generate the signing/encryption certificates/keys in Azure KeyVault and keep them there. In this case, the tokens will be signed and decrypted by AKV, which offers a very high security level. Sadly, there are strict limits on the number of operations you can do per second, so it's not really appropriate for public websites. Read https://kevinchalet.com/2017/08/15/using-azure-key-vault-with-asos-and-openiddict/ for more information.

To generate a X.509 certificate locally, you can create a tiny console app and use the new CertificateRequest APIs:

  • Encryption certificate:

    using var algorithm = RSA.Create(keySizeInBits: 2048);
    var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
    request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true));
    certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2));
    // Note: setting the friendly name is not supported on Unix machines (including Linux and macOS).
    // To ensure an exception is not thrown by the property setter, an OS runtime check is used here.
    if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
    {
    certificate.FriendlyName = "OpenIddict Server Development Encryption Certificate";
    }
    // Note: CertificateRequest.CreateSelfSigned() doesn't mark the key set associated with the certificate
    // as "persisted", which eventually prevents X509Store.Add() from correctly storing the private key.
    // To work around this issue, the certificate payload is manually exported and imported back
    // into a new X509Certificate2 instance specifying the X509KeyStorageFlags.PersistKeySet flag.
    var data = certificate.Export(X509ContentType.Pfx, string.Empty);

  • Signing certificate:

    using var algorithm = RSA.Create(keySizeInBits: 2048);
    var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
    request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true));
    certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2));
    // Note: setting the friendly name is not supported on Unix machines (including Linux and macOS).
    // To ensure an exception is not thrown by the property setter, an OS runtime check is used here.
    if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
    {
    certificate.FriendlyName = "OpenIddict Server Development Signing Certificate";
    }
    // Note: CertificateRequest.CreateSelfSigned() doesn't mark the key set associated with the certificate
    // as "persisted", which eventually prevents X509Store.Add() from correctly storing the private key.
    // To work around this issue, the certificate payload is manually exported and imported back
    // into a new X509Certificate2 instance specifying the X509KeyStorageFlags.PersistKeySet flag.
    var data = certificate.Export(X509ContentType.Pfx, string.Empty);

@shengjing2019
Copy link
Author

Thank you @kevinchalet for detailed answer. I have this question I'd like to ask, when you do a File New Project on a Blazor WebAssembly project with Individual User Accounts, it puts the server and client code in the same project. Part of app is the wasm SPA and when you register or login user in it redirects you to the views from the /Identity folder. In that sample I don't see it's doing any certificate creation or signing, so is it doing everything implicit internally or is this step optional in certain setup? Am I missing something?

@kevinchalet
Copy link
Member

The VS templates use IdentityServer4 with a custom layer on top of it. That layer creates a "development" RSA key for you and persists it in obj/, but just like the X.509 development certificate offered by OpenIddict, it's not suitable for production scenarios, specially in web farms, where the credentials must be shared by all the instances.

@shengjing2019
Copy link
Author

I see that Azure now provides free TLS/SSL certificate, could I create and use this same certificate for both token Encryption and Sigining?

@kevinchalet
Copy link
Member

Technically, nothing prevents you from doing that, but I strongly encourage you to use self-signed certificates dedicated to this purpose, instead of trying to reuse a TLS certificate, free or not.

@amrohith
Copy link

I am trying to deploy the same in azure app service (Linux). even after adding the thumbprint I am getting the following exception.

System.Security.Cryptography.CryptographicException: The owner of '/home/.dotnet/corefx/cryptography/x509stores/my' is not the current user. at Internal.Cryptography.Pal.DirectoryBasedStoreProvider.EnsureDirectoryPermissions(String path, UInt32 userId) at Internal.Cryptography.Pal.DirectoryBasedStoreProvider.AddCertToStore(ICertificatePal certPal) at Internal.Cryptography.Pal.DirectoryBasedStoreProvider.Add(ICertificatePal certPal) at System.Security.Cryptography.X509Certificates.X509Store.Add(X509Certificate2 certificate) at Microsoft.Extensions.DependencyInjection.OpenIddictServerBuilder.AddDevelopmentEncryptionCertificate(X500DistinguishedName subject) at Microsoft.Extensions.DependencyInjection.OpenIddictServerBuilder.AddDevelopmentEncryptionCertificate() at OpeniddictServer.Startup.<>c.<ConfigureServices>b__4_7(OpenIddictServerBuilder options) in C:\OpeniddictServer\Startup.cs:line 123
Can you suggest an approach to solve this issue?

@joelnotified
Copy link

  • You can generate the signing/encryption certificates/keys in Azure KeyVault and keep them there. In this case, the tokens will be signed and decrypted by AKV, which offers a very high security level. Sadly, there are strict limits on the number of operations you can do per second, so it's not really appropriate for public websites. Read https://kevinchalet.com/2017/08/15/using-azure-key-vault-with-asos-and-openiddict/ for more information.

Hi! Just curious what the problem with this might be. Will the cert be loaded and decrypted per request, or just at startup of the application? If it's the latter case, then that won't happen too often and would be fine in our case.

@MGrAtQS
Copy link

MGrAtQS commented Sep 21, 2021

Hello,

It seems to me that this would be the best option:

However, the local generation requires an expiration date to be set:

using var algorithm = RSA.Create(keySizeInBits: 2048);

var subject = new X500DistinguishedName("CN=Fabrikam Encryption Certificate");
var request = new CertificateRequest(subject, algorithm,
    HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
request.CertificateExtensions.Add(new X509KeyUsageExtension(
    X509KeyUsageFlags.KeyEncipherment, critical: true));

var certificate = request.CreateSelfSigned(
    DateTimeOffset.UtcNow,
    DateTimeOffset.UtcNow.AddYears(2));

var data = certificate.Export(X509ContentType.Pfx, string.Empty);

Does this mean I need to perform this step again in 2 years from now and upload the new certificate to the Azure platform? It would be very nice to not have to do that. Is there a recommendation for that case?

Thanks you.

@MaheshTFS

This comment was marked as off-topic.

@dicksonkimeu
Copy link

Am having below error. Have added the cert file on the web root server

2022-10-11 20:01:14.125 +03:00 [INF] Starting web host.
2022-10-11 20:01:15.120 +03:00 [FTL] Host terminated unexpectedly!
Volo.Abp.AbpInitializationException: An error occurred during ConfigureServicesAsync phase of the module Volo.Abp.OpenIddict.AbpOpenIddictAspNetCoreModule, Volo.Abp.OpenIddict.AspNetCore, Version=6.0.0.0, Culture=neutral, PublicKeyToken=null. See the inner exception for details.
---> Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Access is denied.
at Internal.Cryptography.Pal.StorePal.FromSystemStore(String storeName, StoreLocation storeLocation, OpenFlags openFlags)
at System.Security.Cryptography.X509Certificates.X509Store.Open(OpenFlags flags)
at Microsoft.Extensions.DependencyInjection.OpenIddictServerBuilder.AddDevelopmentEncryptionCertificate(X500DistinguishedName subject)
at Microsoft.Extensions.DependencyInjection.OpenIddictServerBuilder.AddDevelopmentEncryptionCertificate()
at Volo.Abp.OpenIddict.AbpOpenIddictAspNetCoreModule.<>c__DisplayClass1_0.b__0(OpenIddictServerBuilder builder)
at Microsoft.Extensions.DependencyInjection.OpenIddictServerExtensions.AddServer(OpenIddictBuilder builder, Action1 configuration) at Volo.Abp.OpenIddict.AbpOpenIddictAspNetCoreModule.AddOpenIddictServer(IServiceCollection services) at Volo.Abp.OpenIddict.AbpOpenIddictAspNetCoreModule.ConfigureServices(ServiceConfigurationContext context) at Volo.Abp.Modularity.AbpModule.ConfigureServicesAsync(ServiceConfigurationContext context) at Volo.Abp.AbpApplicationBase.ConfigureServicesAsync() --- End of inner exception stack trace --- at Volo.Abp.AbpApplicationBase.ConfigureServicesAsync() at Volo.Abp.AbpApplicationFactory.CreateAsync[TStartupModule](IServiceCollection services, Action1 optionsAction)
at Microsoft.Extensions.DependencyInjection.ServiceCollectionApplicationExtensions.AddApplicationAsync[TStartupModule](IServiceCollection services, Action1 optionsAction) at Microsoft.Extensions.DependencyInjection.WebApplicationBuilderExtensions.AddApplicationAsync[TStartupModule](WebApplicationBuilder builder, Action1 optionsAction)
at API.Web.Program.Main(String[] args) in D:\Dev\API\src\API.Web\Program.cs:line 36

@kevinchalet
Copy link
Member

@kevinchalet
Copy link
Member

kevinchalet commented Oct 13, 2022

@dicksonkimeu FYI I updated the OpenIddict documentation to mention more clearly that the development certificates feature is not meant to be used on IIS/Azure App Service (https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html#registering-a-development-certificate) and I'll see with the ABP team whether they can also mention that in the ABP documentation: abpframework/abp#14312

@nuhrecep
Copy link

nuhrecep commented Nov 17, 2022

Hi, i have a similar problem
I have 3 separate projects for Frontend - API - OpenId server.
In localhost, I can send a request from FE and get a successful token.
However, when I deploy the code in AppService, I get an error.

I don't have a clear error message, only lines with incorrect cert are displayed in the AppService console.

do you have an idea?

        options.AddSigningCertificate(new X509Certificate2(signingCert ));

        var encryptionCert = File.ReadAllBytes("encryption-certificate.pfx");
        options.AddEncryptionCertificate(new X509Certificate2(encryptionCert ));

Error message :

Unhandled exception. Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: The specified network password is not correct.
at Internal.Cryptography.Pal.CertificatePal.FilterPFXStore(ReadOnlySpan1 rawData, SafePasswordHandle password, PfxCertStoreFlags pfxCertStoreFlags) at Internal.Cryptography.Pal.CertificatePal.FromBlobOrFile(ReadOnlySpan1 rawData, String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(ReadOnlySpan1 data) at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[] rawData) at Program.<>c.<<Main>$>b__0_2(OpenIddictServerBuilder options) in C:\home\site\repository\Program.cs:line 58 at Microsoft.Extensions.DependencyInjection.OpenIddictServerExtensions.AddServer(OpenIddictBuilder builder, Action1 configuration)
at Program.

$(String[] args) in C:\home\site\repository\Program.cs:line 16

@nuhrecep
Copy link

I created an SSL with openssl

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365

Then I applied the openssl pkcs12 command to export the pfx file from the key and pem files.

openssl pkcs12 -export -in cert.pem -inkey key.pem -certpbe PBE-SHA1-3DES -out cert.pfx

I keep the resulting pfx file in the project.

When I defined the certificate on the OpenId Server side as follows, the Client Api was able to send the auth code and receive the token.

options.AddSigningCertificate(new X509Certificate2("cert.pfx", pass, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable));

options.AddEncryptionCertificate(new X509Certificate2("cert.pfx", pass, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable));

Is there any downside to this process?

@kevinchalet
Copy link
Member

Is there any downside to this process?

Should be fine, but consider using 2 separate certificates (one for signing, one for encryption) 😃

@vohoanvu
Copy link

vohoanvu commented Mar 3, 2023

I created an SSL with openssl

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365

Then I applied the openssl pkcs12 command to export the pfx file from the key and pem files.

openssl pkcs12 -export -in cert.pem -inkey key.pem -certpbe PBE-SHA1-3DES -out cert.pfx

I keep the resulting pfx file in the project.

When I defined the certificate on the OpenId Server side as follows, the Client Api was able to send the auth code and receive the token.

options.AddSigningCertificate(new X509Certificate2("cert.pfx", pass, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable));

options.AddEncryptionCertificate(new X509Certificate2("cert.pfx", pass, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable));

Is there any downside to this process?

I am using this approach because I am deploying the API to Azure Free Tier so uploading Cert is not possible. I generated and uploaded the 2 .pfx files to the server root. Running 'dotnet .dll' cmd at the server root still gave me this error:
Volo.Abp.AbpInitializationException: An error occurred during ConfigureServicesAsync phase of the module Volo.Abp.OpenIddict.AbpOpenIddictAspNetCoreModule, Volo.Abp.OpenIddict.AspNetCore, Version=7.0.2.0, Culture=neutral, PublicKeyToken=null. See the inner exception for details. ---> System.Security.Cryptography.CryptographicException: Access is denied.
Here is my PreConfigure code in Module.cs file:
`public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.PreConfigure(options =>
{
options.AddAssemblyResource(
typeof(CodeJitsuResource)
);
});

    var hostingEnv = context.Services.GetHostingEnvironment();

    PreConfigure<OpenIddictServerBuilder>(builder =>
    {
        if (hostingEnv.IsDevelopment())
        {
            builder.AddDevelopmentEncryptionCertificate().AddDevelopmentSigningCertificate();
        }
        else
        {
            builder.AddSigningCertificate(new X509Certificate2("signing-cert.pfx", "vuadmin96", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable));
            builder.AddEncryptionCertificate(new X509Certificate2("encryption-cert.pfx", "vuadmin96", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable));
        }
    });

	PreConfigure<OpenIddictBuilder>(builder =>
    {
        builder.AddServer();
        builder.AddValidation(options =>
		{
			options.AddAudiences("CodeJitsu");
			options.UseLocalServer();
			options.UseAspNetCore();
        });
	});
}`

Am I doing anything wrong or missing anything?

@kevinchalet
Copy link
Member

@vohoanvu hard to say without seeing the stack trace of the inner exception, but have you tried to set AddDevelopmentEncryptionAndSigningCertificate to false as suggested in the ABP docs?

@vohoanvu
Copy link

vohoanvu commented Mar 3, 2023

image

@kevinchalet This statement in the docs threw me off and made me think I was not supposed to touch the AddDevelopmentEncryptionAndSigningCertificate value in deployed code at all. But having set it as false has resolved the issue mentioned. Now I am getting new errors but its okay its still progress. Thank you so much for help!

@kevinchalet
Copy link
Member

@vohoanvu ah yeah, it should probably be reworded to clarify that 😅

/cc @gterdem 👋🏻

@Fergus-McBride
Copy link

I'm curious if your suggestion from your original answer here -

You can generate it locally and store it as a .pfx in your repository. For that, use the Add*Certificate() overload accepting an Assembly and a name to load it from the embedded resources. It's the least interesting option.

is still a viable soloution?

I'm trying to implement this for my API hosted in azure (free tier), but seem to consistently get

System.Security.Cryptography.CryptographicException: Bad Data

When attempting to set the certificates from an embedded resource

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

10 participants