Donnerstag, 6. Februar 2025

Generate and Scan cryptographically signed QR Codes im Blazor

QR Codes are very common nowdays - they are so common that they are used to attack users by replacing the real QR Code with a manipulated version so the user comes to a phising site instead of the real site.

So, why not adding a digital signature to a QR Code to be sure about its origin and that it has not been tampered with? In this article I will show you all the basic building blocks you need to do this in Blazor. And because most of the code is acutally JavaScript, you will be able to use it in any SPA as well.

TL;DR

The code is on my GitHub.

Generating QR Codes

At first we need to be able to genereat QR Codes. For this we are using a QR Code Library. I have chosen QRious. The JavaScript code to generat a QR Code is:
var qrCode;

window.qrGenerator = {

    initializeQrCode: function (container) {
        var containerElement = document.getElementById(container);

        if (containerElement !== null && qrCode === undefined) {
            qrCode = new QRious({element: containerElement, size: 300});
        }
    },

    generateQrCode: function (data) {
        // stringify json the data
       const jsonData = JSON.stringify(data);
       qrCode.set({value: jsonData});
    },

    clearQrCode: function () {
        qrCode.set({value: ''});
    }

}
The initialize function takes the name of a container element which must be a canvas.Then a QRious object is created on that element. To generate a QR-Code we can call the set funtion and pass a value. In the code above we pass an object to the function that we turn into JSON before setting it to the QR Code.

On the Balzor end we need a way to call to the JavaScript, which is done via the IJSRuntime interface:

@page "/"
@inject IJSRuntime JS

<canvas id="qrcode" />

@code {
    override protected async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync("qrGenerator.initializeQrCode", "qrcode");
            await JS.InvokeAsync<string>("qrGenerator.generateQrCode", "Hello World!!");
        }
    }
}
 

Adding a cryptographic signature

The code to generate a public / private key pair is not C# but JavaScript again. I was hoping to be able to use C# for this in Blazor, but it turns out that the needed APIs are not supported in Blazor. So we have to fall back to the native JavaScript crypto.subtle APIs. These are low level cryptographic APIs which involve a high risk of getting things wrong, sacrificing any security. So make sure to check this with a security expert.

The code to generate a key pair is as follows.
window.cryptoHelper = {
    generateKeyPair: async function () {
        const keyPair = await window.crypto.subtle.generateKey(
            {
                name: "RSA-OAEP",
                modulusLength: 2048,
                publicExponent: new Uint8Array([1, 0, 1]),
                hash: "SHA-256"
            },
            true,
            ["encrypt", "decrypt"]
        );

        const publicKey = await window.crypto.subtle.exportKey("spki", keyPair.publicKey);
        const privateKey = await window.crypto.subtle.exportKey("pkcs8", keyPair.privateKey);

        return {
            publicKey: btoa(String.fromCharCode(...new Uint8Array(publicKey))),
            privateKey: btoa(String.fromCharCode(...new Uint8Array(privateKey)))
        };
    }
}

The generateKey funciton is used, and the first parameter is the algorithm. RSA-OAEP generates an asymetric key pair. The function can also create symmetric keys. The second parameter tells the api that the key is exportable. And the third parameter tells us what we can do with the key, which is irellevant in the code above, because the key is not used, it is just exported. This is done by the exportKey function. The public and private key are exported in suitable formats (spki and pkcs8) and returned to the calling Blazor app as base64 encoded strings.

Calling this is done in the same way as above using the IJSRuntime.
public class KeyPair
{
    public string PublicKey { get; set; }
    public string PrivateKey { get; set; }
}

@page "/signaturePlayground"
@inject IJSRuntime JS

code {

    private async Task GenerateKeyPair(MouseEventArgs args)
    {
        var keyPair = await JS.InvokeAsync<KeyPair>("cryptoHelper.generateKeyPair");
    }

}

For signing and validating a signature the respecive key must be imported first: for signing we need the private key, for validation the public key:

const privateKey = await window.crypto.subtle.importKey(
    "pkcs8",
    Uint8Array.from(atob(privateKey), c => c.charCodeAt(0)),
    {
        name: "RSA-PSS",
        hash: { name: "SHA-256" }
    },
    false,
    ["sign"]
);


For the public key and the virification the code is basically the same, you just have to switch the format from pkcs8 to spki and the purpose of the imported key from sign to verify.

Siging is done by the sign function:

const signature = await window.crypto.subtle.sign(
    {
        name: "RSA-PSS",
        saltLength: 32
    },
    privateKey,
    new TextEncoder().encode(data)
);

And verification by the verify function:

const isValid = await window.crypto.subtle.verify(
    {
        name: "RSA-PSS",
        saltLength: 32
    },
    publicKey,
    Uint8Array.from(atob(signature), c => c.charCodeAt(0)),
    new TextEncoder().encode(data)
);


Scanning a QR Code

For scanning a QR Code I choose the nimic libarary. You need to download the qr-scanner-umd.min.js and the qr-scanner-worker.min.js, the latter one is a dependency to the first one.

Starting a scan needs a video element for the QrScanner class and a callback into Balzor which is called OnQrCodeScanned.

startScan: function (dotNetObject) {
    qrScanner = new QrScanner(
        document.getElementById("qrScanner"),
        result => {
            console.log('decoded qr code:', result)
            dotNetObject.invokeMethodAsync('OnQrCodeScanned', result.data);
        },
        {
            highlightScanRegion: true,
            highlightCodeOutline: true,
        },
    );
    qrScanner.start();
}

The dotNetObject being passed to the function allows the callback to Blazor. This has to be passed in from the Balzor side:


@page "/scanQrCode"
@inject IJSRuntime JS

<video id="qrScanner"></video>
<textarea @bind="qrCodeData"></textarea>

@code {
    private string qrCodeData = string.Empty;

    override protected async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync("qrScanHelper.startScan", DotNetObjectReference.Create(this));
        }
    }

    [JSInvokable]
    public async Task OnQrCodeScanned(string qrCodeData)
    {
        this.qrCodeData = qrCodeData;
        StateHasChanged();
    }
}


This is achived by the DotNetObjectReference.Create method that gets a reference to this, the object representing the page. By marking a method with the [JSInvokable] attribute Blazor knows that this method is allowed to be called from JavaScript.

Just glue it together

These are all the moving parts you need to add a digital signature to your QR Code and verify that you have a real QR code. Working code for all this is on my GitHub.







Mittwoch, 7. Februar 2018

Handling child collectins in EF (now Core) - Again

I hitted the same issue as described in a previous post once again. Last time it was EF6, this time EF Core.

It is about having a simple model with a parent entity having a collection of child entities like in

public class Basket
{
    public Guid Id { get; set; }
    public Guid CustomerId { get; set; }
    public virtual IList<BasketItem> Positions { get; set; }
}

public class BasketItem
{
    public int Id { get; set; }
    public string ArticleName { get; set; }
    public decimal Amount { get; set; }
    public decimal Price { get; set; }
}

public class BasketContext : DbContext
{
    public BasketContext() : base("BasketContext") 
    {
    }
    
    public DbSet<Basket> Baskets { get; set; }
}

Please notice that there are neither unecessary navigation properties nor unecessary DbSets in the context.

With EF6 we needed to override the OnSaveChanges method to handle the problem, but things got better with EF Core. While the default behaviour still is an optional relationship whose foreign key will be set null on the database, we can now configure the relationship to be required and cascade on delete like so:


protected override void OnModelCreating(ModelBuilder modelBuilder)
{
            
     modelBuilder.Entity()
                .HasOne(typeof(Basket))
                .WithMany(nameof(Basket.Positions))
                .IsRequired()
                .OnDelete(DeleteBehavior.Cascade);

     base.OnModelCreating(modelBuilder);
}

Thanks to EF core it is now possible to define one-to-many relationships without the need of an explicit navigation property just by giving it a type. The IsRequired call makes the FK-filed on the database non nullable and the delete behaviour yields to the deletion of the chilg, as described in the EF Core docs on the topic.

So while I still feel that the EF Core default is unatural, the possibilities to fix the error became much cleaner.

Sonntag, 19. November 2017

noderunner.exe is eating all your memory?

Just as a note to myself: if you have to work with sharepoint you might notice a very high memory consumption from several noderunner.exe-processes.

There alerady exists an explanation of how to reduce this, here are the crucial steps:


  • Open a Sharepoint Management Shell
  • Set-SPEnterpriseSearchService -PerformanceLevel Reduced
  • Get-SPEnterpriseSearchService
    Should show "Performance Level: Reduced" now
  • Open C:\Program Files\Microsoft Office Servers\15.0\Search\Runtime\1.0\noderunner.exe.config in Notepad
  • Update <nodeRunnerSettings memoryLimitMegabytes=”0″ /> to say 100
  • Restart SharePoint Search Host Controller Service

And you are done.


Dienstag, 24. Oktober 2017

.NET Core Web API: Returning a file from an OutputFormatter

Let's say you want to return a csv file from your web API. To return a file from a controller action is as easy as writing

return File(stream, "text/csv");

It is not that eays if you are using an output formatter to be able to return either json or a file from the same controller method based on the Accept header. Then your controller looks like this:

return Ok(data);

And you need to register your output formatter with MVC:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services
        .AddMvc(options =>
            {
                options.RespectBrowserAcceptHeader = true;
                options.OutputFormatters.Add(new CsvOutputFormatter());
            });
};

Within your formatter you need to render your data to Csv. You can easily use a NuGet package for this purpose. Writing the formatter is easy then:

public class ApetitoArticleCsvOutputFormatter : OutputFormatter
{

    public ApetitoArticleCsvOutputFormatter()
    {
        ContentType = "text/csv";
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/csv"));
    }
    
    public string ContentType { get; private set; }

    public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
    {

        var articles = context.Object as IEnumerable<Article>;

        var response = context.HttpContext.Response;
        response.Headers.Add("Content-Disposition", "attachment; filename=export.csv");

        using (var writer = context.WriterFactory(response.Body, Encoding.UTF8))
        {
            var csv = new CsvWriter(writer);
            csv.WriteRecords(articles);

            await writer.FlushAsync();

            
        }
    }
    
}


You just get your data from context.Object, get the Repsones object and a writer to the body stream and let CsvHelper write your data to this stream. Usually you will get the data being send back as text, for example when using swagger. But we want to make the browser save a file from the csv we requested.

The bold line does the trick: simply add a Content-Disposition header with value attachment and the name of you file. Now the browser saves the file, or swagger shows you a link to start the download.


Dienstag, 4. April 2017

Team Build with Remote Powershell Cross Domain

I ran into a series of issues when trying to establish a release pipeline in TFS where the build agent is located in the company domain whereas the target server is inside a DMZ-domain. I tried to run the "Run Powershell on Remote Machine" from the company domain build agent computer with a target machine located in the DMZ domain. I do not memorize all the errors I got in detail, but they were all around "WinRM, Could not process request, Kerboros, No authentication Server, Host not found".

Basically the problem comes down to open a remote powershell session. So if this succeeds when logged in to the company domain build agent computer and you connect to srv.mydmz.de being the target server in the dmz-domain:

Enter-PSSession 
    -ComputerName "srv.mydmz.de" 
    -Credential mydmz\username

then your build / release Task "Run Powershell on Remote Machine" should succeed as well. This is useful for testing purposes because you do not need to create a release definition upfront and create a release every time you try to get things up and running.

I found the steps to solve my problem in a blog post from Christopher Hunt, but I want to stress out on thing that I got wrong from many other blog posts providing the same solution.

The solution is rather simple. On the build agent computer and on the target DMZ computer run: 

WinRM Quickconfig 

Then, log in to the build agent computer and run this from an elevated command prompt:

Set-Item wsman:\localhost\Client\TrustedHosts -value "srv.mydmz.de" 

This adds the target server located in the DMZ as trusted host on the company domain joined build agent computer. Then the above command to open a remote powershell succeeded for me where it formerly failed. So a release definition like this should work then if the build agent computer is configured as stated above:

Release Definition executing a powershell across domains


So, call me dumb, but here is the thing I always got wrong until now: you have to add the DMZ-Server as trusted host on the company domain joined server, not the other way round.

To me it appeared more logical that the computer being called (the DMZ-server), i. e. where the remote powershell executes stuff, should trust the computer calling it (the company domain joined server). So I repeatedly tried the Set-Item-Command on the DMZ-Server setting the domain joined build agent computer as the trusted host.

Now that my incompetence in this case is revealed, maybe it might save others some time :-)