sgcSign 2026.9.0 é uma versão grande. A maior parte dela surgiu de pedidos de clientes, e se concentra em três áreas: saber com qual certificado você está prestes a assinar, construir uma assinatura que um validador ainda vai aceitar daqui a dez anos, e verificar uma assinatura contra algo que não seja ela mesma.
Este artigo percorre os novos recursos com o código Delphi de cada um. Há também uma seção curta no final sobre assinaturas feitas por versões anteriores que devem ser refeitas.
Listas de Certificados Entre as Quais Você Pode Escolher
A enumeração de certificados costumava devolver uma lista de nomes de exibição, o que basta para preencher uma caixa de seleção e não basta para tomar uma decisão. Dois cartões da mesma autoridade, emitidos para a mesma pessoa, parecem idênticos nessa lista.
A enumeração agora traz a impressão digital SHA-1, o identificador fiscal, o número de série, o emissor e as datas de validade, e funciona da mesma forma para o repositório de certificados do Windows, um token PKCS#11 e um arquivo PFX. Certificados expirados e certificados sem chave privada podem ser filtrados. A impressão digital vai direto para SelectCertificateByThumbprint, de modo que o certificado escolhido pelo usuário é o certificado que assina.
uses
sgcSign_KeyProvider_WinCertStore, sgcSign_X509, sgcSign_Types;
var
oProvider: TsgcWindowsCertStoreProvider;
oList: TsgcX509CertificateList;
i: Integer;
begin
oProvider := TsgcWindowsCertStoreProvider.Create(nil);
Try
// only certificates that are still valid and hold a private key
oList := oProvider.EnumerateCertificateList([cfNotExpired, cfPrivateKey]);
Try
for i := 0 to oList.Count - 1 do
Memo1.Lines.Add(Format('%s | %s | %s | %s .. %s | %s',
[oList[i].Subject, oList[i].NIF, oList[i].SerialNumber,
DateToStr(oList[i].NotBefore), DateToStr(oList[i].NotAfter),
oList[i].Thumbprint]));
// and sign with exactly the one that was chosen
oProvider.SelectCertificateByThumbprint(oList[0].Thumbprint);
Finally
oList.Free;
End;
Finally
oProvider.Free;
End;
end;
A chamada sem parâmetros permanece inalterada, portanto o código existente continua funcionando.
Cartões de Múltiplos Slots Inventariados Sem PIN
Um cartão de assinatura qualificada costuma conter mais de um certificado, cada um protegido por seu próprio PIN. Os cartões poloneses são o caso mais comum, um cartão Certum com dois perfis ou um cartão PWPW Sigillum com três contêineres. Pedir três PINs ao usuário apenas para mostrar uma lista não é uma interface viável.
Um token PKCS#11 agora pode ser inventariado sem nenhum login. TokenSlotCount informa quantos slots realmente contêm um token, que é o intervalo que vale a pena percorrer, e cada entrada registra o slot e o rótulo do token de onde veio, de modo que o PIN correto só precisa ser pedido quando aquele certificado for o escolhido.
uses
sgcSign_KeyProvider_PKCS11, sgcSign_X509, sgcSign_Types;
var
oPKCS11: TsgcPKCS11Provider;
oList: TsgcX509CertificateList;
i: Integer;
begin
oPKCS11 := TsgcPKCS11Provider.Create(nil);
Try
oPKCS11.LibraryPath := 'C:\Windows\System32\cryptoCertum3PKCS.dll';
ShowMessage(Format('%d slots hold a token', [oPKCS11.TokenSlotCount]));
// walks every slot that holds a token, never logs in, never needs a PIN
oList := oPKCS11.EnumerateCertificateListAllSlots([cfNotExpired]);
Try
for i := 0 to oList.Count - 1 do
Memo1.Lines.Add(Format('slot %d (%s): %s',
[oList[i].SlotIndex, oList[i].TokenLabel, oList[i].Subject]));
Finally
oList.Free;
End;
Finally
oPKCS11.Free;
End;
end;
Encontrando o Certificado Que Emitiu o Seu
Os perfis de assinatura de longo prazo precisam do certificado emissor, e a maioria dos cartões de assinatura qualificada carrega apenas o seu próprio. Duas novas chamadas em todo provedor de chaves o encontram: GetIssuerCertificate retorna o certificado que emitiu aquele com o qual você está assinando, e GetCertificateChain retorna todo o caminho acima dele. A correspondência é verificada criptograficamente e não pelo nome, de modo que uma autoridade que trocou sua chave de assinatura não é confundida com sua antecessora.
Onde procurar é uma decisão, portanto é uma propriedade. O repositório de certificados do Windows é pesquisado por padrão, iluLocalStore pesquisa arquivos PEM ou DER que você distribui com sua aplicação, e iluAIA baixa o certificado a partir do endereço contido no seu próprio, o que está desativado por padrão porque acessa a rede.
uses
sgcSign_Classes, sgcSign_X509;
var
vIssuerDER: TBytes;
oChain: TsgcCertificateChain;
begin
oProvider.IssuerLookup := [iluSystemStore, iluLocalStore];
oProvider.IssuerFiles.Add('certs\ca-intermediate.pem');
oProvider.IssuerFiles.Add('certs\ca-root.pem');
vIssuerDER := oProvider.GetIssuerCertificate;
oChain := oProvider.GetCertificateChain;
end;
Dois Novos Perfis PAdES
spPAdESBasicT assina com um carimbo de tempo embutido e sem dados de revogação, que é o que você quer quando a assinatura só precisa provar quando foi feita. spPAdESDocumentArchive vai na direção oposta e adiciona um carimbo de tempo de arquivamento sobre o perfil de longo prazo, cobrindo todo o documento, incluindo seus dados de revogação, de modo que o arquivo continua verificável depois que a janela de validade do primeiro carimbo de tempo tiver passado.
uses
sgcSign_PAdES, sgcSign_Types;
var
oPAdES: TsgcPAdESSigner;
begin
oPAdES := TsgcPAdESSigner.Create(nil);
Try
oPAdES.KeyProvider := oProvider;
oPAdES.Profile.Profile := spPAdESDocumentArchive;
oPAdES.TSAClient := TSAClient1;
oPAdES.OCSPClient := OCSPClient1;
// revocation lists you supply yourself, for an authority whose CRL is
// issued by its own root. IssuerCertificate is resolved automatically
// when it is left empty and OCSPClient is assigned
oPAdES.CRLFiles.Add('crl\ca-intermediate.crl');
oPAdES.SignPDFFile('contract.pdf', 'contract-signed.pdf');
Finally
oPAdES.Free;
End;
end;
Certificados Informam Tudo o Que Carregam
O titular e o emissor costumavam informar os sete atributos que o analisador reconhecia, e descartavam o restante. Agora eles informam todos os atributos do certificado, o endereço postal é decodificado em linhas legíveis, e qualquer atributo pode ser lido por seu OID.
uses
sgcSign_X509;
var
i: Integer;
begin
// by OID: organizationIdentifier
ShowMessage(oCert.GetSubjectAttribute('2.5.4.97'));
// or the full list, in the order the certificate declares it
for i := 0 to oCert.SubjectAttributeCount - 1 do
Memo1.Lines.Add(oCert.SubjectAttributeOID[i] + ' = ' +
oCert.SubjectAttributeValue[i]);
end;
Requisições de Carimbo de Tempo Assinadas
Algumas autoridades de carimbo de tempo qualificadas, as polonesas em particular, não respondem a uma requisição RFC 3161 simples. Elas querem a própria requisição envolvida em um CMS SignedData e assinada. Isso agora é uma propriedade em vez de algo que você monta à mão.
uses
sgcSign_TSA;
begin
oTSA.URL := 'https://tsa.example.com';
oTSA.RequestFormat := trfCMS; // plain RFC 3161 is still the default
oTSA.KeyProvider := oProvider; // trfCMS needs one
// authorities differ in what they expect inside the wrapper
oTSA.SignOptions.IncludeSignedAttributes := True;
// and the exact bytes are available when an authority needs checking
oTSA.OnBeforeSendRequest := DoBeforeSendRequest;
oTSA.OnAfterReceiveResponse := DoAfterReceiveResponse;
end;
O formato padrão corresponde a uma requisição aceita pela autoridade de carimbo de tempo PWPW Sigillum. O código existente continua enviando uma requisição simples, nada muda a menos que você defina RequestFormat.
Certificados Cruzados Authenticode
A assinatura de um driver em modo kernel precisa encadear até a Microsoft Code Verification Root por meio de um certificado cruzado, que é o que signtool /ac incorpora. O sgcSign agora pode incorporar certificados extras da mesma forma.
uses
sgcSign_Authenticode;
var
oSigner: TsgcAuthenticodeSigner;
begin
oSigner := TsgcAuthenticodeSigner.Create(nil);
Try
oSigner.KeyProvider := oProvider;
oSigner.AddCertificateFromFile('MSCV-VSClass3.cer');
// or from bytes you already hold
// oSigner.AddCertificate(vCrossCertDER);
oSigner.SignFile('driver.sys', 'driver-signed.sys');
Finally
oSigner.Free;
End;
end;
Eles fluem para toda assinatura aninhada, e não adicionar nada deixa a assinatura byte a byte como era. O servidor de assinatura aceita um campo add_certs e a linha de comando uma opção --add-cert repetível.
Verificação Com Âncoras de Confiança
Esta é a mudança mais importante da versão. Até agora o verificador extraía o certificado de assinatura do próprio documento que estava conferindo e confirmava que aquela chave havia assinado aquele documento. Isso prova que quem escreveu o documento também escreveu a assinatura nele, e nada mais. Qualquer um pode produzir um documento que passe.
A verificação agora pode receber âncoras de confiança, e constrói e confere a cadeia de certificados contra elas. Uma âncora é identificada pela impressão digital SHA-256 ou pela verificação sob sua própria chave, nunca pelo nome.
uses
sgcSign_Verifier, sgcSign_Types;
var
oVerifier: TsgcSignatureVerifier;
begin
oVerifier := TsgcSignatureVerifier.Create(nil);
Try
oVerifier.TrustedCertificates.Add('certs\qualified-root.pem');
// or a Windows certificate store by name
oVerifier.TrustedCertificateStore := 'ROOT';
oVerifier.RequireTrustedChain := True;
oVerifier.CheckKeyUsage := True;
oVerifier.RequireCompleteRevocationCheck := True;
if oVerifier.Verify(vSignedXML) = vsValid then
ShowMessage('signed, and chained to a root you trust');
Finally
oVerifier.Free;
End;
end;
Um verificador sem âncora devolve o mesmo veredito que devolvia antes, portanto nada quebra na atualização. Uma coisa muda: o relatório ETSI TS 119 102-2 já não diz total-passed para uma assinatura que nunca foi encadeada a uma âncora, ele diz indeterminate com NO_CERTIFICATE_CHAIN_FOUND. Relatórios armazenados de versões anteriores precisam ser gerados novamente.
Um Único Transporte HTTP, Com Proxies
A biblioteca faz requisições de rede a partir de vários pontos: o cliente de carimbo de tempo, os clientes OCSP e de listas de revogação, o download da lista de confiança da UE e os provedores de chaves na nuvem. Cada um tinha sua própria ideia de como fazer uma. Agora todos compartilham um único transporte com uma propriedade HTTPOptions.
uses
sgcSign_WinHTTP;
begin
oTSA.HTTPOptions.Proxy.Mode := pxCustom; // pxSystem is the default
oTSA.HTTPOptions.Proxy.URL := 'proxy.corp.local:8080';
oTSA.HTTPOptions.Proxy.Username := 'user';
oTSA.HTTPOptions.Proxy.Password := 'secret';
// the certificate presented when the gateway asks for client authentication
oTSA.HTTPOptions.ClientCertificate.StoreName := 'MY';
oTSA.HTTPOptions.ClientCertificate.Thumbprint := 'a1b2c3...';
oTSA.HTTPOptions.MinTLSVersion := tlsTLS1_2;
end;
O proxy pode ser o da máquina inteira, nenhum, um endereço explícito, ou a configuração por usuário resolvida através de WPAD ou de um script PAC, que é o que o navegador faz. Cada configuração assume por padrão o que essas requisições faziam antes. Para um gateway que essas configurações não conseguem descrever, um novo evento OnHTTPRequest substitui o transporte por completo.
Coisas Menores Que Vale a Pena Saber
Nonces OCSP. A requisição de revogação agora carrega um nonce aleatório do gerador criptográfico do sistema e a resposta é conferida contra ele. Uma resposta que não devolve nenhum nonce continua sendo aceita, porque o RFC 6960 permite respostas pré-produzidas, mas uma que devolve um nonce diferente é recusada. NonceEnabled desativa a extensão para um respondedor que não a aceita.
Fixação de pivôs da lista de confiança da UE. Uma nova propriedade RequirePinnedPivot decide se a lista de listas confiáveis precisa encadear até uma das impressões digitais de pivô fixadas do Jornal Oficial, com LOTLPivotPinned e LastPivotFingerprint informando o resultado. A verificação existia e não era chamada de lugar nenhum. As constantes fixadas que acompanham a biblioteca ainda são os marcadores documentados, portanto uma falha é reportada até você preencher impressões digitais reais e ativar a propriedade.
Um digest à sua escolha. CAdES e PKCS#11 ganham ambos uma propriedade HashAlgorithm, com padrão SHA-256 para que o código existente produza os mesmos bytes. O CAdES costumava escrever SHA-256 em todo algoritmo de digest como um literal, e o PKCS#11 escolhia seu cabeçalho DigestInfo pelo tamanho do que recebia, de modo que nenhum outro digest podia ser expresso. Um cartão agora pode assinar com SHA-1, SHA-256, SHA-384 ou SHA-512 conforme solicitado.
ASiC com um callback de assinatura. Uma nova sobrecarga de BuildCAdES recebe um callback em vez dos bytes de assinatura já prontos. Ela constrói primeiro o META-INF/ASiCManifest.xml, entrega esses bytes exatos ao seu callback e armazena o que volta como META-INF/signature.p7s, que é a única ordem em que a assinatura pode cobrir o manifesto. Para ASiC-S, que não carrega manifesto, o callback recebe o próprio documento de dados. GetCAdESSignedData retorna os mesmos bytes para quem prefere dois passos explícitos.
Certificados de KMS na nuvem. AWS KMS e Google Cloud KMS ganham SetCertificate e SetCertificateFromFile, iguais ao par que o HashiCorp Vault já tinha. Ambos os serviços entregam apenas uma chave pública, e antes não havia como informar a nenhum dos dois provedores qual certificado X.509 lhe pertence.
Um carimbo de tempo da máquina que executa a linha de comando. A linha de comando sgcsign ganha --tsa-direct, que consulta a autoridade de carimbo de tempo diretamente em vez de passar pelo sgcSign Server. Passe-a junto com --tsa.
O Servidor de Assinatura
O lado servidor ganhou sua própria lista. Uma assinatura Authenticode agora pode carregar mais de duas assinaturas aninhadas com um certificado diferente para cada uma, através de uma lista ordenada hash_algorithms como sha1,sha256,sha384 ou de uma lista ordenada providers como certA:sha256,certB:sha1, até quatro entradas em qualquer dos casos. Isso serve para distribuir um único arquivo assinado por um certificado prestes a expirar e por seu substituto. Cada certificado é conferido contra as permissões da chave de API antes que qualquer assinatura comece.
Arquivos de catálogo do Windows podem ser assinados: o endpoint de upload aceita catalog como formato e assina um arquivo .cat existente do tipo que o makecat produz, de modo que um pacote de driver é assinado da mesma forma que um programa.
Um novo endpoint /api/v1/sign/raw assina um digest que você já calculou e retorna apenas o valor da assinatura, sem envelope PKCS#7, sem atributos assinados e sem carimbo de tempo. É exatamente o que o signtool pede através de seu callback /dlib. Como ele assina qualquer digest que lhe for entregue, vem desativado por padrão e é ativado um provedor de cada vez com allow_raw_sign.
As chaves de API e os usuários que as criam agora estão isolados por projeto, um administrador de projeto gerencia as chaves do seu próprio projeto, e as chaves podem ser ativadas e desativadas em vez de apenas revogadas em definitivo. O limite de taxa e a cota diária de cada chave podem ser editados depois que a chave é criada. Uma nova configuração SessionAbsoluteMaxMin limita a duração total de uma sessão de administrador a doze horas por padrão, porque cada requisição autenticada costumava empurrar a expiração adiante sem nenhum teto. O registro de auditoria pode ser filtrado por endereço do cliente, no console e na exportação CSV, com um endereço parcial correspondendo a partir da esquerda. E novas configurações de cabeçalhos encaminhados, desativadas por padrão, recuperam o endereço real do cliente quando o servidor roda atrás de um proxy reverso, acreditadas apenas quando a conexão chega de um proxy confiável listado.
Assinaturas Que Você Deveria Refazer
Três defeitos em versões anteriores produziram arquivos estruturalmente errados, e atualizar não repara um arquivo que já foi gravado. Se algum destes casos descrever o que você assinou, assine novamente com a 2026.9.0.
- Qualquer coisa assinada com uma chave EC. Toda assinatura PAdES, CAdES, Authenticode, NuGet e RFC 3161 feita com uma chave EC era inválida. Os padrões exigem que o valor da assinatura dentro de uma estrutura CMS seja um DER ECDSA-Sig-Value, os provedores de chaves produzem a concatenação bruta de r e s, e nada a convertia no caminho de entrada. P-256, P-384 e P-521 funcionam todos agora. Os caminhos XAdES e XML-DSig estavam corretos e foram deliberadamente deixados como estavam.
- Toda assinatura CAdES, e qualquer coisa assinada com um cartão, um token USB ou uma chave na nuvem.
SignDatanão tinha um contrato escrito e os provedores discordavam sobre se seu argumento são os dados ou um digest deles, de modo que uma assinatura CAdES era RSA sobre um hash de um hash. O argumento agora são os bytes brutos e o provedor os processa com o digest configurado. - Arquivos PAdES assinados depois que outro assinador havia usado o mesmo provedor de chaves.
Profile.HashAlgorithmera escrito uma vez no construtor e nunca mais lido, de modo que um PDF assinado depois de um assinador Facturae ou SAF-T podia ser RSA sobre um digest SHA-1 dentro de uma assinatura que declarava SHA-256.
A verificação mudou na mesma direção. A verificação Authenticode nunca conferia uma assinatura, ela recalculava o hash do arquivo e o comparava com o que estava na assinatura, de modo que forjar um arquivo que o sgcSign considerasse validamente assinado não exigia chave privada alguma. Respostas de revogação e tokens de carimbo de tempo eram embutidos sem serem verificados. A lista de confiança da UE era baixada e usada sem verificar absolutamente nada. Todos esses casos agora fazem a verificação que seu nome sugere, e o relato completo de cada um está no changelog.
Como Obter
O sgcSign 2026.9.0 já está disponível, com código-fonte completo e um ano de atualizações, para Delphi 7 até Delphi 13 Florence, as versões correspondentes do C++ Builder, e .NET.
Página do produto · Baixe a versão de avaliação · Changelog
Dúvidas ou comentários? Entre em contato, você receberá uma resposta das pessoas que escreveram o código.
