Artículo técnico

Evidencia LTV de PAdES y seed values en HotPDF

Un PDF que acaban de firmar es una firma B-B y nada más. Prueba quién firmó y que los bytes no se movieron, pero no lleva prueba de que el certificado del firmante era válido en el momento de la firma, así que un validador de aquí a años tendría que salir a buscar datos de revocación que quizá ya no existan. Cerrar ese hueco significa escribir respuestas OCSP y CRL en el Document Security Store a nivel de documento, y en HotPDF eso es una llamada: PopulatePAdESLTVEvidence recorre cada firma cargada, deriva las solicitudes de revocación del conjunto de certificados, las ejecuta por un transporte que ustedes suministran y escribe el material obtenido más la cadena CMS en el DSS. Devuelve el número de firmas cuya evidencia se guardó, o menos uno cuando el documento no tiene ningún campo de firma

La decisión de diseño que vale entender antes de usarla es que la biblioteca nunca abre un socket. Cada byte que llega de la red llega a través de una callback que ustedes escribieron. Eso no es cautela por la cautela; es la única manera de que esta función funcione dentro de los entornos que de verdad exigen validación a largo plazo

Por qué la biblioteca se niega a hacer su propio HTTP

Porque los lugares que exigen firmas B-LT son los lugares donde a una biblioteca no se le puede confiar la red. Los servicios de firma corren detrás de proxies con autenticación y raíces corporativas. Los niveles de firma aislados de la red no tienen ruta a ningún respondedor y deben recibir evidencia cacheada. Los regímenes de auditoría exigen que cada solicitud saliente quede registrada por la aplicación, no enterrada en una dependencia. Y las suites de prueba necesitan respuestas deterministas, algo imposible si la biblioteca marca por su cuenta

El transporte es una referencia de función simple con una forma fija, así que la política sigue siendo suya. HotPDF les entrega un record de solicitud que describe exactamente qué obtener, incluido el tipo de contenido y un tope de tamaño de respuesta, y ustedes devuelven los bytes más un estado

Flujo de PopulatePAdESLTVEvidence de HotPDF: el transporte FetchEvidence suministrado por quien llama, campos del record de solicitud y estados por firma
Cada byte de red pasa por su callback FetchEvidence, y cada firma recibe su propio estado para que un timeout nunca aborte la pasada
function FetchEvidence(const Request: THPDFSignatureEvidenceRequest;
  Attempt: Integer; CancellationToken: THPDFCancellationToken;
  out Response: TBytes; out RetryAfterMS: Cardinal;
  out ErrorMessage: UnicodeString): THPDFSignatureEvidenceTransportStatus;
begin
  RetryAfterMS := 0;
  try
    // Request.Kind dice si esto es un OCSP POST o un CRL GET;
    // Request.ContentType y Request.Body ya vienen preparados,
    // y Request.MaxResponseBytes es el tope que deben respetar
    Response := HttpExchange(Request.URI, Request.ContentType,
      Request.Body, Request.MaxResponseBytes);
    Result := setsSucceeded;
  except
    on E: Exception do
    begin
      ErrorMessage := E.Message;
      // setsRetry deja que la política de reintentos espere; usen
      // setsPermanentFailure para un 404 o una URL mala
      Result := setsRetry;
    end;
  end;
end;

// Actualización de B-B a B-LT en una llamada para cada firma del archivo cargado
var
  Pdf: THotPDF;
  Upgraded: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.BeginIncrementalUpdate('signed.pdf');
    Upgraded := Pdf.PopulatePAdESLTVEvidence(FetchEvidence,
      THPDFSignatureEvidenceRetryPolicy.Default);
    if Upgraded > 0 then
      // Guardado de solo anexado: los bytes que cubren las firmas
      // existentes se conservan verbatim
      Pdf.SaveIncrementalUpdate('signed-lt.pdf');
  finally
    Pdf.Free;
  end;
end;

Los fallos son por firma, no por documento. Un respondedor que da timeout para un firmante se salta el material de ese firmante y deja intacto el resto de la pasada, que es el comportamiento que se quiere en un lote: evidencia parcial le gana a una corrida abortada, y el valor de retorno les dice cuántas firmas mejoraron realmente

La cadena que el CMS olvidó incluir

La comprobación de revocación necesita el certificado del emisor, y una cantidad sorprendente de pilas de firma omiten los intermedios del contenedor CMS. La vía de recuperación es la extensión Authority Information Access, método de acceso 1.3.6.1.5.5.7.48.2, que anuncia una URL donde se puede descargar el certificado del emisor. HPDFFetchAIAIntermediates recorre esas URL por el mismo transporte, extrae el DER de cada respuesta y devuelve solo los certificados que el CMS no traía ya, indexados por hash DER para que duplicados y ciclos no puedan girar

Dos detalles deciden si esto funciona contra autoridades de certificación reales. El primero es la codificación: los puntos de acceso de las CA sirven el certificado como DER puro casi tan a menudo como PEM con armadura, y no hay un tipo de contenido confiable para distinguirlos. La prueba robusta es textual y luego estructural. Busquen el marcador -----BEGIN CERTIFICATE-----, quiten la armadura y decodifiquen base64 si está presente, y en ambas vías confirmen que el primer byte del resultado es $30, la etiqueta DER de un SEQUENCE. El segundo es la profundidad: un intermedio obtenido puede anunciar a su vez una URL AIA para su propio emisor, así que el recorrido añade candidatos nuevos a la cola y completa cadenas a las que les faltan dos o tres saltos. Eso hay que acotarlo, y para eso está el parámetro MaxFetch

Diagrama de finalización de cadena AIA para HotPDF: obtención de URL caIssuers, prueba PEM frente a DER, deduplicación por hash DER y tope de profundidad MaxFetch
HPDFFetchAIAIntermediates recorre las URL caIssuers por el mismo transporte, prueba la armadura PEM y acota la cola con MaxFetch

Qué es un seed value de firma y por qué falla en silencio

Un seed value es una restricción que el autor del documento adjunta a un campo de firma para decirle al firmante qué tipo de firma es aceptable: qué SubFilter, qué algoritmo de resumen, qué motivos, qué versión mínima de PDF, si la información de revocación debe ir incrustada. Vive en un diccionario /SV del campo y se define en ISO 32000-1 §12.7.5.5. HotPDF lo escribe con AttachPAdESSeedValue y lo comprueba con CheckLoadedSignatureSeedValue, que devuelve True cuando el campo no tiene restricciones o todas las presentes pasan, y con False nombra la primera restricción que falla mediante un parámetro de salida que pueden poner directo en un mensaje de error

El mecanismo que hace fácil equivocarse con los seed values es la entrada de banderas /Ff descrita en §12.7.5.5.3. Un bit encendido marca su restricción como obligatoria: un desajuste es un error y el firmante debe rechazar. Un bit apagado marca la misma restricción como preferencia: el valor filtra lo que la interfaz debe ofrecer y nada más. De ahí siguen dos trampas. Primero, /Ff vive dentro del diccionario /SV, no en la anotación de widget, así que el código que lee el /Ff a nivel de campo recibe una respuesta vacía para siempre y concluye que nada es obligatorio. Segundo, las asignaciones de bits no son una simple secuencia de uno, dos, cuatro, ocho; en HotPDF el escritor emite 2 para SubFilter, 4 para MinVersion, 32 para AddRevInfo y 64 para DigestMethod. Un lector que asume bits secuenciales decodifica toda restricción como opcional y pasa todas las pruebas excepto la que importa

Tabla de bits de banderas de seed value para la firma PAdES de HotPDF que muestra los bits Ff 2, 4, 32 y 64 y el manejo de restricciones obligatorias frente a preferidas
La entrada /Ff vive dentro de /SV, y cada posición de bit decide si un desajuste es un rechazo duro o una preferencia de interfaz
var
  Violation: AnsiString;
begin
  // Preguntar al campo si el perfil con el que vamos a firmar está permitido
  if not Pdf.CheckLoadedSignatureSeedValue(0, 'ETSI.CAdES.detached',
       'SHA256', 'Approved for payment', 1, Violation) then
    raise Exception.Create('Signature field rejects this profile: ' +
      String(Violation));
  // Restricción satisfecha: seguir con la pasada de firma
end;

La prueba que expuso el error de decodificación original no era una prueba positiva. Era la aserción de que un desajuste obligatorio debe rechazarse, y es el único tipo de prueba que puede atrapar esta clase de defecto: un decodificador que lee el diccionario equivocado o las posiciones de bits equivocadas produce "ninguna restricción violada" para toda entrada, que se ve exactamente como el comportamiento correcto hasta que violan deliberadamente una

Dónde queda esto en la escalera LTV

Cuatro peldaños, y cada uno necesita el de abajo. B-B es la firma desnuda. B-T añade una marca de tiempo confiable, que fija el momento de la firma para que un validador sepa contra qué instante evaluar la revocación. B-LT añade la evidencia de revocación al DSS, que es lo que PopulatePAdESLTVEvidence automatiza. B-LTA añade marcas de tiempo de documento que se renuevan antes de que la anterior se debilite, extendiendo la validez indefinidamente; HotPDF lo expone como RenewPAdESLTATimestamp, que anexa una marca de tiempo nueva como revisión incremental y preserva intactas todas las firmas, marcas de tiempo y entradas DSS anteriores

El modelo de actualización incremental es la única forma correcta de añadir evidencia a un documento firmado, porque reescribir el archivo rompería los rangos de bytes que las firmas existentes cubren. Si necesitan razonar sobre qué cambió entre revisiones, y si esos cambios son del tipo que una firma permite, ese análisis se cubre aparte en el análisis de revisiones DocMDP y FieldMDP. El pipeline de firma en sí, incluidas las fuentes de certificados y las trampas de orden de bytes, está en el recorrido de firma PAdES, y el lado de validación está en verificar firmas en documentos cargados

Una advertencia práctica sobre el orden. Recojan la evidencia lo antes posible después de firmar, idealmente en el mismo trabajo. Los respondedores que pueden responder por un certificado están en línea mientras el certificado está vigente y desaparecen años después, así que un documento que sale de su pipeline como B-B quizá nunca pueda actualizarse de nuevo. HotPDF corre como un componente VCL nativo para Delphi y C++Builder, y toda la pasada de evidencia es en proceso salvo su propio transporte; los perfiles soportados están en la página del producto HotPDF Delphi PDF component