Follow-up on secret values in a Business Central app

4 Feb

Wow… the community proved to be strong! About 10 days ago I asked to find the secret value in an app file and many of you really tried! Thank you so much!

Before I move on, I would like to draw your attention to this idea. Please give it your vote! For more info, see below at the end of this post.

What happened with the contest?

Because I shared the source code, it was not hard to figure out that the navxdata file contained the secret value. What I really wanted to know was if there are ways to get values out of the navxdata file. And it turned out to be possible. Erik Hougaard was the first to crack that nut. It took only 10 minutes for him! Apparently, he knows some details about the navxdata file that are not so widely known in the community. A few other people also found the value by hex editing into the navxdata file. Well done!

Some other people took the source code and manipulated it to get to the secret value. And for sure, that’s a possibility if you can get to the source code at all. Honestly, I only shared the source code to give you an idea about the technique I used, but not to manipulate the code. Having access to the source code reveals the way how the secret is stored and then the fun is over.

So, what’s next?

Well, my first recommendation is to not share source code when an app contains a secret value. So at least, set showMyCode to false in the app.json file.

As one suggested, you could just store the secret value into the code when you set showMyCode to false. And technically, that’s absolutely right. But I don’t think you should store any secrets in code, so I still believe the approach with the navxdata file is a valid one. Well, as long as Microsoft doesn’t give us any other option.

If you want to deploy your app file in an on-prem environment, then you should only use a runtime package. To prove that point, I’ve uploaded a runtime package here. If you are interested, please download it and try to get to the navxdata file that is inside it and then try to get the secret value. As Erik said, it would only be a little bit more difficult, so let’s see if he (or others) can find it again…

Security by obscurity

An extra level of security that I’ve added to this runtime package is obfuscating the secret value. There are several techniques you could apply, like creating multiple values, mixing them together, XOR-ing values, etc. If you want to get some ideas, then you can look into the code at GitHub. I’ve added some code in the table SecretValue to obfuscate the secret value with bitwise XOR comparison. Not with one, but with two random values. Just for the fun of it, and because it doesn’t exist out of the box in AL code. 😁

local procedure XORSecretValues(
    var SecretValue1List: List of [Byte];
    SecretValue2List: List of [Byte];
    SecretValue3List: List of [Byte])
var
    Index: Integer;
    SecretValueResult: List of [Byte];
    Byte1: Byte;
    Byte2: Byte;
begin
    foreach Byte1 in SecretValue1List do begin
        Index += 1;
        if Index mod 2 <> 0 then
            SecretValue2List.Get(Index, Byte2)
        else
            SecretValue3List.Get(Index, Byte2);
        SecretValueResult.Add(XORBytes(Byte1, Byte2));
    end;
    Clear(SecretValue1List);
    SecretValue1List := SecretValueResult;
end;
    
local procedure XORBytes(Byte1: Byte; Byte2: Byte) ReturnValue: Byte
var
    Binary1: Text;
    Binary2: Text;
    Bool1: Boolean;
    Bool2: Boolean;
    i: Integer;
    XORValue: Text;
begin
    Binary1 := ByteToBinary(Byte1);
    Binary2 := ByteToBinary(Byte2);

    for i := 1 to 8 do begin
        Evaluate(Bool1, Binary1[i]);
        Evaluate(Bool2, Binary2[i]);
        XORValue += Format(Bool1 xor Bool2, 0, 2);
    end;
    ReturnValue := BinaryToByte(XORValue);
end;

local procedure ByteToBinary(Value: Byte) ReturnValue: Text;
begin
    while Value >= 1 do begin
        ReturnValue := Format(Value MOD 2) + ReturnValue;
        Value := Value DIV 2;
    end;
    ReturnValue := ReturnValue.PadLeft(8, '0');
end;

local procedure BinaryToByte(Value: Text) ReturnValue: Byte;
var
    Multiplier: Integer;
    IntValue: Integer;
    i: Integer;
begin
    Multiplier := 1;
    for i := StrLen(Value) downto 1 do begin
        Evaluate(IntValue, Value.Substring(i, 1));
        ReturnValue += IntValue * Multiplier;
        Multiplier *= 2;
    end;
end;

Limiting access to code with access modifiers

Another recommendation I would like to make is to keep create a small app that only contains the secret value handling. Your other apps can then depend on it. Even better would be if that small base app doesn’t just store the secret value but uses it to get access to an Azure Key Vault in which you have the real secrets. In that way, the Azure Key Vault is a resource that belongs to your app and your other apps can just use that app.

But wait… if other apps can depend on it, then the secrets that are stored in an Azure Key Vault could leave your app and that would expose the values to any other app that takes a dependency. Well, not if you take some precautions.

Mark all the codeunits in that app as internal and non-debuggable. That will prevent dependent apps from calling the code. But that includes your apps, right? But there is another option on top of that. In the app.json you can define what other apps can access the internal objects. That opens the possibility to create trusted dependent apps and still prevent other apps from accessing your internal code.

That’s exactly what I’ve done in the source code. You will find a second app on GitHub, called Demo Secret Values Management. And that app is listed as a trusted app in the app.json of the Demo Secret Values app.

"internalsVisibleTo": [
    {
      "appId": "a9e18554-682c-42c0-a3bf-7427ce225bfb",
      "name": "Demo Secret Values Management",
      "publisher": "Arend-Jan Kauffmann"    
    }
  ]

This management app contains code to set the secret values from outside and tocall the function that prepares the table so it can be exported to a navxdata file. When you take this approach, you don’t have to ship the management app, it’s only needed during development and the build process. The corresponding PowerShell commands are available in the Scripts folder. This management app also contains pages to inspect the values in Isolated Storage and the table.

Platform feature

I’ve tried really hard to find a way to store a value inside the app in the most secure way. And for a moment I thought I found one. But after some good reading, I’ve reached the conclusion that there is no other way than trying to hide or obfuscate the code and data. With these blog posts and corresponding code, I’ve tried to inspire you. Please feel free to copy it and modify it to your needs.

What we really need is a platform feature to store secrets. The best option I can think of is creating an Azure Key Vault and an application identity that has access to it. The application identity, which consists of a client id and a secret, should not be shipped together with the app file, but rather be specified outside. For example as extra properties when we upload the app to AppSource. Then those values should become available in our app (and our app only) with AL code, for example with NavApp.AzureClientId and NavApp.AzureSecret. Or, even better, with a built-in feature to get values from the Azure Key Vault.

Can we expect such a platform feature in the future? Well, I certainly believe so. I’ve created an idea to add this as a built-in feature in the AL code. Please review it and give it your vote! The more votes it gets, the higher the chance we will get it. I’ve heard rumours that it is on the radar, but we still need to tell Microsoft that we really need such a feature.

Thanks for following this journey, I hope I’ve given you some ideas on how to handle secret values. If you have any other idea or enhancements, please don’t hesitate to share!

4 thoughts on “Follow-up on secret values in a Business Central app

  1. Hi arend jan,

    Storing a secret is one part of the puzzle.
    Using the secret later on will expose it, because you need to get it from its protected place and use it .
    admin level privileges on the nst and Using a .net or windows debugger will expose the secret to a person wanting it bad enough.

    You will find it to be impossible to use a secret on a machine that is controlled by the person the secret needs to be kept from.

    The only way to achieve this is to control the machine on which the secret is used, most of the time this means creating a online service, dedicated piece of hardware,…

    • I wonder why you would let a person manage a server that contains secrets he shouldn’t get access to. Isn’t that always a challenge with self-managed hosting?

      Creating an online service sounds like a good idea. But how can I make sure that the one who calls that service is who he says he is?

      • Say you are company A and you buy a cloud service for wich you get an API key. Then you build a solution using this api key. Now you want to distribute this solution to a company B , then you run into the problem how do i keep my API key hidden from company B. The answer is, by not distributing it to company B in the first place and keeping it hidden behind your own service layer. In your own service you implement a security mechanism that company b can acces it. Please note that this service is not exposing the API key to company B , instead it is calling the API with the api key. That way the api key never can be seen by company B and company B only needs to know it’s personal logon credentials to your service. The other way arround this is to make comapny B buy acces to the cloud service and get its own api key, wich then is personal and doesnt need hiding.

        • That is absolutely a very valid scenario. And if it is no problem to manually set up a system, by entering personal logon credentials, then that’s the way to go.

          But it becomes a challenge if you have a service that is part of your solution and they system should automatically and silently be able to use it. Then you need to ship some kind of credentials, key or otherwise in your app that identifies it. And that must be done in a secure way so that no others can see it, or when they see it, they still should not be able to use it anyway. That’s quite a hard nut to crack.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.