Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Embed user-specific data into an authenticode signed installer on download

I have a Windows Forms application installed using InnoSetup that my users download from my website. They install this software onto multiple PCs.

The application talks to a Web API which must be able to identify the user. I am creating a web application where the user can log in and download the app. I would like to embed a universally unique ID into the installer so that they do not have to login again after installation. I want them to download and run setup.exe, and have the application take care of itself.

I am considering a couple of options:

  1. Embed a user-specific UUID into setup.exe and perform code-signing on-demand on the web server
    Downside: not sure how to do this?
  2. Embed a user-specific UUID into the name of the installer file (e.g. setup_08adfb12_2712_4f1e_8630_e202da352657.exe)
    Downside: this is not pretty and would fail if the installer is renamed
  3. Wrap the installer and a settings file containing the UUID into a self-extracting zip

How can I embed user-specific data into a signed executable on the web server?

like image 888
Belliez Avatar asked Sep 07 '17 12:09

Belliez


Video Answer


1 Answers

The entire PE is not signed. You can embed data into a signed PE by adding it to the signature table. This method is used by Webex and other tools to provide the one-click meeting utilities.

Technically, the PKCS#7 signature has a list of attributes that are specifically designated as unauthenticated which could be used, but I know of no easy way to write to these fields without a full PE parser. Luckily, we already have signtool, and adding an additional signature to an already signed file is a non-destructive operation that uses the unauthenticated fields.

I put together a demo which uses this technique to pass data from an MVC website to a downloadable windows forms executable.

The procedure is to:

  1. Start with the authenticode signed and timestamped exe produced by standard processes
    (must be able to run without dependencies - ILMerge or similar)
  2. Copy the unstamped exe to a temp file
  3. Create an ephemeral code signing certificate which includes the auxiliary data as an X509 extension
  4. Use signtool to add the auxiliary signature to the temp file
  5. Return the temp file to the client, delete it after the download completes

On the client side, the app:

  1. Reads the signing certificates from the currently executing exe
  2. Finds the certificate with a known subject name
  3. Finds the extension with a known OID
  4. Alters its behavior based off of the data contained in the extension

The process has a number of advantages:

  • No monkeying with the PE layout
  • The publicly trusted code signing certificate can stay offline (or even in an HSM), only ephemeral certificates are used on the web server
  • No outbound traffic is generated from the web server (as would otherwise be required if timestamping were performed)
  • Fast (<50ms for a 1MB exe)
  • Can be run from within IIS

Usage

Client side retrieval of data (Demo Application\MainForm.cs)

try
{
    var thisPath = Assembly.GetExecutingAssembly().Location;
    var stampData = StampReader.ReadStampFromFile(thisPath, StampConstants.StampSubject, StampConstants.StampOid);
    var stampText = Encoding.UTF8.GetString(stampData);

    lbStamped.Text = stampText;
}
catch (StampNotFoundException ex)
{
    MessageBox.Show(this, $"Could not locate stamp\r\n\r\n{ex.Message}", Text);
}

Server side stamping (Demo Website\Controllers\HomeController.cs)

var stampText = $"Server time is currently {DateTime.Now} at time of stamping";
var stampData = Encoding.UTF8.GetBytes(stampText);
var sourceFile = Server.MapPath("~/Content/Demo Application.exe");
var signToolPath = Server.MapPath("~/App_Data/signtool.exe");
var tempFile = Path.GetTempFileName();
bool deleteStreamOpened = false;
try
{
    IOFile.Copy(sourceFile, tempFile, true);
    StampWriter.StampFile(tempFile, signToolPath, StampConstants.StampSubject, StampConstants.StampOid, stampData);

    var deleteOnClose = new FileStream(tempFile, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete, 4096, FileOptions.DeleteOnClose);
    deleteStreamOpened = true;
    return File(deleteOnClose, "application/octet-stream", "Demo Application.exe");
}
finally
{
    if (!deleteStreamOpened)
    {
        try
        {
            IOFile.Delete(tempFile);
        }
        catch
        {
            // no-op, opportunistic cleanup
            Debug.WriteLine("Failed to cleanup file");
        }
    }
}
like image 152
Mitch Avatar answered Sep 28 '22 03:09

Mitch