Gordon Luvinsky Gordon Luvinsky - 13 days ago 12
Javascript Question

CefSharp Accessing Encrypted HTML/JS/CSS Files

I have been building a Windows desktop application in WPF and it has an embedded browser (CefSharp) that load local HTML/JS/CSS files. These files do not come readily with the application. They will be downloaded from a server each time the program is started. These files are kept in user's application data folder.

My concern is that these files, although hidden in the application data folder, user could still find them, open them and read their content. These files actually play the biggest part for the program's functionalities and my client really hope that no one could steal the source codes, meaning the content of the downloaded HTML/JS/CSS files.

So my first thought is to encrypt the files and decrypt them only when the program access the files. Question is, does CefSharp support this somehow? When or at which step does CefSharp begin to access the files? Could I intercept it and decrypt the files' content before CefSharp could read them? Otherwise CefSharp would not be able to read the encrypted content right? Note that the files should remain encrypted the whole time even when the program is running.

Answer

After a long struggle, I finally found a working solution myself. The basic idea is to write my own resource handler as well as resource handler factory.

  1. Create MyResourceHandler that implements IResourceHandler.
  2. Do decryption in GetResponse function if ResponseLength has no value and Stream is null (optional).
  3. Create MyResourceHandlerFactory that implements IResourceHandlerFactory.
  4. Do decryption in GetResourceHandler function.
  5. Use MyResourceHandlerFactory while initializing ChromiumWebBrowser.

Sample GetResponse function in MyResourceHandler:

public virtual Stream GetResponse(IResponse response, out long responseLength, out string redirectUrl)
{
    redirectUrl = null;
    responseLength = -1;

    response.MimeType = MimeType;
    response.StatusCode = StatusCode;
    response.StatusText = StatusText;
    response.ResponseHeaders = Headers;

    if (ResponseLength.HasValue)
    {
        responseLength = ResponseLength.Value;
    }
    else
    {
        //If no ResponseLength provided then attempt to infer the length
        var memoryStream = Stream as MemoryStream;
        if (memoryStream != null)
        {
            responseLength = memoryStream.Length;
        }
        else
        {
            var absoluteFilePath = new Uri(FilePath).AbsolutePath;
            var fileBytes = File.ReadAllBytes(absoluteFilePath);
            if (ShouldDecrypt)
            {
                memoryStream = Decrypt(fileBytes);
            }
            else
            {
                memoryStream = new MemoryStream(fileBytes);
            }
            responseLength = memoryStream.Length;
            Stream = memoryStream as Stream;
        }
    }

    return Stream;
}

Sample GetResourceHandler function in MyResourceHandlerFactory:

public virtual IResourceHandler GetResourceHandler(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request)
{
    try
    {
        var uri = new Uri(request.Url);
        var filePath = uri.AbsolutePath;

        if (!File.Exists(filePath)) return null;

        var mime = GetMimeType(filePath);
        var fileBytes = File.ReadAllBytes(filePath);

        if (ShouldDecrypt)
        {
            Stream decryptedStream = Decrypt(fileBytes);
            return MyResourceHandler.FromStream(decryptedStream, mime);
        }

        Stream fileStream = new MemoryStream(fileBytes);
        return MyResourceHandler.FromStream(fileStream, mime);
    }
    finally
    {
        request.Dispose();
    }
}

And finally in case you wish to know how to initialize ChromiumWebBrowser that uses MyResourceHandlerFactory:

var browser = new ChromiumWebBrowser
{
    ResourceHandlerFactory = new MyResourceHandlerFactory()
};