SDK viafirma platform

Uso Viafirma Desktop por protocolo - firma

Al igual que ocurre con la autenticación, desde la versión 3.15 de viafirma platform se soporta una modalidad (inicialmente solo para windows) que permite utilizar directamente Viafirma Desktop (versión mínima: 1.6.0) invocando la aplicación por protocolo para realizar firma de documentos (incluyendo firma múltiple). Para ello, la estrategia reside en la preparación inicial de la operación mediante una invocación a servicios REST, devolviendo el servidor un código de operación y un enlace para abrir la aplicación por protocolo. Al abrirlo la aplicación pierde el control de lo que ocurre, por lo que debe iniciar un polling Javascript a otro servicio REST donde consulta el estado de la operación, sabiendo si está finalizada o no, con errores, etc. y se exponen funciones que son invocadas automáticamente (callback) en caso de error, éxito, cancelación, etc., pasando un objeto JSON con los datos de la operación.

Desde la versión 3.17.1 de Viafirma Platform y versión mínima 1.6.7 de Viafirma Desktop, se pueden utilizar filtros de CA y Serial Number tal y como se explica en el apartado de autenticación usando Viafirma Desktop.

Java

Podemos encontrar un ejemplo de uso de esta lógica de invocación directa en la aplicación de ejemplo.

Como se verá en ese ejemplo de código, se realiza una preparación de la operación, devolviendo el servidor un objeto con el código de operación y el enlace para abrir la aplicación, al igual que ocurría con la autenticación. En el caso de la firma, también se puede inicializar información similar a la de autenticación (filtros de certificado, autosend) pero obviamente también hay que enviar información de la firma: ficheros, formatos, políticas, etc.

// Now use the new prepareSignatureForDirectDesktop method
// The method requires:
// 1.- An AuthOperationRequest object (includes logic to filter certificates, autosend, etc.) - optional (can be null)
// 2.- Files to be signed, in a list of OperationFile (base64 and filename) - mandatory
// 3.- Policy object - mandatory
AuthOperationRequest authRequest = new AuthOperationRequest();
// For instance...
authRequest.setAutoSend(true);

// Policy (mandatory)
// Since viafirma-client 2.14.6 - viafirma platform 3.15.6, policy is a param of file!
Policy policy = new Policy();
policy.setTypeFormatSign(TypeFormatSign.PAdES_BASIC);
policy.setTypeSign(TypeSign.ATTACHED);
policy.addParameter(PolicyParams.DIGITAL_SIGN_PAGE.getKey(), "1");
policy.addParameter(PolicyParams.DIGITAL_SIGN_RECTANGLE.getKey(), new org.viafirma.cliente.vo.Rectangle(40,10,550,75));

// Files to be signed
byte[] documentBinaryContent = IOUtils.toByteArray(getClass().getResourceAsStream("/exampleSign.pdf"));
List<OperationFile> files = new LinkedList<OperationFile>();
OperationFile file = new OperationFile();
file.setFilename("exampleSign.pdf");
file.setBase64Content(Base64.encodeBase64String(documentBinaryContent));
file.setPolicy(policy);
files.add(file);

documentBinaryContent = IOUtils.toByteArray(getClass().getResourceAsStream("/exampleSign.pdf"));
file = new OperationFile();
file.setFilename("exampleSign2.pdf");
file.setBase64Content(Base64.encodeBase64String(documentBinaryContent));
file.setPolicy(policy);
files.add(file);

// The method returns an object with the information required to:
// a) Create a button that opens Viafirma Desktop by protocol
// b) Gets the just-prepared operation ID to start polling using Javascript
DirectDesktopInvocation directCall = viafirmaClient.prepareSignatureForDirectDesktop(authRequest, files, request);
String operationId = directCall.getOperationId();
String viafirmaDesktopLink = directCall.getViafirmaDesktopInvocationLink();

Al igual que ocurre con la autenticación, se hace una estrategia de polling Javascript para saber cuándo finaliza la firma, recibiendo callbacks para los casos de error, cancelación, imposibilidad de arrancar Viafirma Desktop (no instalado) o finalización de la operación, con la principal diferencia del JSON de firma, que en este caso es distinto del de autenticación (contiene también los datos del certificado, pero así mismo los de la operación de firma).

<script src="<%=ConfigureUtil.getViafirmaServer() %>/viafirma.js?t=<%=System.currentTimeMillis()%>">
                                     // Include this remote Javascript is mandatory, it includes the polling logic
</script>
<script>
// Customize this in your own client webapp... this is just a sample!
// When the polling detects an error, it invokes this function
function showError(response) {
       document.getElementById("loading").style = "display: none;";
       document.getElementById("signatureError").innerHTML = "Ocurrió un problema durante la firma: "+ JSON.stringify(response);
}
// If the user cancels the operation in Viafirma Desktop app, this function is invoked
function showCancel(response) {
       document.getElementById("loading").style = "display: none;";
       document.getElementById("signatureCancel").innerHTML = "La firma fue cancelada: "+ JSON.stringify(response);
}
// If the signature operation runs ok, this function is invoked - customize it with your own logic 
function showSuccess(response) {
    window.location.replace("./exampleSignatureViafirmaDesktopResult.jsp?operationId=" + response.operationId);
}
// If Viafirma Desktop is not loaded, this function is invoked
function showUnloaded() {
    alert("Viafirma Desktop no encontrado");
     document.getElementById("loading").style = "display: none;";

      document.getElementById("signatureSuccess").innerHTML = 
      "<p>Viafirma Desktop no ha sido cargado, aquí se puede incluir código para gestionar la instalación, instrucciones, etc.</p>";
}
// Here we initialize the viafirma.js polling 
function initSignature() {
       document.getElementById("signatureButton").style = "display: none;";
       document.getElementById("loading").innerHTML = "<img src='../images/icons/ajax-loader.gif' />";

       // Start the viafirma JS client: watch the status of a given operationId
       // - if the operation fails, "errorCallback" will be called
       // - if the operation is cancelled, "cancelCallback" will be called
       // - if the operation is completed: "successCallback" will be called
       // - if Viafirma client is not loaded after unloadedTime seconds: "unloadedCallback" 
       viafirma.init({
           // Here we include 
           operationId: "<%=operationId%>",
           viafirmaUrl: "<%=ConfigureUtil.getViafirmaServer() %>/",
           unloadedTime: 5,
           errorCallback: function(response) {
               showError(response);
           },
           successCallback: function(response) {
               showSuccess(response);
           },
           cancelCallback: function(response) {
               showCancel(response);
           },
           unloadedCallback: function(response) {
               showUnloaded();
           }
       });
}
</script>

.NET

Podemos encontrar un ejemplo de uso de esta lógica de invocación directa en la aplicación de ejemplo aspx - aspx.cs

El código es muy similar al ejemplo anterior Java, realizando una preparación de la operación, devolviendo el servidor un objeto con el código de operación y el enlace para abrir la aplicación.

Code behind:

public async void Firmar_ClickAsync(object sender, EventArgs e)
{
  // Iniciamos el proceso de autenticar redireccionando el usuario a Viafirma.
  ViafirmaClient clienteViafirma = ViafirmaClientFactory.GetInstance();

  AuthOperationRequest authRequest = new AuthOperationRequest();
  authRequest.AutoSend = true;

  string sessionId = HttpContext.Current.Session.SessionID;
  string[] languages = HttpContext.Current.Request.UserLanguages;
  string locale = languages[0];

  //Creamos la politica de firma
  policy pol = PolicyUtil.newPolicy(typeFormatSign.PAdES_BASIC, typeSign.ATTACHED);
  //Creamos el rectangle
  rectangle r = PolicyUtil.newRectangle(40, 10, 550, 75);
  //Seteamos la politica
  PolicyUtil.AddParameter(pol, PolicyParams.DIGITAL_SIGN_PAGE, "1");
  PolicyUtil.AddParameter(pol, PolicyParams.DIGITAL_SIGN_RECTANGLE, PolicyUtil.rectangleToJson(r));
  PolicyUtil.AddParameter(pol, PolicyParams.DIGITAL_SIGN_STAMPER_HIDE_STATUS, "true");
  PolicyUtil.AddParameter(pol, PolicyParams.DIGITAL_SIGN_STAMPER_TEXT, "Firmado por [CN] con DNI [SERIALNUMBER]\ntrabajador de [O] en el departamento de [OU]");
  PolicyUtil.AddParameter(pol, PolicyParams.DIGITAL_SIGN_STAMPER_TYPE, "QR-BAR-H");
  PolicyUtil.AddParameter(pol, PolicyParams.DIGITAL_SIGN_STAMPER_ROTATION_ANGLE, "90");

  // Recuperamos el documento a firmar.
  Assembly assembly = Assembly.GetExecutingAssembly();
  Stream fs = assembly.GetManifestResourceStream(Global.DEMO_FILE_PDF_PATH);
  byte[] datos_a_firmar = new byte[fs.Length];
  fs.Read(datos_a_firmar, 0, datos_a_firmar.Length);

  OperationFile file = new OperationFile();
  file.Filename = "example.pdf";
  file.Base64Content = System.Convert.ToBase64String(datos_a_firmar);
  file.Policy = pol;

  List<OperationFile> files = new List<OperationFile>();
  files.Add(file);

  desktopInvocation = await clienteViafirma.PrepareSignatureForDirectDesktopAsync(authRequest, files, sessionId, locale);
  System.Console.Write("OperationId: " + desktopInvocation.OperationId);
}

Al igual que ocurre con la autenticación, se hace una estrategia de polling Javascript para saber cuándo finaliza la firma, recibiendo callbacks para los casos de error, cancelación o finalización de la operación, con la principal diferencia del JSON de firma, que en este caso es distinto del de autenticación (contiene también los datos del certificado, pero así mismo los de la operación de firma).

<form id="form1" runat="server">    
                                    <%  if (desktopInvocation == null) { %>
                                    <p>
                                        <asp:button  ID="firmarBoton" runat="server" 
                                                Text="Iniciar proceso de firma"  OnClick="Firmar_ClickAsync" 
                                                Width="245px"  />
                                    </p>
                                    <% }
                                        else
                                        {
                                    %>
                                    <script src="<%=Viafirma.ViafirmaClientFactory.GetInstance().UrlPublica%>/viafirma.js">
                                         // Include this remote Javascript is mandatory, it includes the polling logic
                                    </script>
                                    <script>
                                        // Customize this in your own client webapp... this is just a sample!
                                        // When the polling detects an error, it invokes this function
                                        function showError(response) {
                                            document.getElementById("loading").style = "display: none;";
                                            document.getElementById("signatureError").innerHTML = "Ocurrió un problema durante la firma: " + JSON.stringify(response);
                                        }
                                        // If the user cancels the operation in Viafirma Desktop app, this function is invoked
                                        function showCancel(response) {
                                            document.getElementById("loading").style = "display: none;";
                                            document.getElementById("signatureCancel").innerHTML = "La firma fue cancelada: " + JSON.stringify(response);
                                        }
                                        // If the signature operation runs ok, this function is invoked - customize it with your own logic 
                                        // For instance, probably you will need to invoke an internal REST service that receives the signature response object
                                        function showSuccess(response) {
                                            document.getElementById("loading").style = "display: none;";

                                            document.getElementById("signatureSuccess").innerHTML = "<p>Operación de firma realizada con éxito. Información obtenida:</p><ul>" +
                                                "<li><strong>ID de operación</strong>: " + response.operationId + "</li>" +
                                                "<li><strong>Identificación usuario</strong>: " + response.certificateValidationData.numberUserId + "</li>" +
                                                "<li><strong>Usuario</strong>: " + response.certificateValidationData.name + " " + response.certificateValidationData.surname1 + " " + response.certificateValidationData.surname2 + "</li>" +
                                                "<li><strong>CA</strong>: " + response.certificateValidationData.shortCa + "</li>" +
                                                "<li><strong>ID Firma</strong>: " + response.signatureId + "</li>" +
                                                "</ul>";
                                        }
                                        // Here we initialize the viafirma.js polling 
                                        function initSignature() {
                                            document.getElementById("signatureButton").style = "display: none;";
                                            document.getElementById("loading").innerHTML = "<img src='./images/icons/ajax-loader.gif' />";

                                            // Start the viafirma JS client: watch the status of a given operationId
                                            // - if the operation fails, "errorCallback" will be called
                                            // - if the operation is cancelled, "cancelCallback" will be called
                                            // - if the operation is completed: "successCallback" will be called
                                            viafirma.init({
                                               // Here we include 
                                                operationId: "<%=desktopInvocation.OperationId%>",
                                                viafirmaUrl: "<%=Viafirma.ViafirmaClientFactory.GetInstance().UrlPublica%>/",
                                                errorCallback: function (response) {
                                                   showError(response);
                                                },
                                                 successCallback: function (response) {
                                                   showSuccess(response);
                                                },
                                                cancelCallback: function (response) {
                                                   showCancel(response);
                                                }
                                            });
                                        }
                                </script>
                                <p id="signatureError"></p>
                                <p id="signatureCancel"></p>
                                <p id="signatureSuccess"></p>
                                <p id="loading"></p>
                                <p id="signatureButton">
                                    <a class="button" href="<%=desktopInvocation.ViafirmaDesktopInvocationLink%>" onClick="initSignature();">Firmar con Viafirma Desktop</a>
                                </p>                                    
                                    <% }
                                    %>
                                </form>

Obsérvese que, al igual que en el caso Java, se ha incluido una query param al cargar el Javascript remoto viafirma.js, para evitar posibles problemas de caché en los usuarios en el caso de que este Javascript se modifique en futuras versiones.

Especificaciones de objetos JSON de respuesta en callbacks

  • Si el polling falla recibiendo un HTTP status distinto de 200/OK -la operación no ha finalizado todavía-, el objeto response recibido en el callback de error sólo tendrá un campo "message" y el operationId. Si la operación está finalizada, el objeto será de este tipo:
    {
      "operationId": "12345678",
      "isFinished": true,
      "isCancelled": false,
      "hasErrors": true,
      "isSignature": true,
      "errorCode": "1",
      "errorMessage": "Error description"
    }
    

Los códigos de error son los mismos que con el resto del API. Son especialmente relevantes los códigos relacionados con los estados CADUCADO (código 104) y REVOCADO (código 107) de un certificado. Si la aplicación Viafirma Desktop tiene algún problema local que comunique al servidor, se recibirá un errorCode = 2.

  • Si la operación ha sido cancelada, el callback de cancelación recibe un JSON como el siguiente:
    {
      "operationId": "12345678",
      "isFinished": true,
      "isCancelled": true,
      "hasErrors": false,
      "isSignature": true
    }
    
  • Si la operación ha acabado con éxito, la operación recibe un JSON con todos los datos de la firma y el operationId. Esta información está en el callback Javascript successCallback, y debe hacerse llegar a la lógica de servidor. No es recomendable consumir un servicio REST interno con la información de la firma, dado que sería sencillo que un atacante realizase un ataque enviando un JSON modificado. Lo recomendable es invocar a lógica de servidor con el ID de operacion, y desde la lógica de servidor utilizar el servicio de viafirma cliente determinado, o simplemente consumir el servicio REST de Platform para descargar dicha información.

Los datos incluidos en el JSON son:

  • operationId: ID de operación devuelto en el primer proceso de preparación de la operación.
  • signatureId: ID de firma de Viafirma (si son varios documentos, los IDs van separados por comas).
  • certificateValidationData, datos del certificado, con los mismos campos que la respuesta de autenticación:
    • numberUserId: número de identificador del usuario (NIF, cédula...)
    • name: Nombre del usuario
    • surname1: Primer apellido
    • surname2: Segundo apellido
    • email: Email del usuario
    • ca: Autoridad de Certificación emisora (por ejemplo Fábrica Nacional de Moneda y Timbre)
    • shortCa: Autoridad de Certificación emisora - descripción corta (por ejemplo FNMT)
    • jobTitle: Cargo del usuario
    • type: Tipo de certificado
    • cn: Common Name
    • organization: Nombre de la organización / empresa del usuario
    • certificateProperties: lista de variables del certificado (en formato key/value)
    • isValidated: booleano - obtiene valor true si el certificado ha sido validado correctamente
    • isExpired: booleano - obtiene valor true si el certificado está caducado
    • isRevoked: booleano - obtiene valor true si el certificado está revocado