2 puntos por GN⁺ 2023-10-29 | 1 comentarios | Compartir por WhatsApp
  • A partir del objetivo de Seattle Public Library y King County Library System de reducir las tarjetas plásticas, terminé manipulando directamente la estructura de JSON, imágenes y firmas de los pases de Wallet en iPhone
  • Wallet solo admite QR code, PDF417, Aztec Code y Code 128, así que no se podía poner Codabar, usado por las tarjetas de biblioteca, como código de barras nativo; el código de barras real para escanear se resolvió con una imagen
  • El pase debía firmarse con una clave confiable para Apple, pero en lugar de pagar una membresía de Apple Developer de 99 dólares al año, encontré y usé un certificado y una clave en una app de pases para iOS que podía generar pases sin conexión
  • Combinando el layout storeCard, logos para distintas resoluciones, el Codabar strip.png y un campo vacío PKBarcodeFormatCode128, se muestra en pantalla un código de barras como imagen mientras se conserva el comportamiento de iOS de aumentar el brillo de la pantalla
  • El .pkpass final se pudo crear generando el manifest, firmando con openssl smime y empaquetando en ZIP; aunque aún no se probó en una biblioteca real, un escáner aparte sí leyó el código de barras desde la pantalla del teléfono

Objetivo: usar el código de barras de la biblioteca sin tarjeta plástica

  • Seattle Public Library y King County Library System emiten tarjetas plásticas con código de barras impreso vinculadas a las cuentas de préstamo
  • A 2023, la mayoría de las funciones de la biblioteca de Seattle son de autoservicio y normalmente se puede ingresar manualmente el código de barras de la biblioteca
  • Aun así, mostrar el código de barras de inmediato es mucho más cómodo, así que el objetivo era no llevar en la billetera una tarjeta plástica que casi no se usa y ponerla en la app Wallet del iPhone

Los pases de Wallet están compuestos por JSON, imágenes y una firma

  • Hoy la app Wallet maneja tarjetas de pago, identificaciones, llaves y más, pero cuando Passbook se lanzó en 2012 solo gestionaba “pases”
  • Según la documentación de pases de Apple, un pase es un archivo ZIP autocontenido que puede distribuirse por correo o por la web, y que contiene JSON y PNG
  • La composición básica de un archivo .pkpass es simple
    • pass.json: describe el contenido del pase que no son imágenes
    • manifest.json: lista de checksums SHA-1 de los demás archivos
    • signature: firma S/MIME sobre el contenido de manifest.json
    • Archivos de imagen compatibles

La falta de soporte para Codabar fue la primera barrera

  • Los pases de Wallet solo admiten cuatro tipos de códigos de barras
    • QR code
    • PDF417
    • Aztec Code
    • Code 128
  • La tarjeta de biblioteca usaba el formato Codabar, ampliamente utilizado en bibliotecas
  • Es posible que los escáneres de códigos de barras de la biblioteca admitan otros códigos de barras lineales, pero el único formato que podía asumirse que funcionaría con certeza en todas las sucursales era Codabar
  • Al final, en lugar de usar la función nativa de códigos de barras de Wallet, había que crear una imagen Codabar escaneable e incluirla dentro del pase

El problema del certificado para firmar el pase

  • Los pases de Wallet requieren una firma criptográfica y deben estar firmados con una clave confiable para una de las autoridades certificadoras de Apple
  • Para casos como que un proveedor actualice automáticamente un pase por cambios de puerta o de asiento en un boleto de avión, la firma tiene cierto sentido
  • Como desarrollador de Apple, se puede obtener una clave de firma de pases con relativa facilidad, pero no quería pagar 99 dólares al año por esta tarea

Por qué las apps existentes para generar pases no servían

  • Había varias apps de diseño de pases creadas por personas que ya eran desarrolladoras de Apple, pero no ofrecían el nivel de control necesario
  • Control del tamaño del logo

    • Apple permite una altura máxima de 50 device-independent pixels para el logo de la esquina superior izquierda del pase
    • Para el logo cuadrado que va junto al texto a la derecha, una altura de unos 40px era lo más adecuado
    • El desarrollador debe proporcionar logo.png, logo@2x.png y logo@3x.png por separado, pero las apps normalmente solo permiten elegir un único logo y no dan control de escala
  • Comportamiento del brillo de la pantalla

    • Al abrir un pase con un código de barras compatible normal, la pantalla aumenta el brillo para mejorar el contraste ante el escáner
    • Para que iOS aumentara el brillo sin mostrar un código de barras que en realidad no funcionara, había que manipular directamente pass.json
    • Algunas apps descargables gratis limitaban la cantidad de pases que podían guardarse en Wallet y pedían un pago

Encontrar una clave de firma en una app de generación offline

  • Descargué varias apps de generación de pases con inicio gratuito y verifiqué si podían crear pases también sin conexión
  • Al probar con modo avión y Wi-Fi desactivado, al menos una app pudo crear pases offline
  • No revelo el nombre específico de esa app para evitar que se revoque la clave
  • El proceso fue más simple de lo esperado
    • Como las apps de iOS pueden ejecutarse en una Mac con Apple silicon, descargué la app en la Mac
    • Dentro del bundle de la app de iOS envuelta, busqué un archivo .p12 llamativo
    • Como los archivos PKCS#12 requieren una contraseña de importación, ejecuté strings sobre el binario principal y busqué cadenas que parecieran contraseñas
  • Al revisar la información del certificado con openssl pkcs12, el issuer apareció como Apple Worldwide Developer Relations Certification Authority
  • También se necesita la cadena de certificados; el certificado intermedio podría estar dentro del bundle de la app y también puede descargarse desde la URL incluida en los campos de extensión X.509

Configuración del layout del pase

  • Para el estilo del pase, hay que elegir uno entre boarding pass, coupon, event ticket, generic y store card
  • Para incluir una imagen horizontal grande, se necesita un layout que admita imágenes strip
    • Las opciones eran coupon, event ticket y store card
    • Elegí store card porque era el formato más parecido a una tarjeta de biblioteca física
  • pass.json contiene los siguientes valores
    • passTypeIdentifier
    • teamIdentifier
    • formatVersion
    • serialNumber
    • organizationName
    • logoText
    • description
    • storeCard
    • backgroundColor
    • foregroundColor
    • sharingProhibited
  • passTypeIdentifier y teamIdentifier deben coincidir respectivamente con los campos UID y OU del subject del certificado de Apple
  • Cada pase creado con el mismo passTypeIdentifier necesita un serialNumber único
  • Preparé las imágenes según su uso
    • icon.png es obligatorio, pero no se muestra en el pase en sí
    • logo.png es el logo que se muestra en la esquina superior izquierda
    • Generé logo.png de 40×40, logo@2x.png de 80×80 y logo@3x.png de 120×120
    • Copié logo@3x.png como icon.png
    • El código de barras pregenerado se puso en strip.png

Crear la imagen de código de barras Codabar

  • iOS escala y recorta strip.png para ajustarla al recuadro en la pantalla del dispositivo, así que no fue necesario crear tres variantes de strip.png para distintas resoluciones
  • Las dos tarjetas de biblioteca usan A y D como símbolos de inicio y fin
  • Los símbolos de inicio y fin se pueden verificar más fácilmente con un escáner de códigos de barras; si no se tiene uno, se pueden comparar visualmente con la tabla de codificación Codabar de Wikipedia
  • No había muchos generadores Codabar listos para usar en línea, pero el formato en sí era fácil de implementar directamente
  • En el prototipo, generé SVG con la biblioteca Barcoders para Rust, ajusté el SVG y luego lo exporté a PNG
  • El layout final se definió considerando la posibilidad de escaneo y la reducción de escala de iOS
    • La altura del código de barras se fijó en unidades equivalentes al doble del número total de símbolos, incluyendo los de inicio y fin
    • Por ejemplo, un número de código de barras de 13 dígitos tiene 15 símbolos, así que la altura se hizo de 30 unidades
    • Antes del inicio y después del fin se dejó un quiet space de 15 unidades en cada lado
    • Arriba y abajo del código de barras se dejó un padding de 50 unidades en cada lado
    • Cada unidad se amplió a 8 píxeles para que iOS siempre redujera la imagen
    • En el ejemplo de código de barras de 15 símbolos, la imagen final mide 1040 píxeles de alto y el código de barras en sí mide 240 píxeles de alto
  • Escribí un script de shell de 69 líneas que genera un BMP Codabar con ese layout y lo convierte a PNG con sips
  • Guardando la salida del script como strip.png, queda lista la imagen del código de barras para incluirla en el pase

Mostrar el número del código de barras y el atajo para el brillo de pantalla

  • El número del código de barras se muestra debajo del código usando secondaryFields
    • key: number
    • label: CARD NUMBER
    • value: número de tarjeta
  • iOS aumenta el brillo de la pantalla al seleccionar un pase con código de barras, para ayudar al escáner
  • Con solo el Codabar insertado como imagen, iOS no considera que haya un código de barras
  • Para conseguir ese comportamiento, se especificó un campo de código de barras vacío en el nivel superior de pass.json
    • message es una cadena vacía
    • format es PKBarcodeFormatCode128
    • messageEncoding es iso-8859-1
  • Este método hizo que el iPhone aumentara el brillo de la pantalla como si el pase tuviera un código de barras, sin mostrar un código de barras en la parte inferior del pase

Firma y empaquetado

  • Después de preparar todos los archivos, hay que generar manifest.json
  • manifest.json es un objeto que usa nombres de archivo como claves y checksums SHA-1 como valores
  • Combiné sha1sum y jq para generar el manifest de los archivos PNG y pass.json
  • La firma del manifest se realiza con el comando openssl smime
    • Certificado del firmante
    • Private key
    • Certificado intermedio Apple WWDR
    • Entrada manifest.json
    • Salida signature
  • Con la opción -attime de openssl smime, se puede especificar el momento de firma deseado
    • El valor de la opción es un UNIX epoch
    • Aunque el certificado recibido de Apple o encontrado haya caducado, se puede firmar con ese momento
  • Por último, se agrupan los siguientes archivos en un ZIP para crear el .pkpass
    • Imágenes PNG
    • pass.json
    • manifest.json
    • signature

Resultado de las pruebas y puntos pendientes

  • macOS tiene una herramienta de vista previa de pases que permite comprobar si el pase es válido y si, en general, se ve correctamente
  • Si no es válido, se pueden buscar errores en Console.app
  • La herramienta de vista previa no es 100% precisa, pero tiene un botón para enviarlo al iPhone mediante iCloud
  • El pase terminado aún no se probó en una biblioteca real
  • Un escáner de códigos de barras aparte sí pudo leer el código de barras desde la pantalla del teléfono igual que desde la tarjeta plástica real
    • Aunque se leía bien cuando el brillo de la pantalla estaba al máximo
    • El brillo se configuró más alto que el que subía con el atajo del código de barras vacío
  • Es bueno que la especificación de pases casi no haya cambiado en 10 años, pero es una lástima que se necesite una membresía de Apple Developer de 99 dólares al año para firmar un pase inofensivo hecho de PNG y JSON
  • Algunas funciones de los pases sí pueden requerir firma, pero considero que lo hecho en esta tarea no debería necesitarla
  • Si Apple agrega soporte para Codabar en Wallet, se podrían admitir tarjetas de biblioteca digitales sin auditar si todos los escáneres del sistema bibliotecario admiten Code 128

1 comentarios

 
GN⁺ 2023-10-29
Opiniones de Hacker News
  • La próxima estaría bueno que también cubran la tarjeta ORCA. Seattle es uno de los centros tecnológicos de EE. UU., pero no solo el metro/transporte público en sí es bastante malo, sino que además está tecnológicamente atrasado frente al transporte público de casi cualquier ciudad importante que haya usado.
    Cualquier ciudad de Europa, CDMX o Denver están mucho más adelante que Seattle, y personalmente creo que Denver fue la mejor.
    Una de las agencias que operan ORCA incluso había publicado una entrada de blog diciendo que iba a agregar soporte NFC a la app de Android, pero esa publicación ya desapareció y, años después, la función sigue sin existir.

    • Hace unos días publicaron que Google Wallet pronto lo va a soportar: https://blog.google/products/google-pay/commute-around-the-w...
    • Nuestro sistema de transporte local agregó recientemente pago por toque, así que se puede usar cualquier tarjeta de crédito, Apple Pay o Google Pay.
      Mientras uses la misma tarjeta, maneja sin fricciones cosas como transbordos y pases de varios días, y aplica automáticamente la tarifa correcta, por ejemplo dejando de cobrarte si haces más de cierta cantidad de viajes dentro de un período determinado.
    • Me pregunto cuánto cuesta mantener los sistemas de cobro de tarifas, comparado con que la mayoría de los sistemas de transporte público solo perderían una parte de sus ingresos si pasaran a viajes gratuitos.
    • Relacionado con eso, ¿no habrá alguna forma de hacer clonado de tarjetas NFC con un Android rooteado?
      La puerta de mi oficina se abre con una tarjeta NFC y busqué si era posible en iOS, pero según recuerdo Apple controla el hardware NFC con más rigidez que el PassKit común, así que con apps normales parece difícil.
    • https://info.myorca.com/news/can-i-use-my-phone-to-pay-for-a...
      Dijeron que el pago por toque llegaría a ORCA en 2023, así que si va a ser cierto, quedan unos dos meses.
      Como Google publicó que pronto soportará ORCA en Google Wallet, soy optimista.
  • Si eres usuario de Android y no te gusta que Google Wallet no permita agregar cualquier código de barras como Apple Passbook, en F-Droid hay una app excelente llamada Loyalty Card Keychain: https://f-droid.org/en/packages/protect.card_locker/
    Puedes ingresar el número manualmente o escanear un código de barras existente, y guardarlo eligiendo entre varios formatos de código de barras, incluido Codabar. Al tocar un elemento en la pantalla principal de la app, muestra el código de barras generado y también sube el brillo de la pantalla.
    Quizá porque no hace mucho más que mostrar códigos de barras, la app también abre muy rápido. Solo tiene una función, pero tal vez sea una de mis apps favoritas.
    Parece que hoy en día recomiendan usar Catima; la probé un rato y se ve igual de simple y basada en el mismo código.

    • En Apple uso Pass4Wallet para generar una “tarjeta” y luego importarla a Apple Wallet.
      Es incómodo, pero cumple el objetivo, y su política de privacidad es buena.
    • También hay una app que permite poner el código de barras que quieras en Google Wallet: https://play.google.com/store/apps/details?id=color.dev.com....
  • No entiendo por qué tiene sentido que los pases deban estar firmados criptográficamente y con una clave conocida por la autoridad certificadora de Apple. Para este tipo de actualizaciones ya existe HTTPS, que está bien soportado.
    Puede que Apple quiera que los pases se puedan verificar no solo al momento de actualizarse, sino también offline en el teléfono, pero aun así es raro. Un actor malicioso podría directamente reemplazar el pase en vez de actualizarlo.

    • Bastaría con que el pase inicial especificara la clave pública que permite actualizaciones.
      No veo en absoluto cómo vincularlo a una cuenta de desarrollador de Apple ayuda con el problema de las actualizaciones.
      La única razón que se me ocurre para exigir que sea una entidad aprobada por Apple es la venta de entradas falsas. No hay forma de saber si un archivo pkpass llamado “entrada para concierto X” es real o no, exista o no ese requisito.
      Tal vez intenten responder cancelando esa cuenta de desarrollador si reciben denuncias de fraude, pero eso tampoco parece una solución. El costo de una cuenta de desarrollador probablemente sea mucho menor que el dinero que se puede ganar con una estafa antes de ser detectado.
  • Yo simplemente me envío por correo un PNG del código de barras con el número de mi tarjeta de biblioteca y lo abro en Photos o en la app de Gmail frente al kiosco.

    • Los pases de Wallet pueden definir la ubicación y el rango horario en los que ese pase aparece como “relevante”.
      Con la app MakePass hice un pase con mi código de membresía de StarBucks, y cuando me acerco a una tienda a la que voy seguido, el teléfono me sugiere automáticamente el pase de StarBucks en la pantalla bloqueada.
      Con las entradas a eventos pasa lo mismo: si defines una ubicación y un rango horario, al llegar al lugar se sugieren automáticamente sin tener que buscar el pase. Cuando termina el evento, deja de sugerirlo y lo mueve a la sección “Expired Passes”, para no desordenar la pantalla principal.
      MakePass: https://pvieito.com
    • Mientras hojeaba el artículo, estaba buscando por qué no simplemente le sacó una foto.
  • Fue una buena entrada de blog, pero termina con “todavía no probé este pase en una biblioteca real”.
    Entiendo que es un proyecto hecho por diversión, pero no sé por qué no dedicó los 10 minutos necesarios a probar la solución final antes de compartir el resultado.

    • Esa parte le quitó fuerza a todo el artículo. Es como decir: “Tengo una excelente teoría científica sobre la pérdida de oxígeno al correr. Hay que admitir que todavía no empecé a trotar, pero…”.
    • Al final no es más que un conjunto de píxeles que representa un código de barras que será escaneado. Si se muestra correctamente, ¿qué podría salir mal?
    • Puede que la biblioteca no abra los sábados.
  • Al generar códigos de barras, personalmente prefiero un generador de códigos de barras en PostScript
    https://bwipp.terryburton.co.uk/

  • Resolví este problema tomando una foto de la tarjeta de la biblioteca. Al sacar libros prestados, basta con abrir la foto y mostrarla al escáner

    • Puse la imagen del código de barras en un recordatorio basado en ubicación, así que cuando voy a la biblioteca del barrio me aparece una notificación
    • Si es un código de barras estático, con una foto es suficiente. No hay gran ventaja en meterlo en Wallet
      Guardo todas mis identificaciones en el álbum de fotos del teléfono
  • Si quieres hacer algo parecido en Android, está Catima, disponible en Google Play y F-Droid. Soporta varios tipos de códigos de barras
    https://catima.app/

    • También puedes usar Google Pay o Samsung Pay
  • Los escáneres láser baratos de códigos de barras 1D no pueden leer códigos de barras desde una pantalla. Quizá funcionen con eInk
    La tarjeta de membresía del supermercado al que voy seguido está basada en código de barras y es incómodo, pero por suerte el lector de códigos de barras emula un teclado, así que simplemente se puede ingresar el código con el teclado

    • Según entiendo, es porque los sistemas antiguos no escanean con un sensor de imagen digital, sino que leen los cambios de brillo reflejados por las zonas blancas/negras del código de barras usando un rayo láser giratorio y un fotodiodo simple
      A los escáneres basados en cámara no les importa si la iluminación del código de barras viene de la luz ambiental, de sus propios LED o de una pantalla retroiluminada, pero los sistemas basados en láser dependen del reflejo de su propia luz, así que no funcionan en absoluto con pantallas con retroiluminación activa
      Me da curiosidad si funcionarían con e-ink o con pantallas LCD pasivas
    • Hice un indicador barato de códigos de barras con un MCU Bluetooth y una pantalla E-ink, y funciona bien con todos los escáneres de códigos de barras solo 1D
    • Cada vez que veo lo rápido y fácil que los escáneres portátiles de Costco leen el código de barras de membresía impreso de forma muy estrecha y pequeña en la tarjeta de socio, me sorprendo
      Por fuera parece un simple tipo 2D de línea roja, pero parece que el hardware sí importa
  • Simplemente memoricé el número de cuenta de KCLS. Debería bastar con unos 30 segundos para memorizarlo; dependerá de cada persona, pero creo que este método sería más rápido
    Después, en vez de escanear el código de barras, puedes ingresar el número de cuenta, y probablemente tome menos que sacar el teléfono y prepararlo
    No sé si SPL funciona de la misma manera. No vivo en Seattle, así que no pude comprobarlo

    • Si no quieres memorizarlo, puedes guardarlo en el administrador de contraseñas que prefieras