Um instalador não assinado é a primeira coisa que dá errado aos olhos do cliente. O SmartScreen avisa sobre um publicador desconhecido, o prompt do UAC não mostra nenhum nome, e um pacote MSIX simplesmente não se instala, porque o Windows só instala um pacote que traga uma assinatura válida.
O sgcSign 2026.10 adiciona dois componentes para isso. TsgcMSISigner assina arquivos .msi e .msp do Windows Installer, e TsgcAppxSigner assina pacotes .msix e .appx e seus bundles. Ambos funcionam exatamente como o assinador Authenticode que você já deve estar usando para um EXE: um provedor de chave, uma autoridade de carimbo de data e hora opcional, uma única chamada.
Um MSI e um MSIX assinados a partir do programa Delphi, e a verificação de publicador impedindo um pacote que nunca se instalaria. Também no YouTube.
O programa
Um programa de console que escolhe o assinador a partir da extensão do arquivo, assina com SHA-256 e, quando uma autoridade de carimbo de data e hora é informada, adiciona um carimbo RFC 3161.
program SignInstaller;
{$APPTYPE CONSOLE}
uses
SysUtils, StrUtils, sgcSign_Authenticode, sgcSign_MSI, sgcSign_APPX,
sgcSign_KeyProvider_PFX, sgcSign_TSA;
var
PFX: TsgcPFXKeyProvider;
TSA: TsgcTSAClient;
Signer: TsgcAuthenticodeSigner;
begin
if ParamCount < 4 then
begin
WriteLn('usage: SignInstaller <input.msi|input.msix> <output> ' +
'<file.pfx> <password> [tsa-url]');
Halt(1);
end;
if MatchText(ExtractFileExt(ParamStr(1)), ['.msi', '.msp']) then
Signer := TsgcMSISigner.Create(nil)
else
Signer := TsgcAppxSigner.Create(nil); // .msix .appx and bundles
PFX := TsgcPFXKeyProvider.Create(nil);
TSA := TsgcTSAClient.Create(nil);
try
try
PFX.LoadFromFile(ParamStr(3), ParamStr(4));
Signer.KeyProvider := PFX;
Signer.Hash := ahSHA256;
Signer.Level := alBES;
if ParamCount > 4 then
begin
TSA.URL := ParamStr(5);
Signer.TSAClient := TSA;
Signer.Level := alT;
end;
if Signer is TsgcAppxSigner then
begin
WriteLn('Publisher : ', sgcAppxReadPublisher(ParamStr(1)));
WriteLn('Certificate: ', PFX.Certificate.SubjectRFC2253);
TsgcAppxSigner(Signer).SignFile(ParamStr(1), ParamStr(2));
end
else
TsgcMSISigner(Signer).SignFile(ParamStr(1), ParamStr(2));
WriteLn('Signed: ', ParamStr(2));
except
on E: Exception do
begin
WriteLn('Error: ', E.Message);
ExitCode := 1;
end;
end;
finally
Signer.Free;
TSA.Free;
PFX.Free;
end;
end.
Ambos os assinadores descendem de TsgcAuthenticodeSigner, então o provedor de chave, o digest, o nível do carimbo de data e hora e a descrição são definidos na base comum exatamente como para um EXE, e SignFile pode ser chamado através do tipo base assim como na classe concreta.
Assinando um pacote do Windows Installer
Um .msi não é um arquivo PE. É um arquivo composto, um pequeno sistema de arquivos de streams e storages, e o digest que o Windows verifica é calculado sobre esses streams em uma ordem precisa, deixando de fora os streams de assinatura. TsgcMSISigner calcula esse digest, monta a assinatura PKCS#7 e a grava no stream DigitalSignature do pacote. SHA-1, SHA-256, SHA-384 e SHA-512 são todos aceitos, e um carimbo de data e hora RFC 3161 mantém a assinatura válida depois que o certificado expira.
Pacotes de patch (.msp) são assinados da mesma forma. Com AppendSignature ativado, um pacote que já está assinado recebe uma segunda assinatura ao lado da primeira, da mesma forma que signtool /as anexa uma a um EXE.
Assinando um pacote MSIX ou APPX
Um MSIX é um ZIP com um mapa de blocos, e o que o Windows assina não é um hash do arquivo, mas uma pequena estrutura de digests sobre suas partes. TsgcAppxSigner monta essa estrutura, a assina e grava o resultado no pacote como AppxSignature.p7x. Se o pacote ainda não declara a parte de assinatura, ela é adicionada. O Windows só aceita SHA-256 para um pacote, então esse é o único digest oferecido, e assinar um pacote que já está assinado substitui a assinatura anterior.
A verificação de publicador
Este é o erro que custa uma tarde inteira. O Publisher no manifesto do pacote precisa ser exatamente o assunto do certificado de assinatura. Quando os dois diferem, o Windows não relata uma assinatura inválida. Ele deixa de reconhecer o pacote e o chama de um formato de arquivo que não consegue verificar, o que parece uma build corrompida.
TsgcAppxSigner verifica isso antes de assinar e para com uma mensagem que nomeia os dois valores:
> SignInstaller.exe DemoApp-wrongpublisher.msix out.msix ..\certs\demo.pfx demo
Publisher : CN=Someone Else
Certificate: CN=sgcSign Demo Code Signing,O=eSeGeCe Demo
Error: APPX: the signing certificate subject "O=eSeGeCe Demo, CN=sgcSign Demo Code Signing"
does not match the Publisher declared in AppxManifest.xml ("CN=Someone Else"). Windows
requires them to be identical and refuses a package where they differ; signtool rejects
the same combination with 0x8007000B. Sign with a certificate whose subject matches the
manifest, or rebuild the package with the Publisher set to the certificate subject.
A comparação é feita por tipo e valor de atributo, não como uma string, então diferenças de espaçamento e de ordem entre o manifesto e o certificado não causam um alarme falso. ValidatePublisher (True por padrão) desativa a verificação, e sgcAppxReadPublisher e sgcAppxDNMatches permitem que você execute a mesma comparação por conta própria, por exemplo para reprovar uma build antes que ela chegue à etapa de assinatura.
Verificando o resultado
> SignInstaller.exe DemoApp.msi DemoApp-signed.msi ..\certs\demo.pfx demo http://timestamp.digicert.com
Signed: DemoApp-signed.msi
> SignInstaller.exe DemoApp.msix DemoApp-signed.msix ..\certs\demo.pfx demo http://timestamp.digicert.com
Publisher : CN=sgcSign Demo Code Signing, O=eSeGeCe Demo
Certificate: CN=sgcSign Demo Code Signing,O=eSeGeCe Demo
Signed: DemoApp-signed.msix
> Get-AuthenticodeSignature DemoApp-signed.msi, DemoApp-signed.msix |
Format-List Path, @{n='Signer';e={$_.SignerCertificate.Subject}},
@{n='TSA';e={$_.TimeStamperCertificate.Subject}}
Path : DemoApp-signed.msi
Signer : CN=sgcSign Demo Code Signing, O=eSeGeCe Demo
TSA : CN=DigiCert SHA256 RSA4096 Timestamp Responder 2026 1, O="DigiCert, Inc.", C=US
Path : DemoApp-signed.msix
Signer : CN=sgcSign Demo Code Signing, O=eSeGeCe Demo
TSA : CN=DigiCert SHA256 RSA4096 Timestamp Responder 2026 1, O="DigiCert, Inc.", C=US
signtool verify /pa /v no Windows encontra o mesmo assinante e o carimbo de data e hora da DigiCert em ambos os arquivos, e o digest que ele calcula é o mesmo que foi assinado. Sua única reclamação é 0x800B010A, porque a cadeia de demonstração termina em uma raiz de teste autoassinada que não está instalada na máquina. Um digest ou uma assinatura corrompidos resultariam em 0x80096010 no lugar. TsgcAuthenticodeVerifier reconhece sozinho um pacote de instalador e um pacote MSIX, então a mesma chamada que verifica um EXE verifica esses também.
No servidor e na linha de comando
O sgcSign Server assina os dois formatos em /api/v1/sign/msi e /api/v1/sign/appx, e a ferramenta de linha de comando aceita --format msi e --format appx. Ambos também têm uma rota apenas de hash, para que um instalador grande nunca atravesse a rede, assunto do próximo post desta série.
Disponibilidade
TsgcMSISigner e TsgcAppxSigner são lançados no sgcSign 2026.10 para Delphi e C++Builder, junto com as rotas do servidor e os formatos da linha de comando. Eles compilam e funcionam no Linux, macOS, iOS e Android, além do Windows. Cada propriedade está documentada na ajuda online do sgcSign.
Perguntas sobre um pacote que não assina ou não instala? Entre em contato, e você receberá uma resposta de quem escreveu o código.
