Cómo funciona la firma digital de actas con Izenpe

Desde hace muuucho tiempo he querido saber cómo funciona internamente la firma digital de actas de mi universidad (EHU). Los que me conocéis ya sabéis que cuando digo «internamente» es realmente a nivel de disección. ¿Me acompañas en el viaje? Vamos allá….

actas1

Las actas de exámenes, trabajos fin de grado, etc. se firman a través de GAUR. Te identificas como profesor, entras en la sección de Exámenes / Proceso de Firma y ahí tendrás los PDF -que previamente has calificado- listos para firmar.

Cuando pulsas en Firmar uno de los PDF se ejecutará la siguiente función JS: onclick=»a_firmar(id_pdf,id_firmante,id_version); Esos valores han sido calculados para esta sesión del usuario (del profesor). La función a_firmar llama a su vez a abrir_dialog() que lanza una URL https://gestion.ehu.es/idazki/index?query_string siguiendo este patrón para los parámetros de la query_string:

Application ID: 3 Language: CAS (Castilian/Spanish) Action: 6 Session ID: 939c91xxxxxxxxxxxxxxxxxxxxxxxxxx3837694 PDF ID: YYYYYYYY Signer ID: ZZZZZZZZZ Document number: AAAAAAA Version: 1 Timestamp: 8,98107480305963801233649306596140178816

Siguiendo esa URL, veremos lo siguiente:

actas2

En el código fuente de esa URL veremos que el PDF a firmar está codificado en base64 dentro de un JS. Si ahora pulsamos en «Firmar», se inicia el proceso de firma digital.

El browser llamará a Idazki Desktop (una aplicación residente de Izenpe, que intercepta las URL que comienzan con el protocolo idazki://). Este script del navegador pasará el PDF a firmar (en base64) a Idazki Desktop para que el usuario (el profesor) seleccione el certificado con el que quiere firmarlo. Una vez firmado, Idazki Desktop devolverá el control al navegador. Este comprobará que todo está OK y enviará el acta firmada a la EHU. Pero hemos dicho que queremos diseccionar, así que hay que meterse en harina… ok, no te aburriré con los detalles en este post, pero puedes verlos aquí si te pica la curiosidad 🙂

Para confirmar que entendí correctamente el funcionamiento del proceso se me ocurrió crear una pequeña web de prueba: https://ikasten.io/idazki.html

actas3

La idea es que puedas subir un PDF cualquiera (te dará la opción para seleccionarlo de tu disco duro) para que mi script lance la aplicación Idazki Desktop y puedas firmar el PDF con tu certificado digital. Si la firma se completa, te dará un bonito mensaje de éxito. Si no, te informará del error.

P: ¿No necesito identificarme? R: No. Idazki Desktop solo recibirá un PDF y una orden de firmarlo digitalmente. Ahí sí, verás tu lista de certificados digitales. Selecciona el tuyo, introduce el PIN y la aplicación Idazki firmará el PDF, devolviendo el control al navegador.

P: ¿No estarás guardando el PDF en tu servidor? R: No. El navegador carga el PDF en un blob base64 en local. No se envía nada al servidor. No tienes por qué creerme, abre el código fuente de idazki.html y lo verás.

Abre la URL de prueba: https://ikasten.io/idazki.html

Selecciona un PDF. Pulsa en Sign PDF. El navegador debe abrir Idazki Desktop.

actas4

Idazki Desktop debe abrir una ventana «nativa» (es una app Java usando Swing, el look&feel le delata) para que selecciones el certificado con el que quieres firmar. Pulsas Aceptar y te pedirá el PIN o la clave asociada. Lo introduces y …..

actas5

¡Bum! Si estás en macOS con un procesador ARM fallará 🙂

actas6

Con este mensaje de error:

actas7

Pero eso es otra larga historia que os contaré otro día (por qué falla, cómo encontrar el bug y cómo arreglarlo SIN tener acceso al código fuente). Una bonita historia que explica cómo parchear un binario Java del que no tienes el código fuente, usando IA por el camino.

actas8
Pero si usas un sistema operativo + procesador soportado (o el parche del que os hablo si usas macOS+ARM), todo irá bien:

actas9

Addendum:

¿Y podría bajarme el PDF firmado? Yep. Si quieres tener esa opción, he preparado otro script de prueba más completo aquí: https://ikasten.io/idazkiFull.html

actas10

Una vez firmado el PDF, pulsa en «Download Signed PDF» y podrás descargártelo. Si lo abres con alguna aplicación que soporte la visualización de firmas en PDF, como Adobe Acrobat Reader, podrás verla:

actas11

Groq Desktop Beta: A Game-Changer for MCP Support

Groq has just released Groq Desktop in beta mode (https://github.com/groq/groq-desktop-beta), and I’ve had the chance to try it out. What caught my attention was its impressive MCP support, which seems to outshine Claude Desktop. Here are three reasons why:

  1. YOLO Mode: Groq Desktop allows you to accept tool execution without asking questions, making the process smoother.
  2. On-the-fly Server Reload: Unlike Claude, where you need to restart the app to reload MCP servers, Groq Desktop lets you do it seamlessly.
  3. Hot Enable/Disable MCP Servers: You can enable or disable MCP servers on the fly, without needing to reload Groq Desktop.

These features make Groq Desktop a strong contender in the MCP support arena. Have you tried it out? What are your thoughts?

How to understand new frameworks (like OpenAI Agents SDK) using an LLM

Recently, OpenAI published its framework https://openai.github.io/openai-agents-python (A lightweight, powerful framework for multi-agent workflows). I wanted to try it out, but I didn’t have much time… so I resorted to a new technique I’ve been using lately to do quick tests on new frameworks I want to explore.

  1. Access the online documentation: https://openai.github.io/openai-agents-python/
  2. Open Firecrawl.dev. This application will allow us to crawl a website, extracting the main text into markdown or json format. The idea is to collect all the HTML documentation of the framework to be explored in plain text.
    • I have indicated that it should not include pages that contain the path ref/.+ to avoid overloading the LLM with extra context (I’ll explain this in a second)

3. Download the results:

4. Unpack and check:

5. We attach the .md as context to Claude. We can do it by drag&drop or by concatenating all in a single file using cat *.md > documentation.md and uploading that single file.

6. The prompt: Read the following info about how to create an agent with OpenAI Agents SDK. I want to create an agent that knows how to fetch info from a webpage. We can use a python function that internally uses request

7. Claude got it right on the first try, and was able to generate an agent, using the new OpenAI Agents Framework, that we can use to ask questions about any website.

Monitorizando logs de LLMs con litellm y langfuse

Contexto: has implementado o estás usando una aplicación web que internamente hace llamadas vía API a un LLM (GPT, Claude, LLama, whatever). Quieres analizar cuáles son los prompts que dicha aplicación está enviando. Necesitarás litellm (para que haga de proxy entre tu aplicación y el LLM) y langfuse, que recibirá un callback y te mostrará gráficamente todos los prompts. La idea es que litellm enviará automáticamente a langfuse una copia de cada lllamada al LLM (y de su respuesta) para que luego las puedas visualizar cómodamente.

Receta rápida:

Instala las dependencias necesarias

$ pip install litellm 'litellm[proxy]' prisma langfuse

Usa el fichero de configuración de litellm del que ya hablamos en su día en ikasten.io.

Instala Postgresql, por ejemplo, a través de docker. Para ello, usa el siguiente docker-compose.yaml:

version: '3'
services:
db:
image: postgres
restart: always
environment:
POSTGRES_DB: litellm
POSTGRES_USER: llmproxy
POSTGRES_PASSWORD: dbpassword9090
healthcheck:
test: ["CMD-SHELL", "pg_isready -d litellm -U llmproxy"]
interval: 1s
timeout: 5s
retries: 10
ports:
- "5432:5432"

(cambia el password dbpassword9090 como quieras). Postgresql es necesario para que litellm guarde información de los modelos. Necesitarás también crear un schema para postgresql a través de Prisma.

Copia el fichero schema.prisma del repositorio GitHub de litellm:

https://github.com/BerriAI/litellm/blob/main/schema.prisma

$ prisma generate

Lanza litellm :

$ DATABASE_URL="postgresql://llmproxy:dbpassword9090@localhost:5432/litellm" STORE_MODEL_IN_DB="True" LITELLM_MASTER_KEY=sk-12345  LITELLM_SALT_KEY="saltkey1234" litellm --config ./config.yaml 

Abre el panel de administración gráfica de litellm en localhost:4000/ui

Por defecto, login: admin, pass: el master_key que hayas definido. En el ejemplo: sk-12345.

Pulsa en Logging&Alerts -> Add Callback

Elige langfuse y a continuación, teclea los parámetros indicados:

Puedes obtener el public_key y secret_key creando una cuenta gratuita en langfuse. https://cloud.langfuse.com/

Crea un proyecto y obtén los api keys:

Prueba a pulsar en el Test Callback y deberías ver a los pocos segundos una nueva entrada de test en los logs de langfuse:

Más info:

https://robert-mcdermott.medium.com/centralizing-multi-vendor-llm-services-with-litellm-9874563f3062

TIL: instalar soporte llama-3-3-70b en llm

llm es una utilidad imprescindible en tu arsenal de comandos. Permite acceder desde la terminal a cualquier LLM, integrándose como un comando Unix más.

groq es una empresa que ofrece acceso a llama3 a través de su API, ejecutándose en sus veloces LPUs, de forma gratuita. Uno de los modelos más potentes que ofrece es llama-3-3-70b

Para poder usar llama3.3-70b desde el comando llm a través de groq, es necesario instalar el plugin llm-groq, por el momento a través del HEAD en su repo GitHub

$ llm install https://github.com/simonw/llm-groq/archive/refs/heads/master.zip

No te olvides de poner llama3.3 como modelo por defecto:

$ llm models default groq-llama-3.3-70b