JWT invalid signature using RS256 algorithm

426 views Asked by At

I was facing problem while generating OAuth2.0 access token using JWT token validation for Azure AD. I am sure that I added the correct certificate to my Azure application. But no matter what, I am facing the same issue. To get to the root of the problem, I wrote a simple program locally to make sure that jwt sign verification works for my certificate and to my surprise, it is not.

Here is the code snippet that I have written:

import crypto from 'crypto';
import fs from 'node:fs';
import jwt from "jsonwebtoken";

function verifySign() {

    let certificatePaths = {
      "validPublicCert" : '/Users/<my-path>/certs/certificate.pem',
      "validPrivateKey" : '/Users/<my-path>/certs/privateKey.pem',
      "failingPublicCert" : '/Users/<my-path>/certs/failing-public.pem',
      "failingPrivateKey" : '/Users/<my-path>/certs/failing-prv-key.key'
    };


    // read and create public certificate
    let publicCertificateBuffer = Buffer.from(fs.readFileSync(certificatePaths.failingPublicCert, 'utf8'));
    let x509publicCert = new crypto.X509Certificate(publicCertificateBuffer);

    // base64 encode sha1 hash of certificate
    let sha = crypto.createHash("SHA1");
    sha.update(x509publicCert.raw);
    let encodedCertificate = sha.digest('base64url');
    console.log("Encoded base64 public certificate is: " + encodedCertificate);

    // prepare jwt headers
    let jwtHeaders = {
        'x5t': encodedCertificate,
        'alg' : 'RS256',
        'typ' : 'JWT'
    };

    // prepare jwt payload
    const now = Math.floor(Date.now() / 1000);
    const expiresIn = now + 3 * 60; 
    let jwtPayload = {
        "iss": "d2057bd6-1560-4b16-9988-790e1f698d9b",
        "aud": "https://login.microsoftonline.com/<my-azure-ad-tenant-domain>/oauth2/v2.0/token",
        "sub": "d2057bd6-1560-4b16-9988-790e1f698d9b",
        "x5t": encodedCertificate,
        "typ": "JWT",
        "alg": "RS256",
        "exp" : expiresIn,
        "iat" : now,
        "nbf": now
      }

      // read encrypted private key and fetch/ build private key from it
      let encryptedPrivateKeyBuffer = fs.readFileSync(certificatePaths.failingPrivateKey, 'utf8');
      let encryptedKeyPassPhrase = '<my pass phrase>';
      let encryptedKey = crypto.createPrivateKey({
        key: encryptedPrivateKeyBuffer, 
        passphrase: encryptedKeyPassPhrase
      });
      let decryptedKey = encryptedKey.export({
        format: 'pem',
        type: 'pkcs1'
    });


    let assertion = jwt.sign(jwtPayload, decryptedKey, {
      algorithm: "RS256",
      header: jwtHeaders
  });

  console.log("JWT assertion created after signing: " + assertion);

  try {
    jwt.verify(assertion, publicCertificateBuffer);
  }catch(error: any) {
    console.log(error);
  }
  
}

verifySign();

Note: For valid certificates, the verification is successful locally and I am able to generate the access token from Azure AD. For failing certificates, both the scenarios are failing.

Important: When I tried using Java, (bouncycastle and jose4j) both the certificate work fine. Is there anything that I am missing while implementing using typescript?

Valid public key

-----BEGIN CERTIFICATE-----
MIIEBzCCAu+gAwIBAgIUeg1UADx1kqu4xEPKNUjOpkAXihMwDQYJKoZIhvcNAQEL
BQAwgZIxCzAJBgNVBAYTAklOMQswCQYDVQQIDAJNSDENMAsGA1UEBwwEUHVuZTEf
MB0GA1UECgwWU2FpbHBvaW50IFRlY25ob2xvZ2llczETMBEGA1UECwwKSW5kaWEg
RW5nZzEQMA4GA1UEAwwHbkRMb2NhbDEfMB0GCSqGSIb3DQEJARYQbmRAc2FpbHBv
aW50LmNvbTAeFw0yMzA5MjkxNzUyMDBaFw0yNDA5MjgxNzUyMDBaMIGSMQswCQYD
VQQGEwJJTjELMAkGA1UECAwCTUgxDTALBgNVBAcMBFB1bmUxHzAdBgNVBAoMFlNh
aWxwb2ludCBUZWNuaG9sb2dpZXMxEzARBgNVBAsMCkluZGlhIEVuZ2cxEDAOBgNV
BAMMB25ETG9jYWwxHzAdBgkqhkiG9w0BCQEWEG5kQHNhaWxwb2ludC5jb20wggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyYSil8dG2wB6zADSBiDfXjQGV
wBODyTb11qkIfI2/nwykkkpqIshnyEOLnaQz2Jrj3h6wzyFslyh+KXu8mxhKanER
+2BVfIMc0NQe5h4qgZoVC9nN2fAS/Pp5ya3lqAF8Bdz90KwR5RYKrNEAVDlno2kt
BxBP+/R08+6nhdZWiWj1dnjXQ+fbv468UaMb3I/oMaflj4LC+WBSI9N401v+SrHE
l1JUi9v4k0nME7NhbdDkFFJubsTdjXKNzCiWsc7BmXDi0HVLeFFPrsXvR2n6DzXu
a5Pmco3rZHqFm+vW+eYGt1yPzFxT/p5bnzBczHiPQiY4ZlwabFdIOxlvarE1AgMB
AAGjUzBRMB0GA1UdDgQWBBRohHr1iFHXGnYFd6vl7Zkh2+5JSjAfBgNVHSMEGDAW
gBRohHr1iFHXGnYFd6vl7Zkh2+5JSjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4IBAQCei8uY2ipOk+3cZUCt1/WAbD0qqhMDv5XVHJmHu6zkj3z5K6Mg
o2YVM66IgtpcRzcEwcdkyLsQZg/T/3nVwo6okk/obYvbbByCLjq4yFNlHPdgZ6O6
ZDye89lBPhcWpaJio/hwwWmpFSUPUHuOeJrejSTU2MiiqlE6voRHGZ556WTsIHCQ
LqVUXf+Oy3cQ/3u96QCNgX2rFTpSdQi346p6OZ3z/j45OP8xTXa4BU4WQLiMunBm
/fWfvPax/C2ZkQh9vMvcbXgNQ5Y9y9lh5hnIhPhivXE/Rbunv2Sin7MQ+WG00Mhe
lf78iga1F+zYZ0cFAigoa0mSTvXVJNSOC8hq
-----END CERTIFICATE-----

Valid private key:

-----BEGIN ENCRYPTED PRIVATE KEY-----
MII
<Remaining - Key>............
EUF7uoapGpNxXMFcqU18OQ==
-----END ENCRYPTED PRIVATE KEY-----

Failing public key:

-----BEGIN CERTIFICATE-----
MIID8TCCAtmgAwIBAgIUY5l2EIzB1FnOeugBSWOf0t5kw2UwDQYJKoZIhvcNAQEL
BQAwgYcxCzAJBgNVBAYTAklOMQswCQYDVQQIDAJNSDENMAsGA1UEBwwEUHVuZTES
MBAGA1UECgwJU2FpbHBvaW50MQ0wCwYDVQQLDARFbmdnMQ4wDAYDVQQDDAVQb29q
YTEpMCcGCSqGSIb3DQEJARYacG9vamEuYmhhdHQxQHNhaWxwb2ludC5jb20wHhcN
MjEwMzEwMTA0NzM1WhcNMzEwMzA4MTA0NzM1WjCBhzELMAkGA1UEBhMCSU4xCzAJ
BgNVBAgMAk1IMQ0wCwYDVQQHDARQdW5lMRIwEAYDVQQKDAlTYWlscG9pbnQxDTAL
BgNVBAsMBEVuZ2cxDjAMBgNVBAMMBVBvb2phMSkwJwYJKoZIhvcNAQkBFhpwb29q
YS5iaGF0dDFAc2FpbHBvaW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAKNl8qxSUa9MvMx/a/KzXF8KyC16YCjbkTGSoGadik7t4ZSX3ZSjTysu
BHCWTwVll9OcQPiPmMv2sEWHWTzcg01CHeMUTgom4fotW1St67pgFWw7ZIo+6iVZ
leOJiM65Igm9Ge/f6zqEliFbMw76Gc5NFWsUxe+OmDQ41UGn8lwC1jT83mNwXj3F
yzy/ZnjHI+BPKmml0rskGkTyItMuKHkaf3iXVtvbWWCGNg3IXQgMLDTU7nzByHrw
gCI7hWnOnxExb87TzfTokkcqq5EGUHo74ycVv9Idv4PEz6GPoEceNb9XUR6pfe7C
AJDkdLo3Zv8helgPSLVdr3CHORgAOtsCAwEAAaNTMFEwHQYDVR0OBBYEFMj5V7ww
N3QMacvKfN8VPUO8guzCMB8GA1UdIwQYMBaAFMj5V7wwN3QMacvKfN8VPUO8guzC
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKDC9FAdmCwXzsrk
aDqXs6sprT9znFyxjv+tLfkU7d/OEKy3a21yzx3Hwt5NFYuBnjBZAwyqQBUuCCJU
lKc3+G7/+yUHKZ5hMvbc/cOWEuZrVBRUSiPh3IZCY9X969VdsM9ChrjCLnbapxdN
Ihhrx3b/WwBpX5r9cyUfOVyGaLIyRcXjtI6h5UqmjtQn4MZnKVBN99fxjLeoPT0i
XegN5oele2WuNpzG8i86K2YB6b+RtXpTrhOvGKcOv5S+3XeAQVfJ5hkOhJv4O12g
ZFAzlwM7lKmL0rQtZHx7cbTFLDfd0r6WsxRibiT1PTnRhY7qA9i2Oxdg+BJ6bFuY
OYDZ+zo=
-----END CERTIFICATE-----

Failing Private Key

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,86FAD4895E136D16

3sv
<Remaining Key>
FjBi0TncmddGjUI58I2TXxJZ/vOxQvbl1nQ+djFttLteIKN7R5YeiaUvNmYMXxB4
-----END RSA PRIVATE KEY-----

As we can see that there is difference in the format of the keys. Since I am no expert in crypto or jwt as well, I am not able to figure out what the problem is

Thanks in advance!

0

There are 0 answers