3

We are using IdentityServer4("http://docs.identityserver.io/en/release/quickstarts/0_overview.html") with EntityFrameworkCore to store operational and configuration data. To add signing credentials we are using x509 self signed certificates. We have used following command to create x509 self signed certificate:makecert -r -pe -n "CN=CertName_IdentityServer" -b 01/01/2015 -e 01/01/2039 -eku 1.3.6.1.5.5.7.3.3 -sky signature -a sha256 -len 2048 identityserver.cer. And add this certificate as embedded source in the solution. Here is our startup.cs file:

 public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IConfiguration>(Configuration);

        //connection string
        string connectionString = Configuration.GetConnectionString("IdentityServer");

        var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

        ConfigureSigningCerts(services);

        services.AddIdentityServer()
            // this adds the config data from DB (clients, resources)
            .AddConfigurationStore(options =>
            {
                options.ConfigureDbContext = builder =>
                builder.UseSqlServer(connectionString,
                sql => sql.MigrationsAssembly(migrationsAssembly));
            }) // this adds the operational data from DB (codes, tokens, consents)
            .AddOperationalStore(options =>
            {
                options.ConfigureDbContext = builder =>
                builder.UseSqlServer(connectionString,
            sql => sql.MigrationsAssembly(migrationsAssembly));

                // this enables automatic token cleanup. this is optional.
                options.EnableTokenCleanup = true;
                options.TokenCleanupInterval = 30;
            });
    }
private static void ConfigureSigningCerts(IServiceCollection services)
    {

        var assembly = typeof(Startup).GetTypeInfo().Assembly; 
        /*
        * IdentityServer.WebApi\
        *     Certificates\
        *         identityserver.cer
        * 
        * {assembly name}.{directory}.{file name}
        */
        using (Stream resource = assembly.GetManifestResourceStream("IdentityServer.WebApi.Certificates.identityserver.cer"))
        using (var reader = new BinaryReader(resource))
        {
            var signingCert = new X509Certificate2(reader.ReadBytes((int)resource.Length));


            var keys = new List<SecurityKey>();

            if (signingCert == null) throw new InvalidOperationException("No valid signing certificate could be found.");

            var signingCredential = new SigningCredentials(new X509SecurityKey(signingCert), "RS256");
            services.AddSingleton<ISigningCredentialStore>(new DefaultSigningCredentialsStore(signingCredential));

            var validationCredential = new SigningCredentials(new X509SecurityKey(signingCert), "RS256");
            keys.Add(validationCredential.Key);
            services.AddSingleton<IValidationKeysStore>(new DefaultValidationKeysStore(keys));
        }
    }

When we execute the application on local host discovery endpoint works fine but when called connect/token endpoint we got the following error message:

    crit: IdentityServer4.Hosting.IdentityServerMiddleware[0]
      Unhandled exception: System.InvalidOperationException: IDX10638: Cannot created the SignatureProvider, 'key.HasPrivateKey' is false, cannot create signatures. Key: Microsoft.IdentityModel.Tokens.X509SecurityKey.
         at Microsoft.IdentityModel.Tokens.AsymmetricSignatureProvider..ctor(SecurityKey key, String algorithm, Boolean willCreateSignatures)
         at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures)
         at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForSigning(SecurityKey key, String algorithm)
         at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateEncodedSignature(String input, SigningCredentials signingCredentials)
         at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.WriteToken(SecurityToken token)
         at IdentityServer4.Services.DefaultTokenCreationService.CreateJwtAsync(JwtSecurityToken jwt) in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Services\DefaultTokenCreationService.cs:line 209
         at IdentityServer4.Services.DefaultTokenCreationService.<CreateTokenAsync>d__4.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Services\DefaultTokenCreationService.cs:line 67
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at IdentityServer4.Services.DefaultTokenService.<CreateSecurityTokenAsync>d__9.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Services\DefaultTokenService.cs:line 210
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at IdentityServer4.ResponseHandling.TokenResponseGenerator.<CreateAccessTokenAsync>d__14.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\ResponseHandling\TokenResponseGenerator.cs:line 313
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at IdentityServer4.ResponseHandling.TokenResponseGenerator.<ProcessTokenRequestAsync>d__13.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\ResponseHandling\TokenResponseGenerator.cs:line 249
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at IdentityServer4.ResponseHandling.TokenResponseGenerator.<ProcessAsync>d__7.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\ResponseHandling\TokenResponseGenerator.cs:line 84
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at IdentityServer4.Endpoints.TokenEndpoint.<ProcessTokenRequestAsync>d__7.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Endpoints\TokenEndpoint.cs:line 98
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at IdentityServer4.Endpoints.TokenEndpoint.<ProcessAsync>d__6.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Endpoints\TokenEndpoint.cs:line 70
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at IdentityServer4.Hosting.IdentityServerMiddleware.<Invoke>d__3.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Hosting\IdentityServerMiddleware.cs:line 54
crit: IdentityServer4.Hosting.IdentityServerMiddleware[0]
      Unhandled exception: System.InvalidOperationException: IDX10638: Cannot created the SignatureProvider, 'key.HasPrivateKey' is false, cannot create signatures. Key: Microsoft.IdentityModel.Tokens.X509SecurityKey.
         at Microsoft.IdentityModel.Tokens.AsymmetricSignatureProvider..ctor(SecurityKey key, String algorithm, Boolean willCreateSignatures)
         at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures)
         at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForSigning(SecurityKey key, String algorithm)
         at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.CreateEncodedSignature(String input, SigningCredentials signingCredentials)
         at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.WriteToken(SecurityToken token)
         at IdentityServer4.Services.DefaultTokenCreationService.CreateJwtAsync(JwtSecurityToken jwt) in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Services\DefaultTokenCreationService.cs:line 209
         at IdentityServer4.Services.DefaultTokenCreationService.<CreateTokenAsync>d__4.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Services\DefaultTokenCreationService.cs:line 67
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at IdentityServer4.Services.DefaultTokenService.<CreateSecurityTokenAsync>d__9.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Services\DefaultTokenService.cs:line 210
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at IdentityServer4.ResponseHandling.TokenResponseGenerator.<CreateAccessTokenAsync>d__14.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\ResponseHandling\TokenResponseGenerator.cs:line 313
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at IdentityServer4.ResponseHandling.TokenResponseGenerator.<ProcessTokenRequestAsync>d__13.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\ResponseHandling\TokenResponseGenerator.cs:line 249
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at IdentityServer4.ResponseHandling.TokenResponseGenerator.<ProcessAsync>d__7.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\ResponseHandling\TokenResponseGenerator.cs:line 84
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at IdentityServer4.Endpoints.TokenEndpoint.<ProcessTokenRequestAsync>d__7.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Endpoints\TokenEndpoint.cs:line 98
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at IdentityServer4.Endpoints.TokenEndpoint.<ProcessAsync>d__6.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Endpoints\TokenEndpoint.cs:line 70
      --- End of stack trace from previous location where exception was thrown ---
         at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
         at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
         at IdentityServer4.Hosting.IdentityServerMiddleware.<Invoke>d__3.MoveNext() in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Hosting\IdentityServerMiddleware.cs:line 54
Rakesh Kumar
  • 2,701
  • 9
  • 38
  • 66
  • makecert has been deprecated. One of the replacement tools you can use is the powershell New-SelfSignedCertificate module. https://www.petri.com/create-self-signed-certificate-using-powershell If you run that and still get the error with your cert, reply here and I'll continue sharing my progress / code, as I'm looking at binding self-signed certs to ID4 as well currently and also have a method to load a cert from embedded resource. – JakeJ Jun 21 '18 at 15:32

2 Answers2

1

It looks like if you use a file you may need to do an additional step and assign a password to allow the private key to be accessed.

This should hopefully help: How to create a self signed certificate with the private key inside in a file in one simple step?

An alternative is to generate the cert in the local machine certificate store and then export it via the certificate management MMC snap-in.

mackie
  • 4,996
  • 1
  • 17
  • 17
1

From Powershell (run Powershell as administrator):

$cert = New-SelfSignedCertificate -DnsName yourSiteHere.com -type Custom -CertStoreLocation cert:\localmachine\my -KeyExportPolicy Exportable

With the above command Issuer becomes yourSiteHere and the expiration date is the default of one year out. It will also have RSA keys of length 2048. You can then export the cert using the certmgr utility (there are also more commands in Powershell to export as well which I haven't used yet). See these links for more info:

https://www.petri.com/create-self-signed-certificate-using-powershell

https://learn.microsoft.com/en-us/powershell/module/pkiclient/new-selfsignedcertificate?view=win10-ps

Now, in IdentityServer4, I extended the IIdentityServerBuilder class to provide a method for cert binding from that type of file - very quickly, if you have a static class, and its methods take a parameter of form "this someClass", then it's an "extension". You can extend any class, even those standard classes internal to C# (like string, etc). If you do this, your methods will also come up with Intellisense when you type the period after that class or variables of that type. This means that I have access to my method from the builder at startup (you just need to put a using to the extension class's namespace in Startup.cs):

public static class SigningCredentialExtension
{
     public static IIdentityServerBuilder GetCertFromAzure(this IIdentityServerBuilder builder)
     {
           //Note:  in order for the certificate to be visible to the app, 
           //an application setting "WEBSITE_LOAD_CERTIFICATES" with the value 
           //of your SSL cert's thumbprint must be added to your IdentityServer
           //webapp on Azure.
           var thumbprint = "your cert's thumbprint";

           var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
           store.Open(OpenFlags.ReadOnly);

           var certs = store.Certificates.Find(X509FindType.FindByThumbprint, 
                    certThumbprint, true);

           if (certs.Count > 0)
           {
                X509Certificate2 cert = 
                       new X509Certificate2(certs[0].Export(X509ContentType.Pfx,
                           "your cert's password"));
                builder.AddSigningCredential(cert);
                builder.AddValidationKey(cert);
           }
           return builder;
     }

     public static IIdentityServerBuilder GetCertFromEmbeddedProjectFile(
                   IIdentityServerBuilder builder)
     {
          var assembly = Assembly.GetExecutingAssembly();
          var fileName = "Your.Project.Namespace.FileName.fileExtension";
          using (Stream stream = assembly.GetManifestResourceStream(resourceName))
          {
                Byte[] raw = new Byte[stream.Length];

                for (Int32 i = 0; i < stream.Length; i++)
                {
                    raw[i] = (Byte)stream.ReadByte();
                }
                X509Certificate2 cert = new X509Certificate2(raw, password);
                builder.AddSigningCredential(cert);
                builder.AddValidationKey(cert);
           }
           return builder;
     }
}

So - include the above class in your project, make sure that Startup.cs can see it (include a using if necessary), get rid of your ConfigureSigningCerts() method, and after your line "services.AddIdentityServer()" type a '.' and you'll see the extension methods in the list. Use the method you want. You don't need to specify a parameter, the method will automatically get the builder. The builder will be returned for the later methods after it.

JakeJ
  • 2,361
  • 5
  • 23
  • 35
  • For the GetCertFromAzure method to work, you must have an SSL cert bound to your webapp on Azure (one that's been issued by a trusted cert authority). – JakeJ Jun 21 '18 at 19:07
  • With the above, just call one of the above methods from builder in Startup where you had been calling the method to add the developer cert. You can adjust it to provide fallback strategies in case a cert expires. – JakeJ Jun 21 '18 at 19:13