UNPKG

@nullplatform/np-scope-token-validator

Version:

JWT authentication service for nginx auth_request

196 lines (163 loc) 6.63 kB
#!/usr/bin/env node /** * Test JWK to PEM conversion with the actual key from nullplatform */ const crypto = require('crypto'); // The actual JWK from nullplatform const testJwk = { "kty": "RSA", "n": "h9SfuSSnIxVejXwZ30T-aeP7z-YdKYSa7vI7HxG-gMzcnhaYTGevHJZLnPmV9QhIKUeOrjbQI0XidJpq57ZXLe_DEhkV4sSfziLzZBagMbV2IRMenjlQchFdLWA0QYJoCv7kpEIXvLFZuuS3gu3L9AeV8yu5EIvkaAG7ftjOnZ_xgA6tY4iwesqtHOubZMTGR4axiOdqzponAYEqK_FBXOuHYjUkbivpOgYaRq_YwjCIGSkeHIWgmWltBY5SIIh6b7R45Pti6gyNJVbaBBsV8JsLIcnDcd4NzuOcDQn3Y9FpXvJbhXkGGnMbJvYptFE-AL0KdtqrZIifpSmQEfwT-w", "e": "AQAB", "alg": "RS256", "use": "sig", "kid": "key-1" }; console.log('Testing JWK to PEM conversion...'); console.log('JWK:', JSON.stringify(testJwk, null, 2)); console.log('Node.js version:', process.version); function testCryptoApi() { console.log('\n=== Testing Node.js crypto API ==='); try { const publicKey = crypto.createPublicKey({ key: { kty: testJwk.kty, n: testJwk.n, e: testJwk.e }, format: 'jwk' }); const pem = publicKey.export({ type: 'spki', format: 'pem' }); console.log('✅ Crypto API conversion successful'); console.log('PEM result:'); console.log(pem); return pem; } catch (error) { console.error('❌ Crypto API failed:', error.message); console.error('Error details:', error); return null; } } function testFallbackMethod() { console.log('\n=== Testing fallback ASN.1 method ==='); try { // Decode base64url encoded values function base64urlDecode(str) { // Add padding if needed str += '=='.slice(0, (4 - str.length % 4) % 4); return Buffer.from(str.replace(/-/g, '+').replace(/_/g, '/'), 'base64'); } const n = base64urlDecode(testJwk.n); const e = base64urlDecode(testJwk.e); console.log('Decoded n length:', n.length); console.log('Decoded e length:', e.length); console.log('n (hex preview):', n.toString('hex').substring(0, 32) + '...'); console.log('e (hex):', e.toString('hex')); // Create ASN.1 DER structure for RSA public key function createDerInteger(buffer) { // If the first bit is set, prepend 0x00 to indicate positive number if (buffer[0] & 0x80) { const padded = Buffer.alloc(buffer.length + 1); padded[0] = 0x00; buffer.copy(padded, 1); buffer = padded; } const length = encodeLength(buffer.length); return Buffer.concat([Buffer.from([0x02]), length, buffer]); } function encodeLength(length) { if (length < 0x80) { return Buffer.from([length]); } const bytes = []; let temp = length; while (temp > 0) { bytes.unshift(temp & 0xff); temp >>= 8; } return Buffer.concat([Buffer.from([0x80 | bytes.length]), Buffer.from(bytes)]); } // RSA public key structure: SEQUENCE { n INTEGER, e INTEGER } const nInteger = createDerInteger(n); const eInteger = createDerInteger(e); const rsaPublicKey = Buffer.concat([nInteger, eInteger]); const rsaLength = encodeLength(rsaPublicKey.length); const rsaSequence = Buffer.concat([Buffer.from([0x30]), rsaLength, rsaPublicKey]); // Algorithm identifier for RSA const algorithmId = Buffer.from([ 0x30, 0x0d, // SEQUENCE (13 bytes) 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, // rsaEncryption OID 0x05, 0x00 // NULL ]); // SubjectPublicKeyInfo structure const bitString = Buffer.concat([Buffer.from([0x00]), rsaSequence]); // Prepend unused bits count const bitStringLength = encodeLength(bitString.length); const publicKeyBitString = Buffer.concat([Buffer.from([0x03]), bitStringLength, bitString]); const spkiContent = Buffer.concat([algorithmId, publicKeyBitString]); const spkiLength = encodeLength(spkiContent.length); const spki = Buffer.concat([Buffer.from([0x30]), spkiLength, spkiContent]); // Convert to PEM format const base64Der = spki.toString('base64'); const pem = [ '-----BEGIN PUBLIC KEY-----', ...base64Der.match(/.{1,64}/g), '-----END PUBLIC KEY-----' ].join('\n'); console.log('✅ Fallback conversion successful'); console.log('PEM result:'); console.log(pem); return pem; } catch (error) { console.error('❌ Fallback method failed:', error.message); console.error('Error details:', error); return null; } } function testJwtWithPem(pem) { console.log('\n=== Testing JWT verification with PEM ==='); try { const jwt = require('jsonwebtoken'); // Test token from the user const testToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI3MzIxODk1NDMiLCJpYXQiOjE3NDgwMjI5NDIsImV4cCI6MTc0ODAyNDc0MiwiaXNzIjoiaHR0cHM6Ly9hcGkubnVsbHBsYXRmb3JtLmNvbS9zY29wZSIsImF1ZCI6ImgtMjAtcGF5bWVudHMtYXBpLWRldmVsb3BtZW50LXVydWd1YXkteXhieWEua3dpay1lLW1hcnQtbWFpbi5udWxsYXBwcy5pbyJ9.ceYaSi_HbEc1dNvcgOPP4bGtc3mkUGycrN_CjY9A3Ai-Tf_jSuqyeugAxOrf9F9j85CMlLwbynbaNMcQdM_paN-UALP-zK7CBphG4u8MW1c9Jj8IBTBg-OYCmJGbRxeF42cyocrJm_2xaeE6plR97zQ_qzCycxrSqmfAgs4bftJKNS3jG3un7fCxtKXNg5RAU6updPwMSuSQ-WqYB2_nMjCC6VuVObsGAVeKE99YIGO3eGhDW6zw0gHHt_6uqqYvOFs4Epb2CWKpSsWO45mYHfnssBTdIcSEGspv7s7O4fLUpxAYZHCl4qUpxtSCz423wZlmeysQUZK6onh9PnYilg"; const payload = jwt.verify(testToken, pem, { algorithms: ['RS256'], issuer: 'https://api.nullplatform.com/scope' }); console.log('✅ JWT verification successful'); console.log('Payload:', payload); return true; } catch (error) { console.error('❌ JWT verification failed:', error.message); return false; } } // Run all tests async function runTests() { console.log('Starting JWK to PEM conversion tests...\n'); // Test crypto API const cryptoPem = testCryptoApi(); // Test fallback method const fallbackPem = testFallbackMethod(); // Test JWT verification if we have a PEM const pemToTest = cryptoPem || fallbackPem; if (pemToTest) { testJwtWithPem(pemToTest); } else { console.log('\n❌ No PEM available for JWT testing'); } console.log('\n=== Summary ==='); console.log('Crypto API:', cryptoPem ? '✅ Success' : '❌ Failed'); console.log('Fallback method:', fallbackPem ? '✅ Success' : '❌ Failed'); } if (require.main === module) { runTests() .then(() => { console.log('\nTesting complete'); }) .catch((error) => { console.error('Test error:', error); process.exit(1); }); }