Embed user-specific data into signed by wildcard installer at boot
I have a Windows Forms application installed using InnoSetup that my users download from my site. They install this software on multiple PCs.
The application is accessing a web API, which must be able to identify the user. I am creating a web application where the user can login and download the application. I would like to embed the universally unique identifier in the installer so that they cannot login again after installation. I want them to download and run setup.exe and the application will take care of itself.
I am considering a couple of options:
- Paste the custom UUID into the setup.exe file and code-sign on demand on the web server. Downside: not sure how to do it?
- Paste custom UUID into installer filename (e.g. setup_08adfb12_2712_4f1e_8630_e202da352657.exe)
Downside: This is ugly and will fail if the installer is renamed - Wrap the installer and settings file containing the UUID in a self extracting zip
How can I embed user data into a signed executable on a web server?
source to share
All PE is unsigned. You can insert data into a signed PE by adding it to the signature table. This method is used by Webex and other tools to provide one-click meeting utilities.
Technically, PKCS # 7 signature has a list of attributes that are specifically designated as failing that can be used, but I don't know how easy it is to write these fields without a full PE parser. Fortunately, we already have signtool
, and adding an extra signature to an already signed file is a non-destructive operation that uses unauthenticated fields.
I have put together a demo that uses this method to pass data from an MVC website to a loaded windows executable.
The procedure is as follows:
- Start with authenticated signature and timestamped exe generated by standard processes
(should be able to run without dependencies - ILMerge or similar) - Copy unused exe to temporary file
- Create an ephemeral code signing certificate that includes ancillary data as an X509 extension
- Use
signtool
to add auxiliary signature to temp file - Return temp file to client, delete it after download finished
On the client side application:
- Reads signing certificates from the currently executable exe
- Finds a certificate with a known name
- Finds an extension using a known OID
- Changes its behavior based on the data contained in the extension
The process has several advantages:
- No monkeys with PE layout
- The Shared Trust Signing Certificate can remain offline (or even in HSM), only ephemeral certificates are used on the web server.
- Outbound traffic is not generated from the webserver (as would be needed if the timing was in progress)
- Fast (<50ms for 1MB exe)
- Can be run from IIS
Using
Finding Client Side 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 Typography ( Demo Site \ 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");
}
}
}
source to share