Document signing in the cloud

Jozef Izso
Slido developers blog
5 min readApr 26, 2023

For our integration with PowerPoint on Mac we need to generate and sign Microsoft Office Add-in documents. The Add-in document is an Office file with macro code which can be signed by an authenticode certificate to confirm the identity of the author of the code in the macro. Signing can be done manually from Office applications on Windows or automatically using tools from Windows SDK Signing Tools.

The tool for code signing is named SignTool (signtool.exe) and it is distributed in Windows SDK Signing Tools. It signs EXE, DLL, MSI and other common Windows binary formats and it requires an authenticode certificate.

The SignTool can be extended to support signing of Microsoft Office documents. This extension is called Subject Interface Package (SIP) and it allows signing new file formats which are not supported by default in the SignTool. We need to install the ‌Microsoft Office Subject Interface Packages for Digitally Signing VBA Projects or Office SIP for short and the SignTool will happily sign documents in formats like DOCM (Microsoft Word Macro-enabled document) or PPAM (Microsoft PowerPoint Add-in file).

White wooden boat floating on light blue water.
Photo by Osman Rana, Unsplash

Sandboxing the signing process

It is quite easy to install Windows SDK Signing Tools from the MSI file and Office SIP manually using README instructions into full Windows Server, but this does not scale and it is error prone. So let’s try to automate it.

We can use Docker to create a Windows Server container image. Build definition for the image can be versioned in Git, and the image can be automatically deployed in Amazon Elastic Container Service or Azure Container Instances.

Building the container with SignTool

The great news is we can put the SignTool and Office SIP into a container and sign application binaries and Office documents in it. But can we optimize the whole image? The default approach with copying MSI files to the container and running installation it the container will create large layers which are unnecessary.

First approach to get the signtool.exe into the container could be this Dockerfile script:

# escape=`
# Copyright 2023 Cisco Systems, Inc.
# MIT License
FROM mcr.microsoft.com/windows/servercore:ltsc2022
USER ContainerAdministrator

SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

# Install Visual C++ Runtime
RUN [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; `
Invoke-WebRequest "https://aka.ms/vs/17/release/vc_redist.x86.exe" -OutFile "vcredist_x86.exe"; `
Start-Process -filepath "C:\vcredist_x86.exe" -ArgumentList "/install", "/passive”, "/norestart" -Passthru | Wait-Process; `
Remove-Item -Force vcredist_x86.exe;

# Install Signing Tools from Windows SDK
COPY "Windows SDK Signing Tools-x86_en-us.msi" "C:/installers/Windows SDK Signing Tools-x86_en-us.msi"
RUN Start-Process 'C:\\installers\\Windows SDK Signing Tools-x86_en-us.msi' '/qn' -PassThru | Wait-Process;

WORKDIR "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64"

We can build the container and run the SignTool:

docker build -t signtool:v1 .
docker run -it signtool:v1 signtool.exe

This container will be large, as it will persist the original MSI installation file and any temporary files which might be created. Let’s try to optimize the image.

Building optimized container

We can use the builder pattern and prepare files required to run the signtool.exe in advance as opposed to installing it inside the container.

We can run administrative installation of the SignTool on the build machine and see the structure of the installed application files:

msiexec /a "Windows SDK Signing Tools-x86_en-us.msi" TARGETDIR=C:\dev\signtool /qn
tree C:\dev\signtool

We will learn from this that signtool.exe is just being installed into Windows Kits directory and there are four versions of it: x86, x64, arm and arm64.

Let’s use the x64 version as that’s the default platform of the Windows Server:

FROM mcr.microsoft.com/windows/servercore:ltsc2022
COPY ["C:/dev/signtool/…/x64/", "C:/signtool/"]
WORKDIR C:/signtool/

Build and run the image:

docker build -t signtool:v2 .
docker run -it signtool:v2 signtool.exe

We will get output from the SignTool which is a good sign that we can just copy it to the container and the Windows Server Core base image has all the necessary libraries to run it.

Installing Office SIP to the container

The README for Office SIP states three requirements for it:

  1. The Microsoft Visual C++ 2010 Runtime must be installed on the machine in order to use Office SIP
  2. The vbe7.dll must be locate next to the SIP libraries (msosip.dll and msosipx.dll)
  3. Register the SIP libraries using regsvr32.exe

The registration step is very tricky for developers with little experience with all the history of Windows. The straightforward command to call is regsvr32.exe msosipx.dll, but this command will fail. Not because we are running it in the container but because the default regsvr32.exerequires 64-bit libraries and must be run with elevated privileges.

The msosip.dll and msosipx.dll files are 32-bit libraries, so we must use the 32-bit version of regsvr32.exe which is located in theC:\Windows\SysWOW64\ directory. So the command to call would be:

C:\Windows\SysWOW64\regsvr32.exe msosipx.dll

The README file also offers an alternative way to register SIP: changing the wintrust.dll.inifile located in the System32 folder. As the signtool.exe file is distributed with its own copy of the wintrust.dll and wintrust.dll.ini files, we must modify those and not the system file.

And this alternative is perfect for container deployment — we just copy files next to the signtool.exe without the need to register anything. This will leave a smaller footprint in the image.

FROM mcr.microsoft.com/windows/servercore:ltsc2022
COPY ["C:/dev/signingtool/…/x64/", "C:/signtool/"]
COPY ["C:/dev/officesips/msosipx.dll", "C:/dev/officesips/vbe7.dll", "C:/dev/officesips/wintrust.dll.ini" "C:/signtool/"]
WORKDIR C:/signtool/

Build and run again:

docker build -t signtool:v3 .
docker run -it signtool:v3 signtool.exe

And nothing happens. We don’t get any output because we did not install the Visual C++ runtime files required by Office SIP (as described in the README).

From the PEExplorer tool and Microsoft documentation we discover we actually need to install two runtimes:

  • vbe7.dll requires the Visual C++ 2010 runtime
  • msosipx.dll (and msosip.dll) require the Visual C++ 2022 runtime

Building the container with builder pattern

We can use the builder pattern to use a temporary build container to download, install and prepare required files and just copy them to the final image.

As Windows includes the curl tool we can also simplify the script by removing all the noisy PowerShell commands.

# Copyright 2023 Cisco Systems, Inc.
# MIT License
FROM mcr.microsoft.com/windows/servercore:ltsc2022 AS builder
USER ContainerAdministrator

# Install Visual C++ Runtime and apps into the builder container
RUN curl -L https://aka.ms/vs/17/release/vc_redist.x86.exe — output vcredist_x86_2022.exe
RUN vcredist_x86_2022.exe /install /passive /norestart

COPY ["./installers/", "C:/installers/"]
RUN cd installers && msiexec /i "Windows SDK Signing Tools-x86_en-us.msi" /qn /norestart

# Build final image with signtool.exe and Office SIP packages
FROM mcr.microsoft.com/windows/servercore:ltsc2022
USER ContainerAdministrator

COPY -from=builder ["C:/Windows/SysWOW64/vcruntime140.dll", "C:/Windows/SysWOW64/msvcp140.dll", "C:/Windows/SysWOW64/"]
COPY -from=builder ["C:/Program Files (x86)/Windows Kits/10/bin/10.0.22621.0/x86/", "C:/signtool/"]
COPY ["officesips/msosip.dll", "officesips/msosipx.dll", "officesips/vbe7.dll", "config/wintrust.dll.ini", "C:/signtool/"]

With this image we can successfully sign all Windows application binaries (DLL, EXE, MSI and other files) and Microsoft Office documents in a container which can be easily managed by devops.

And the results of these optimizations? Running docker history will show the SignTool require just 8MB of data. Which is a great improvement from the original build which took 112MB of space.

Testing out the code signing

To test code signing we can generate a self-signed authenticode certificate named acmecert.pfs, copy it to the container image and use it locally.

Use PowerShell to generate new certificate:

$cert = New-SelfSignedCertificate -Type CodeSigningCert `
-Subject "ACME Code Signing" `
-CertStoreLocation cert:\CurrentUser\My
$mypwd = ConvertTo-SecureString -String "acme" -Force -AsPlainText
$cert | Export-PfxCertificate -FilePath acmecert.pfx -Password $mypwd

Using the certificate in the container:

signtool.exe sign /f acmecert.pfx /p acme /fd sha256 SignableFile.bin

--

--

Jozef Izso
Slido developers blog

Building the ultimate presentation experience. Software devevleoper at Slido.