Estoy tratando de usar trabajadores de Cloudflare para realizar acciones autenticadas.
Estoy usando firebase para la autenticación y tengo acceso a los tokens de acceso, pero dado que firebase-admin usa módulos nodejs, no puede funcionar en la plataforma, así que me quedo validando manualmente el token.
He estado intentando autenticarme con Crypto API y finalmente logré importar la clave pública, firmar el token para verificar si es válido, pero sigo obteniendo FALSO. Estoy luchando por descubrir por qué siempre devuelve falso para la validez.
La clave criptográfica que importé viene como tipo "secreto" donde esperaría que fuera "público".
Cualquier pensamiento o ayuda sería enorme. He estado golpeando mi cabeza contra una mesa durante los últimos días tratando de resolver esto.
Esto es lo que tengo hasta ahora:
function _utf8ToUint8Array(str) { return Base64URL.parse(btoa(unescape(encodeURIComponent(str)))) } class Base64URL { static parse(s) { return new Uint8Array(Array.prototype.map.call(atob(s.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, '')), c => c.charCodeAt(0))) } static stringify(a) { return btoa(String.fromCharCode.apply(0, a)).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_') } } export async function verify(userToken: string) { let jwt = decodeJWT(userToken) var jwKey = await fetchPublicKey(jwt.header.kid); let publicKey = await importPublicKey(jwKey); var isValid = await verifyPublicKey(publicKey, userToken); console.log('isValid', isValid) // RETURNS FALSE return isValid; } function decodeJWT(jwtString: string): IJWT { // @ts-ignore const jwt: IJWT = jwtString.match( /(?<header>[^.]+)\.(?<payload>[^.]+)\.(?<signature>[^.]+)/ ).groups; // @ts-ignore jwt.header = JSON.parse(atob(jwt.header)); // @ts-ignore jwt.payload = JSON.parse(atob(jwt.payload)); return jwt; } async function fetchPublicKey(kid: string) { var key: any = await (await fetch('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com')).json(); key = key[kid]; key = _utf8ToUint8Array(key) return key; } function importPublicKey(jwKey) { return crypto.subtle.importKey('raw', jwKey, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']); } async function verifyPublicKey(publicKey: CryptoKey, token: string) { const tokenParts = token.split('.') let res = await crypto.subtle.sign({ name: 'HMAC', hash: { name: 'SHA-256' } }, publicKey, _utf8ToUint8Array(tokenParts.slice(0, 2).join('.'))) return Base64URL.stringify(new Uint8Array(res)) === tokenParts[2]; }Tenga en cuenta que puede obtener los jwks desde el extremo no documentado https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com .
async function fetchPublicKey(kid) { const result = await ( await fetch( "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com" ) ).json(); return result.keys.find((key) => key.kid === kid); }Usando la clave (llamada jwk a continuación), puede verificar la firma:
const encoder = new TextEncoder(); const data = encoder.encode([token.raw.header, token.raw.payload].join(".")); const signature = new Uint8Array( Array.from(token.signature).map((c) => c.charCodeAt(0)) ); const key = await crypto.subtle.importKey( "jwk", jwk, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["verify"] ); return crypto.subtle.verify("RSASSA-PKCS1-v1_5", key, signature, data);Hay algunos problemas con su código:
La URL a la que llama para obtener claves públicas devuelve una lista de certificados x509. Estas no son claves públicas utilizadas para verificar firmas. ¿Estás seguro de que no tienes acceso directo a las claves públicas? Parece que es posible obtener la información de la clave pública de un certificado x509 (como se describe aquí: Extraer la clave pública PEM del certificado X.509 ), aunque no estoy seguro de si eso es posible de un trabajador de Cloudflare.
En importPublicKey , le está diciendo al método de import que la clave está en formato sin formato y que es una clave HMAC . Esto significa que crypto trata su clave como una clave HMAC simétrica, no como una clave pública. De acuerdo con los documentos: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#subjectpublickeyinfo , debe usar el formato spki , ya que este es el que importa una clave pública. Debería saber por adelantado si el token de acceso JWT está firmado con el algoritmo RSA o Elliptic Curve. (por ejemplo, verifique el reclamo del encabezado alg )
Está utilizando el método de sign para verificar la firma. Así no es cómo funciona. Debe usar el método de verify de crypto.subtle y este método verificará la firma por usted.
Creo que no debería intentar verificar los JWT manualmente, ya que lo más probable es que lo haga mal (y cree problemas de seguridad para su aplicación). Debería usar bibliotecas que se ocupen de la verificación de firmas JWT. Será mucho más fácil para ti y más seguro para tu aplicación. Una cosa que debe averiguar es de dónde debe tomar la clave pública.