Novedades de sgcSign 2026.9.0

· Versiones
sgcSign 2026.9.0, componentes de firma digital para Delphi y C++ Builder

sgcSign 2026.9.0 es una versión importante. La mayor parte surge de peticiones de clientes, y se concentra en tres áreas: saber con qué certificado vas a firmar, construir una firma que un validador siga aceptando dentro de diez años, y verificar una firma contra algo que no sea ella misma.

Este artículo repasa las nuevas características con el código Delphi de cada una. Al final hay además una sección breve sobre las firmas creadas con versiones anteriores que conviene volver a generar.

Listas de certificados entre las que puedes elegir

Enumerar certificados devolvía antes una lista de nombres visibles, que basta para rellenar un combo y no basta para tomar una decisión. Dos tarjetas de la misma autoridad, emitidas a la misma persona, se ven idénticas en esa lista.

La enumeración lleva ahora la huella SHA-1, el identificador fiscal, el número de serie, el emisor y las fechas de validez, y funciona igual para el almacén de certificados de Windows, un token PKCS#11 y un archivo PFX. Se pueden filtrar los certificados caducados y los que no tienen clave privada. La huella se pasa directamente a SelectCertificateByThumbprint, así que el certificado que eligió el usuario es el certificado que firma.

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;

La llamada sin parámetros no cambia, así que el código existente sigue funcionando.

Tarjetas multi-slot inventariadas sin PIN

Una tarjeta de firma cualificada contiene a menudo más de un certificado, cada uno tras su propio PIN. Las tarjetas polacas son el caso habitual, una tarjeta Certum con dos perfiles o una tarjeta PWPW Sigillum con tres contenedores. Pedir al usuario tres PIN solo para mostrarle una lista no es una interfaz viable.

Ahora se puede inventariar un token PKCS#11 sin iniciar sesión en absoluto. TokenSlotCount indica cuántos slots contienen realmente un token, que es el rango que merece la pena recorrer, y cada entrada registra el slot y la etiqueta del token de los que procede, de modo que el PIN correcto se pide solo cuando ese certificado es el elegido.

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;

Encontrar el certificado que emitió el tuyo

Los perfiles de firma de largo plazo necesitan el certificado emisor, y la mayoría de las tarjetas de firma cualificada llevan solo el tuyo. Dos nuevas llamadas, presentes en todos los proveedores de claves, lo localizan: GetIssuerCertificate devuelve el certificado que emitió aquel con el que estás firmando, y GetCertificateChain devuelve toda la ruta por encima de él. La correspondencia se comprueba criptográficamente y no por nombre, así que una autoridad que ha renovado su clave de firma no se confunde con su predecesora.

Dónde buscar es una decisión, así que es una propiedad. Por defecto se busca en el almacén de certificados de Windows, iluLocalStore busca en archivos PEM o DER que distribuyes con tu aplicación, e iluAIA descarga el certificado desde la dirección que hay dentro del tuyo, que está desactivado por defecto porque sale a la red.

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;

Dos nuevos perfiles PAdES

spPAdESBasicT firma con un sello de tiempo incrustado y sin datos de revocación, que es lo que quieres cuando la firma solo tiene que demostrar cuándo se hizo. spPAdESDocumentArchive va en la dirección contraria y añade un sello de tiempo de archivo sobre el perfil de largo plazo, cubriendo el documento completo incluidos sus datos de revocación, de modo que el archivo se puede seguir comprobando después de que haya pasado la ventana de validez del primer sello de tiempo.

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;

Los certificados informan de todo lo que llevan

El sujeto y el emisor informaban antes de los siete atributos que reconocía el analizador, y descartaban el resto. Ahora informan de todos los atributos del certificado, la dirección postal se decodifica en líneas legibles, y cualquier atributo se puede leer por su 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;

Peticiones de sello de tiempo firmadas

Algunas autoridades de sellado de tiempo cualificadas, las polacas en particular, no responden a una petición RFC 3161 simple. Quieren que la propia petición vaya envuelta en un CMS SignedData y firmada. Eso es ahora una propiedad, en lugar de algo que construyes a mano.

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;

La forma por defecto coincide con una petición aceptada por la autoridad de sellado de tiempo PWPW Sigillum. El código existente sigue enviando una petición simple, nada cambia salvo que establezcas RequestFormat.

Certificados cruzados Authenticode

La firma de un driver en modo kernel tiene que encadenar hasta la Microsoft Code Verification Root a través de un certificado cruzado, que es lo que incrusta signtool /ac. sgcSign puede ahora incrustar certificados adicionales de la misma 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;

Se propagan a todas las firmas anidadas, y no añadir ninguno deja la firma byte a byte tal y como estaba. El servidor de firma acepta un campo add_certs y la CLI una opción --add-cert repetible.

Verificación con anclas de confianza

Este es el cambio más importante de la versión. Hasta ahora el verificador tomaba el certificado de firma del propio documento que estaba comprobando y confirmaba que esa clave había firmado ese documento. Eso demuestra que quien escribió el documento escribió también la firma que contiene, y nada más. Cualquiera puede producir un documento que pase la comprobación.

A la verificación se le pueden dar ahora anclas de confianza, y construye y comprueba la cadena de certificados contra ellas. Un ancla se identifica por su huella SHA-256 o verificando bajo su propia clave, nunca por nombre.

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;

Un verificador sin ningún ancla devuelve el mismo veredicto que devolvía antes, así que nada se rompe al actualizar. Una cosa sí cambia: el informe ETSI TS 119 102-2 ya no dice total-passed para una firma que nunca se encadenó a un ancla, dice indeterminate con NO_CERTIFICATE_CHAIN_FOUND. Los informes guardados de versiones anteriores hay que volver a generarlos.

Un único transporte HTTP, con proxies

La librería hace peticiones de red desde varios sitios: el cliente de sellado de tiempo, los clientes de OCSP y de listas de revocación, la descarga de la lista de confianza de la UE y los proveedores de claves en la nube. Cada uno tenía su propia idea de cómo hacerlas. Ahora comparten un único transporte con una sola propiedad 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;

El proxy puede ser el de toda la máquina, ninguno en absoluto, una dirección explícita, o la configuración por usuario resuelta mediante WPAD o un script PAC, que es lo que hace el navegador. Cada ajuste tiene como valor por defecto lo que hacían antes esas peticiones. Para una pasarela que estos ajustes no puedan describir, un nuevo evento OnHTTPRequest sustituye por completo al transporte.

Otras cosas menores que conviene conocer

Nonces OCSP. La petición de revocación lleva ahora un nonce aleatorio del generador criptográfico del sistema y la respuesta se comprueba contra él. Una respuesta que no devuelve ningún nonce se sigue aceptando, porque RFC 6960 permite respuestas pregeneradas, pero una que devuelve un nonce distinto se rechaza. NonceEnabled desactiva la extensión para un respondedor que no la admita.

Fijado del pivote de la lista de confianza de la UE. Una nueva propiedad RequirePinnedPivot decide si la lista de listas de confianza tiene que encadenar con una de las huellas de pivote fijadas del Diario Oficial, con LOTLPivotPinned y LastPivotFingerprint informando del resultado. La comprobación existía y no se llamaba desde ningún sitio. Las constantes fijadas que se distribuyen siguen siendo los marcadores de posición documentados, así que se informa de un fallo hasta que rellenas huellas reales y activas la propiedad.

Un digest que eliges tú. CAdES y PKCS#11 ganan ambos una propiedad HashAlgorithm, con SHA-256 por defecto para que el código existente produzca los mismos bytes. CAdES escribía SHA-256 en cada algoritmo de digest como un literal, y PKCS#11 elegía su cabecera DigestInfo a partir de la longitud de lo que recibía, así que no se podía expresar ningún otro digest. Una tarjeta puede ahora firmar con SHA-1, SHA-256, SHA-384 o SHA-512 según se le pida.

ASiC con un callback de firma. Una nueva sobrecarga de BuildCAdES recibe un callback en lugar de los bytes de firma ya terminados. Construye primero META-INF/ASiCManifest.xml, entrega esos bytes exactos a tu callback y guarda lo que devuelve como META-INF/signature.p7s, que es el único orden en el que la firma puede cubrir el manifiesto. Para ASiC-S, que no lleva manifiesto, el callback recibe el propio documento de datos. GetCAdESSignedData devuelve los mismos bytes para quien prefiera dos pasos explícitos.

Certificados en KMS en la nube. AWS KMS y Google Cloud KMS ganan SetCertificate y SetCertificateFromFile, igualando el par que HashiCorp Vault ya tenía. Ambos servicios entregan una clave pública desnuda, y antes no había manera de indicar a ninguno de los dos proveedores qué certificado X.509 le corresponde.

Un sello de tiempo desde la máquina que ejecuta la CLI. La línea de comandos sgcsign gana --tsa-direct, que pregunta a la autoridad de sellado de tiempo directamente en lugar de pasar por el sgcSign Server. Pásalo junto con --tsa.

El servidor de firma

El lado del servidor tiene su propia lista. Una firma Authenticode puede llevar ahora más de dos firmas anidadas con un certificado distinto para cada una, mediante una lista ordenada hash_algorithms como sha1,sha256,sha384 o una lista ordenada providers como certA:sha256,certB:sha1, hasta cuatro entradas en cualquiera de los dos casos. Eso sirve para distribuir un único archivo firmado por un certificado que caduca y por su sustituto. Todos los certificados se comprueban contra los permisos de la clave API antes de que empiece ninguna firma.

Se pueden firmar archivos de catálogo de Windows: el endpoint de subida acepta catalog como formato y firma un archivo .cat existente del tipo que produce makecat, de modo que un paquete de drivers se firma igual que un programa.

Un nuevo endpoint /api/v1/sign/raw firma un digest que ya has calculado y devuelve solo el valor de la firma, sin envoltorio PKCS#7, sin atributos firmados y sin sello de tiempo. Eso es exactamente lo que pide signtool a través de su callback /dlib. Como firma cualquier digest que se le entregue, está desactivado por defecto y se activa proveedor a proveedor con allow_raw_sign.

Las claves API y los usuarios que las crean están ahora aislados por proyecto, un administrador de proyecto gestiona las claves de su propio proyecto, y las claves se pueden activar y desactivar en lugar de solo revocarse sin vuelta atrás. El límite de tasa y la cuota diaria de cada clave se pueden editar después de crear la clave. Un nuevo ajuste SessionAbsoluteMaxMin limita la duración total de una sesión de administrador a doce horas por defecto, porque antes cada petición autenticada desplazaba la caducidad hacia adelante sin ningún tope. El registro de auditoría se puede filtrar por dirección del cliente, en la consola y en la exportación CSV, con una dirección parcial que coincide por la izquierda. Y unos nuevos ajustes de cabeceras reenviadas, desactivados por defecto, recuperan la dirección real del cliente cuando el servidor se ejecuta detrás de un proxy inverso, y solo se creen cuando la conexión llega desde un proxy de confianza incluido en la lista.

Firmas que deberías volver a generar

Tres defectos de versiones anteriores producían archivos estructuralmente incorrectos, y actualizar no repara un archivo que ya está escrito. Si alguno de estos casos describe lo que firmaste, vuelve a firmarlo con 2026.9.0.

La verificación cambió en la misma dirección. La verificación Authenticode no comprobaba nunca una firma, recalculaba el hash del archivo y lo comparaba con el que había en la firma, así que falsificar un archivo que sgcSign daba por firmado válidamente no requería ninguna clave privada. Las respuestas de revocación y los tokens de sello de tiempo se incrustaban sin verificarse. La lista de confianza de la UE se descargaba y se usaba sin verificar absolutamente nada. Todos ellos hacen ahora la comprobación que su nombre implica, y el detalle completo de cada uno está en el registro de cambios.

Cómo conseguirlo

sgcSign 2026.9.0 ya está disponible, con el código fuente completo y un año de actualizaciones, para Delphi 7 hasta Delphi 13 Florence, las versiones equivalentes de C++ Builder, y .NET.

Página del producto · Descargar la versión de prueba · Registro de cambios

¿Preguntas o comentarios? Ponte en contacto, recibirás una respuesta de las personas que escribieron el código.