Sunday, March 10, 2024

Cinnamon: first thing you may want to install when switched from Windows to Linux

If you worked in Windows and then switched to Linux it may be painful in beginning since user experience is a bit different (and here we are talking about graphical UX in Linux, not command line). E.g. this is how RHEL8 (Red Hat Enterprise Linux) with default GNOME desktop environment looks like:

Yes, there are windows also but no minimize/maximize icons nor taskbar. The good news is that there is more Windows-like desktop environment available called Cinnamon. Here you may find instructions how to install it on RHEL (you may find more complete list also here). After installation and reboot you will be able to select Cinnamon from available desktop environments list on login screen:


And system will look more familiar for those who worked with Windows:


There will be minimize/maximize icons, taskbar and other familiar things. Hopefully with them transition from Windows to Linux will go smoother.

Tuesday, February 6, 2024

Fix problem with Git client for Linux which asks for credentials on every push with installed SSH key

Recently I faced with the problem that Git client for Linux (CentOS) always asked for user credentials on every push even though SSH key was installed. In general SSH key is installed exactly for avoiding that. So what went wrong?

Let's briefly check whole process. First of all we need to install SSH key pair. On Linux it can be done with ssh-keygen tool. If you don't want to enter passphrase on every push just click Enter on each step. By default it will save public/private key files (id_rsa.pub and id_rsa) into ~/.ssh folder (where ~ means local user folder - usually under /home/local/...). After that copy content of public key file id_rsa.pub and go GitHub > your profile Settings > SSH and GPG keys > SSH keys and paste content there:

Installation of SSH key has been completed. But if you will try to clone some repository and try to push changes there (assuming that you have write permission in this repository) git may still ask for username/pw credentials and every push. As it turned out it depends on how repository was cloned. There are several ways to clone repositories: HTTPS, SSH and GitHub CLI (HTTPS tab goes first in UI)

Mentioned problem with credentials appears when repository is cloned via HTTPS. Solution here is to clone repository with SSH instead:

After that git should not ask you for credentials anymore.

Tuesday, January 9, 2024

Verify JWT tokens with EdDSA encryption algorithm

In my previous posts of this series I showed how to generate EdDSA private and public keys pair and how to sign JWT tokens using private EdDSA key. In this last post of the series I will show how to verify signed JWT token with public key.

Let's remind that EdDSA key pair may look like that in JSON format:

{
	"kty": "OKP",
	"alg": "EdDSA",
	"crv": "Ed25519",
	"x": "...",
	"d": "..."
}

where "x" property is used for public key and "d" is for private key. Private key (d) was used for signing. For verification we need to use public key (x).
For token validation we will use JsonWebTokenHandler.ValidateTokenAsync() method from Microsoft.IdentityModel.JsonWebTokens. Here is the code which decodes token:

string token = ...;
var jwk = ...; // get EdDSA keys pair
var pubKey = new EdDsaSecurityKey(new Ed25519PublicKeyParameters(Base64UrlEncoder.DecodeBytes(jwk.X), 0));
pubKey.KeyId = jwk.KeyId;
var result = await new JsonWebTokenHandler().ValidateTokenAsync(token, new TokenValidationParameters()
{
  ValidIssuer = JwtHelper.GetServiceName(jwk),
  AudienceValidator = (AudienceValidator) ((audiences, securityToken, validationParameters) => true), // or whatever logic is needed for verifying aud claimm
  IssuerSigningKey = (SecurityKey) pubKey
});
if (!result.IsValid)
  throw result.Exception;
json = JWT.Payload(token);

Here we use EdDsaSecurityKey class from ScottBrady.IdentityModel.Tokens.
If public key matches private key which was used for signing then result.IsValid will be true (otherwise code will throw exception). At the end we call JWT.Payload() from jose-jwt to get JSON token representation (from which we may get needed claims and other data).

With these techniques you may generate EdDSA keys, sign tokens and verify them. Hopefully information in these posts will help you.

Monday, December 25, 2023

Sign JWT tokens with EdDSA encryption algorithm

In my previous post of this series I showed how to generate key pair for EdDSA encryption algorithm. Let's now go further and use these keys to sign JWT token. If you remember from previous post "d" property of json object with keys pair belongs to private key. We will use this private key for signing our JWT token.

For creating JWT token we need to define claims. They are app/domain specific. We can add e.g. iss (issuer), exp (expired) and other standard claims (standard claims are defined in RFC 7519). Also we may add custom claims as we need in the app:

List<Claim> claims = ...; // fill claims

Then we need to load private key (from some secrets storage/vault usually):

var jwk = ...; // load private key

this jwk object may be json object showed in my previous post (plus it should have keyId string property for key identifier which may contain e.g. some guid).

Then we need to create EdDSA security key object and create signed token. We can do that using ScottBrady.IdentityModel nuget package (it uses Portable.BouncyCastle internally):

var edDsaSecurityKey = new EdDsaSecurityKey(new Ed25519PrivateKeyParameters(Base64UrlEncoder.DecodeBytes(jwk.d), 0));
edDsaSecurityKey.KeyId = jwk.keyId;
var securityTokenHandler = new JwtSecurityTokenHandler();
string token = securityTokenHandler.WriteToken(securityTokenHandler.CreateToken(new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity(claims),
    Issuer = ..., // define issuer (iss) claim as you need
    Expires = new DateTime(DateTime.UtcNow.AddMinutes(1)), // add expired date as you need
    SigningCredentials = new SigningCredentials(edDsaSecurityKey, "EdDSA")
}));

This code will create JWT token signed with EdDSA private key. In the next post I will show how to verify this token using public EdDSA key.

Update 2024-01-09: see also Verify JWT tokens with EdDSA encryption algorithm.

Tuesday, December 12, 2023

Fix Linq 2 NHibernate for MySQL

If you use NHibernate with MySQL and Linq 2 NHibernate to simplify fetching data you may face with problem: queries created by Linq2NH use square brackets by default. That is fine for SQL Server but won't work in MySQL which uses backticks ``.

For MySQL we need to instruct NHibernate to use backticks instead of square brackets. It can be done by setting interceptor in NH config:

public class NHConfiguration
{
    public static Configuration Build(string connStr)
    {
        var config = Fluently.Configure()
            .Database(
                MySQLConfiguration.Standard
                    .ConnectionString(connStr)
                    .AdoNetBatchSize(100)
                    .DoNot.ShowSql()
            )
            .Mappings(cfg =>
            {
                // add mappings
            })
            .ExposeConfiguration(x =>
            {
with backticks for MySQL
                x.SetInterceptor(new ReplaceBracesWithBackticksInterceptor());
            });

        return config.BuildConfiguration();
    }
}

in its OnPrepareStatement method we just replace square brackets with backticks:

public class ReplaceBracesWithBackticksInterceptor : EmptyInterceptor
{
    public override NHibernate.SqlCommand.SqlString OnPrepareStatement(NHibernate.SqlCommand.SqlString sql)
    {
        return sql.Replace("[", "`").Replace("]", "`");
    }
}

After that Linq2NH will start working in MySQL.

Saturday, November 18, 2023

Use Obfuscar for obfuscating ASP.Net Core web apps

Obfuscar is free and open source code obfuscation library for .NET. However if you want to use it in web app (ASP.Net Core) it is not that straightforward. First of all you should be ready that Obfuscar may break many things in runtime quite easily. I.e. project will be still compiled successfully but when you will try to run it may get unclear errors. Second - in web apps it is even more tricky to use Obfuscar comparing e.g. with console apps or dll class libraries. In this post I will describe some findings which may help you for using Obfuscar in ASP.Net Core web app.

For using Obfuscar you need to have xml config file. Here is obfuscar.xml file which can be used for obfuscating ASP.Net Core web app (below I will describe it in more details):

<?xml version='1.0'?>
<Obfuscator>
  <Var name="InPath" value="..." />
  <Var name="OutPath" value="..." />
  
  <!-- don't obfuscate Public classes and methods to avoid runtime exceptions -->
  <Var name="KeepPublicApi" value="true" />
  <Var name="HidePrivateApi" value="true" />

  <!-- Additional search path for ASP.Net Core dlls (to avoid) depending on currently used .NET version -->
  <AssemblySearchPath path="C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\6.0.25\ref\net6.0" />

  <Module file="$(InPath)\MyWebApp.dll">
    <!-- don't obfuscate anonymous types to have less problems when pass objects from server side to client side (e.g. in json format) -->
    <SkipType name="*AnonymousType*" skipProperties="true" skipMethods="true" skipFields="true" skipEvents="true" skipStringHiding="true" />
    <!-- don't obfuscate classes generated from cshtml to avoid runtime exceptions -->
    <SkipNamespace name="AspNetCoreGeneratedDocument" />
    <!-- don't obfuscate middlewares to avoid runtime exceptions -->
    <SkipNamespace name="MyWebApp.Middleware" />
  </Module>
</Obfuscator>

First of all we need to use default Obfuscar behavior when public classes and methods are not changed and only private (and internal) classes/methods are obfuscated:

  <!-- don't obfuscate public classes and methods to avoid runtime exceptions -->
  <Var name="KeepPublicApi" value="true" />
  <Var name="HidePrivateApi" value="true" />

Otherwise runtime exceptions will occur.

Second - we need to add additional search path where Obfuscar will search for references ASP.Net Core assemblies:

  <!-- Additional search path for ASP.Net Core dlls (to avoid) depending on currently used .NET version -->
  <AssemblySearchPath path="C:\Program Files\dotnet\packs\Microsoft.AspNetCore.App.Ref\6.0.25\ref\net6.0" />

Without that we will get "Unable to resolve dependency Microsoft.AspNetCore.Mvc.Razor" error during obfuscation. This path depends on actual .NET version which you use in the project. In above example I used .NET 6.0.25:

(I will write separate blog post how to get this .NET version automatically instead of hard coding it in obfuscar.xml)

Third - we need to specify our dll module with few rules:

  <Module file="$(InPath)\MyWebApp.dll">
    <!-- don't obfuscate anonymous types to have less problems -->
    <SkipType name="*AnonymousType*" skipProperties="true" skipMethods="true" skipFields="true" skipEvents="true" skipStringHiding="true" />
    <!-- don't obfuscate classes generated from cshtml to avoid runtime exceptions -->
    <SkipNamespace name="AspNetCoreGeneratedDocument" />
    <!-- don't obfuscate middlewares to avoid runtime exceptions -->
    <SkipNamespace name="MyWebApp.Middleware" />
  </Module>

1. For skipping obfuscation of anonymous types (e.g. if you have controller action which returns JsonResult and returns object of anonymous type from there (which is quite common practice) it will stop working because properties names of returned object will be changed by Obfuscar and since your client side (javascript) scripts will still expect the same properties names in received json object which you specified in C# code they will stop working).

2. For skipping obfuscation of classes from AspNetCoreGeneratedDocument namespace. If you use Razor view engine and cshtml files for views compiler will generate view classes for these cshtml files under the hood. These classes by default have AspNetCoreGeneratedDocument namespace. Obfuscation of these classes also should be skipped because otherwise you will just get empty browser window.

3. For skipping obfuscation of middlewares - otherwise will get runtime exceptions because middleware classes should have predefined public method (async Task Invoke(HttpContext context)). This is needed only if middlewares are defined as internal classes.

With this config basic ASP.Net Core web app should work. Of course I could forgot something or didn't find it yet - in this case will update this post. Also there may be other things which may be needed. If you know them please share in comments.


Tuesday, November 7, 2023

Bind complex view model objects in ASP.Net Core using ComplexObjectModelBinder

The way how view model objects are bound has been changed in ASP.Net Core. Now we need to create custom model binder class which inherits IModelBinder interface and implement binding logic there (in ASP.Net Core there is no DefaultModelBinder base class anymore which we may inherit in custom binders). However for most cases binding logic itself will be the same still - only custom logic will differ (like validation specific to view model class). Instead of implementing this binding logic for each view model class by ourselves we may use builtin ComplexObjectModelBinder which will do actual work for us. In this post I will show how to do that.

Let's say we have RequestModel view model class which can be used for asking contact information (name, email, phone):

public class RequestModel
{
    public string Name { get; set; }
    public string Email { get; set; }
    public string Phone { get; set; }
}

For binding this model we need to implement some infrastructural classes. First of all we need custom model binder provider which inherits IModelBinderProvider. But instead of creating own binder provider for each view model class let's create generic ComplexModelBinderProvider class which can be used with any view model:

public class ComplexModelBinderProvider<TModel, TBinder> : IModelBinderProvider
{
    private ComplexObjectModelBinderProvider complexObjectBinderProvider;

    public ComplexModelBinderProvider(ComplexObjectModelBinderProvider complexObjectBinderProvider)
    {
        this.complexObjectBinderProvider = complexObjectBinderProvider;
    }

    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context.Metadata.ModelType == typeof(TModel))
        {
            return (IModelBinder)Activator.CreateInstance(typeof(TBinder),
                new object[] { complexObjectBinderProvider.GetBinder(context) });
        }
        return null;
    }
}

It use 2 generic types: one for view model class (TModel) and another for its model binder which will be used for binding objects of TModel. Note that when it creates model binder it passes instance of ComplexObjectModelBinder to constructor (complexObjectBinderProvider.GetBinder(context)).

We also need custom model binder class RequestModelBinder for our view model - but instead of implementing binding logic there we will just call ComplexObjectModelBinder which was injected in constructor and which will do all complex work (get values from form values, query string params, etc):

public class RequestModelBinder : IModelBinder
{
    protected IModelBinder complexObjectBinder;
	
    public RequestModelBinder(IModelBinder complexObjectBinder)
    {
	    this.complexObjectBinder = complexObjectBinder;
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var model = new RequestModel();
        bindingContext.Model = model;
        await this.complexObjectBinder.BindModelAsync(bindingContext);
		
		// add custom logic there if needed
    }
}

Now we just need to link all of that together in Program.cs:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews(
    o =>
    {
        var complexObjectBinderProvider = o.ModelBinderProviders.First(p => p is ComplexObjectModelBinderProvider) as ComplexObjectModelBinderProvider;
        o.ModelBinderProviders.Insert(0, new ComplexModelBinderProvider<RequestModel, RequestModelBinder>(complexObjectBinderProvider));
        // add more ComplexModelBinderProvider instances for other view models
        ...
    });

Here we added our generic ComplexModelBinderProvider for RequestModel view model which during binding will create instance of RequestModelBinder which in turn will bind model by delegating work to ComplexObjectModelBinder. The more view models we have in the code the more advantages this approach will bring since we will just reuse the same binding logic for all models.