Kong / kong

🦍 The Cloud-Native API Gateway and AI Gateway.
https://konghq.com/install/#kong-community
Apache License 2.0
39k stars 4.78k forks source link

Edge case issues in HS256 JWT client auth invalid signature being reported #13356

Closed jeremyjpj0916 closed 2 months ago

jeremyjpj0916 commented 2 months ago

Is there an existing issue for this?

Kong version ($ kong version)

3.7.1

Current Behavior

Going from 2.8.x to 3.7.1 I have had now 2 customers out of many(Vast majority reporting no issues) that are reporting signature failure for hs256 jwt token generation clients calling a jwt auth protected kong proxy.

The proxy will have exp set to 3600 and be using hs256 algo for the kong consumer and only cares about iss/exp claims.

Two cases: one client was using mulesoft code and they managed to use a different underlying jwt library vs another and it fixed the issue for them. Still super strange a library that used to work w older kong versions stopped working w the newer one and a diff library fixed their invalid signature problem.

Another client is using some kinda salesforce apex tech and use some code they added like this to make a jwt token:

private static string generateJWTToken(string clientSecret, string clientKey) {

        string timestamp = string.valueOf((DateTime.Now().getTime() / 1000) + 3600);
        string payload= '{"exp": ' + timestamp + ', "iss": "' + clientKey + '"}';
        string signature = EncodingUtil.base64Encode(blob.valueOf(HEAD)) + '.' + EncodingUtil.base64Encode(blob.valueOf(payload));
        Blob HmacSHA256 = Crypto.generateMac('HmacSHA256', Blob.valueOf(signature), blob.valueOf(clientSecret));

        return EncodingUtil.base64Encode(Blob.valueOf(HEAD)) + '.' + EncodingUtil.base64Encode(Blob.valueOf(payload)) + '.' + EncodingUtil.base64Encode(HmacSHA256);
    }

Which to my untrained eyes seems like something that would work and build a valid jwt ^ , and it has for them in prod on 2.8.x over a year +.

And now seeing in our upgraded 3.7.1 non-prod a response like:

image

Which is where the secret used to sign jwt signature reports an invalid signature now from Kong when they call the proxy.

Worth noting I have tons of client libraries and clients that post upgrade the jwt token generation and calls are working fine still too. So really at a loss at what changed under the hood for the Kong's way of processing these, but something obscure must have changed. Either from some kinda lax validation aspect or different ways of doing things with respect to verifying the hs256 signatures of these tokens.

I can use the above client that has the issue with their proxy and build their jwt using http://jwtbuilder.jamiekurtz.com/ for instance and it validates just fine against said proxy.

Just calling this out early and may try to study and point out differences in 2.8.x and 3.7.x for client jwt auth that may pop up for your other OSS or Enterprise customers as they make the jump, still seems like only small % of customers using non-standard languages and products are impacted by it.

Expected Behavior

  1. No signature invalid errors like 2.8.x gave them.

Steps To Reproduce

  1. Am clueless as to why this happens but seems to have happened between Kong versions for edge case users. The only thing that changed is the version of the kong runtime. Creds and endpoints all stayed the same.

Anything else?

No response

jeremyjpj0916 commented 2 months ago

Also tried to get both cases of clients to reduce the exp to like 59 mins incase was some kinda server clock skew issue in the look ahead of the ttl but was not that. plus if it was a ttl issue Kong would not be reporting invalid signature error so shoulda known it wasn't that.

jeremyjpj0916 commented 2 months ago

JWT plugin on this proxy configs just for ref:

{
  "created_at": 1713418384,
  "updated_at": 1718949841,
  "consumer": null,
  "enabled": true,
  "name": "jwt",
  "tags": null,
  "service": null,
  "instance_name": null,
  "protocols": [
    "grpc",
    "grpcs",
    "http",
    "https"
  ],
  "id": "67a7e617-be3b-45fd-85df-593906c84271",
  "route": {
    "id": "835ff072-ffa2-4bff-84f7-fea46ece8d38"
  },
  "config": {
    "header_names": [
      "authorization"
    ],
    "secret_is_base64": false,
    "claims_to_verify": [
      "exp"
    ],
    "run_on_preflight": true,
    "key_claim_name": "iss",
    "cookie_names": [],
    "maximum_expiration": 3600,
    "anonymous": null,
    "uri_param_names": [
      "jwt"
    ]
  }
}
jeremyjpj0916 commented 2 months ago

Oddly this fixes it for client in question, they found a work around fix that makes their client jwt token gen work with the newer version of Kong too:

private static string generateJWTToken(string clientSecret, string clientKey) {

        Map<String, Object> claims = new Map<String, Object>{
            'iss' => clientKey, // Replace with your issuer
            'exp' => Datetime.now().addMinutes(1).getTime()/1000};
            String payload = JSON.serialize(claims);
            Map<String, Object> header = new Map<String, Object>{
                'alg' => 'HS256', // Algorithm used for signing
                'typ' => 'JWT' // Type of token
            };
            String encodedHeader = EncodingUtil.base64Encode(Blob.valueOf(JSON.serialize(header))).replace('+', '-').replace('/', '_').substringBefore('=');
            String secretKey = clientSecret;
            String unsignedToken = encodedHeader + '.' + EncodingUtil.base64Encode(Blob.valueOf(payload)).tostring().replace('+', '-').replace('/', '_').substringBefore('=');
            Blob signature = Crypto.generateMac('hmacSHA256',Blob.valueOf(unsignedToken),Blob.valueOf(secretKey));
            String encodedSignature = EncodingUtil.base64Encode(signature).replace('+', '-').replace('/', '_').substringBefore('=');
            String jwtToken = unsignedToken + '.' + encodedSignature;
            return jwtToken;

    }

So replacing the chars + with - , and / with _ fixes their issue post b64 encoding. But Kong 2.8.x somehow it worked.

jeremyjpj0916 commented 2 months ago

Seems like the + and / do play a role: https://www.edx.org/learn/base64 . And somehow older kong allowed more flexibility there than new Kong :/ .. still gotta wrap my brain around whats happening.

chronolaw commented 2 months ago

@jschmid1 , could you take a look? thanks.

chronolaw commented 2 months ago

Perhaps it is related to this PR (https://github.com/Kong/kong/pull/11569), I think that + and / are invalid base64url code.

chronolaw commented 2 months ago

See https://jwt.io/introduction, it must be Base64Url encoded.

jeremyjpj0916 commented 2 months ago

Looks like it to me, I see similar characters being removed etc. Guess next question was that PR the right move. base64 encoding a url vs just base64 encoding a string has different stipulations right? URLs have a set of requirements separate from just regular base64 encoding. I suppose where it matters is if a client passes a jwt in URL?

But this was breaking change I think for some clients then.

jeremyjpj0916 commented 2 months ago

Ahh yep RFC is supposed to respect only base64 url encoded values:

" A JWT is represented as a sequence of URL-safe parts separated by period ('.') characters. Each part contains a base64url-encoded value. The number of parts in the JWT is dependent upon the representation of the resulting JWS using the JWS Compact Serialization or JWE using the JWE Compact Serialization. "

Earlier Kong could respect just pure b64, new kong needs base64 url encoding specifically. Probs should note this as breaking changes across major versions will save some kong users head scratchings. Good find @chronolaw . Will inform our users broadly too.