Introducción
Hace un tiempo, trasteando con la referencia de Block Kit de Slack, me di cuenta de algo peculiar: el bloque de vídeo. Cuando vi que aceptaba una video_url, lo primero que pensé fue: ¿cómo distingue entre cualquier contenido y un vídeo de verdad? ¿Habrá algún requisito o limitación para incrustarlo?
Pues resulta que no. No hay ninguna comprobación en runtime fuera de que la video_url que le pasas sea accesible y responda con un código 2xx o 3xx.
Pasadas estas comprobaciones, no es más que un simple iframe.
Así que hace unos días se me ocurrió una idea. ¿Y si hago un bot de Slack que te deje encriptar mensajes con un par de claves y mandarlos por Slack?
La idea es simple. Dentro de tu cliente, usando las APIs criptográficas del navegador, creas un par de claves, encriptas la clave privada y se la mandas al servidor. Luego, cada vez que quieras hacer una operación (firmar, encriptar, desencriptar), el servidor te devuelve tu clave y (dentro de un bloque de vídeo) la desencriptas y haces la operación.
De esta forma, el servidor nunca ve la clave desecnriptada, pero gracias a los pares de claves, puedes encriptar mensajes para cualquiera.
Ejemplo del proceso de registro para el Slack e2ee
Implementación
Clic para saltarte la implementación.
Para desarrollar este bot, elegí TypeScript. Básicamente porque estoy acostumbrado a usarlo y me permite iterar rápido.
Mientras hacía la app de Slack en si, perdí un buen rato hasta darme cuenta de que los bloques de vídeo no se pueden meter en mensajes efímeros. Este comportamiento no está documentado en ninguna parte.
En cuanto a la encriptación, primero intenté programar toda la lógica yo mismo usando la API subtle crypto del navegador (que está totalmente disponible en el bloque de vídeo de Slack). Enseguida vi lo difícil que era y la cantidad de técnicas y casos raros de los que me tenía que preocupar.
Por suerte, antes de sufrir de más, encontré openpgpjs. Una librería que mantiene Proton (sí, los del email) y que hace todas las operaciones criptográficas que necesito.
Quería que el servidor guardara la menor cantidad de datos posible, almacenando todo lo posible en los metadatos de Slack. Esto ya lo había hecho antes en honest-impressions (bot de Slack stateless de mensajes anónimos).
Todos los mensajes o views de Slack pueden guardar un campo metadata que nunca se muestra en el cliente. Sin embargo, por culpa de lo largos que son los mensajes encriptados, al final no pude usar esta función.
Para servir los iframes, acabé usando un sistema de slugs. En cada llamada que necesita interactuar con el cliente, se guarda un slug en una base de datos KV con la información necesaria para realizar la acción. Cuando carga el iframe, esta información se incrusta en el código del cliente para que todas las operaciones de criptografía se puedan hacer localmente.
Por ejemplo, el flujo para encriptar un mensaje es bastante sencillo:
- Primero, se ejecuta el comando de Slack
/e2ee send. Se abre un modal pidiendo los destinatarios del mensaje. - Después de enviar ese modal, se genera un slug que contiene: la clave privada del autor y la clave pública de los destinatarios.
- Al hacer clic en el bloque de vídeo dentro de Slack, el iframe se carga con la información anterior.
- El autor desencripta su clave privada usando su contraseña (localmente).
- El autor escribe el mensaje, lo encripta para los destinatarios y lo firma con su clave (localmente).
- El autor envía solo el mensaje encriptado al servidor.
- El servidor envía sobres a cada uno de los destinatarios del mensaje.
¡Eso es todo!
Por cierto, mientras montaba este proyecto, descubrí algunas de las maravillas que ofrece nodejs moderno. ¿Sabías que node tiene nativo para archivos .env? Yo no tenía ni idea.
Resultado
Puedes echarle un vistazo al proyecto ahora mismo, el código fuente está en gh:v1ctorio/e2ee-slack. Y lo puedes selfhostear en tu propio workspace de Slack en unos 5 minutos.
Este proyecto no deja de ser una especie de hack, ya que no cumple del todo con las reglas de diseño que impone Slack. Pero me dejó pensando. Con toda la flexibilidad que nos dan las tecnologías web, ¿No sería interesante que los servicios grandes soportaran aplicaciones completas dentro de su cliente?
A ver, Discord ya hace algo parecido con sus ‘Activities’ o Telegram con sus ‘Mini Apps’. Pero, ¿y si más servicioes empezasen a implementar esta idea?