Skip to content

Commit

Permalink
Merging to release-5.3: [TT-12495] Add support for RSASSA-PSS signed …
Browse files Browse the repository at this point in the history
…JWTs (#6368)

[TT-12495] Add support for RSASSA-PSS signed JWTs (#6368)

<details open>
<summary><a href="https://tyktech.atlassian.net/browse/TT-12495"
title="TT-12495" target="_blank">TT-12495</a></summary>
  <br />
  <table>
    <tr>
      <th>Summary</th>
      <td>JWT RSA PUB Improvement - Support RSAPSS</td>
    </tr>
    <tr>
      <th>Type</th>
      <td>
<img alt="Story"
src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10315?size=medium"
/>
        Story
      </td>
    </tr>
    <tr>
      <th>Status</th>
      <td>In Code Review</td>
    </tr>
    <tr>
      <th>Points</th>
      <td>N/A</td>
    </tr>
    <tr>
      <th>Labels</th>
<td><a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20innersource%20ORDER%20BY%20created%20DESC"
title="innersource">innersource</a></td>
    </tr>
  </table>
</details>
<!--
  do not remove this marker as it will break jira-lint's functionality.
  added_by_jira_lint
-->

---

Adding support for the more secure RSA-PSS signed JWTS.

## Description

allows for the use of the RSA-PSS signature algorithm commonly referred
to as PS256, PS384, PS512.
The change is invisible to existing RSA Public Keyuse cases. Simply - by
using "RSA Public Key" signing algorithm, Tyk will now validate JWTs
signed by both RS & PS Class algorithms using Public Keys.

## Motivation and Context

RSA-PSS is considered more secure than PKCS1 v1.5 due to its
probabilistic nature, which helps mitigate certain attacks (e.g.,
padding oracle attacks).

RS256: Commonly used in legacy systems, JWT (JSON Web Tokens), and many
existing protocols where backward compatibility is important.
PS256: Recommended for new applications where higher security is
desired. It is becoming more widely adopted in modern security
protocols.

## How This Has Been Tested

Unit tests have been added. Both positive + negative tests that test
both RS class JWTs and PS class JWTs.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Refactoring or add test (improvements in base code or adds test
coverage to functionality)

## Checklist

- [ ] I ensured that the documentation is up to date
- [ ] I explained why this PR updates go.mod in detail with reasoning
why it's required
- [ ] I would like a code coverage CI quality gate exception and have
explained why
  • Loading branch information
buger authored Dec 17, 2024
1 parent c9dac0a commit 7ec7522
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 4 deletions.
5 changes: 4 additions & 1 deletion gateway/mw_jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -838,9 +838,12 @@ func assertSigningMethod(signingMethod string, token *jwt.Token) error {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return fmt.Errorf("%v: %v and not HMAC signature", UnexpectedSigningMethod, token.Header["alg"])
}
// Supports both RSA + RSAPSS Signing.
case RSASign:
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return fmt.Errorf("%v: %v and not RSA signature", UnexpectedSigningMethod, token.Header["alg"])
if _, ok := token.Method.(*jwt.SigningMethodRSAPSS); !ok {
return fmt.Errorf("%v: %v and not RSA or RSAPSS signature", UnexpectedSigningMethod, token.Header["alg"])
}
}
case ECDSASign:
if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok {
Expand Down
111 changes: 108 additions & 3 deletions gateway/mw_jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1650,9 +1650,114 @@ func TestParseRSAKeyFromJWK(t *testing.T) {
t.Error("expected an error")
}
_, err = ParseRSAPublicKey(b)
if err != nil {
t.Error("decoding as default ", err)
}
assert.NoError(t, err)
}

func TestParseRSAPubKeyFromJWK(t *testing.T) {
sample := `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
mwIDAQAB
-----END PUBLIC KEY-----`

_, err := ParseRSAPublicKey([]byte(sample))
assert.NoError(t, err)
}

func TestAssertPS512JWT(t *testing.T) {
signingMethod := "rsa"
rawJWT := "eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODgiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZSwiaWF0IjoxNTE2MjM5MDIyfQ.Xm1AAFaIP12krQ57NF0FvFulIBvYPh_rtK2FgeUBN2TVbIBPBSgZ0EfdsPcGqKM1i-PeJM6PjcX_cRpdyJvMMq4xFkoEZTj6ONw4wg3kcIHBxKu8hg2qW-7voE6GGyldtQG5XmdzaayEdtuG-9mo_8BLADqbCR_-R8T3B7X1ko1TyDz0ZzMpT-46xsYPCFOMV0-u2xvqBBNfgMeXCOUzyxrl_sxw9yMgtu38qVCCRAK3lojxUjCsXMqL-wjpact0LBydX-880CU7QNAab4qdi6xA1GZhj-osJ267cHQO9Zc7G-stRMzw2zOKk3JfFQJes-t7TiMCpFdehUFNqGlgCw"
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
pubKeyPem := `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
mwIDAQAB
-----END PUBLIC KEY-----`

// Convert the PEM string to a byte slice
pubKey := []byte(pubKeyPem)

// Verify the token
_, err := parser.Parse(rawJWT, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if err := assertSigningMethod(signingMethod, token); err != nil {
return nil, err
}

return parseJWTKey(signingMethod, pubKey)
})

// Should be able to validate RS256
assert.NoError(t, err)
}

func TestAssertNegativePS512JWT(t *testing.T) {
signingMethod := "rsa"
rawJWT := "eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODgiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZSwiaWF0IjoxNTE2MjM5MDIyfQ.I4IxcLO5sEMPXP_gX2UyoGN0lg2DWcRTm9w2ceSxqixE67qFODWUDNxI1TdbN4oCl9ZC_Jy8G4nJhNCu9dVptkMxnawnbIUwCsILd0SLfcAi-hFcG9K0nSzagm--6CtWlve1UbuQFW9X5fTQUESIblXbMFj6L95j4exVv1l7ch-N1Jl68fGLwoXJTQSg"
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
pubKeyPem := `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
mwIDAQAB
-----END PUBLIC KEY-----`

// Convert the PEM string to a byte slice
pubKey := []byte(pubKeyPem)

// Verify the token
_, err := parser.Parse(rawJWT, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if err := assertSigningMethod(signingMethod, token); err != nil {
return nil, err
}

return parseJWTKey(signingMethod, pubKey)
})

assert.Error(t, err)
}

func TestAssertRS256JWT(t *testing.T) {
signingMethod := "rsa"
rawJWT := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODgiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZSwiaWF0IjoxNTE2MjM5MDIyfQ.m2mydd79-40muPDwYbue7idTj-cKfW0jPYxcZH8-eqBc6WFJVCL--pr8IHqP-YdN7bNgfwq6iLh0kvOZ9l4Uu6xBaTdCpaXvJDfKqIqLzhltS4EfDNRkHRLDwLBvfsYt-9ijfNYvPOtTXfcIBXPby8fo529q7WYLFYR9tHAQYCLC_lS_2NieTQjAk5xAWIQ5LNItSM9iXmxhhqK47ZdzzVJnhtQ7onVY4LNgxxKqPPUQxQrq34cOBXozfA65bG7PLzvT7ais-E2_4AOXxDzspxYrDYwQFV2kjRijFcMcPc5pCWXWY9leUD1VklaSae6FuC9qJ2BATTsK8f92LSV4HA"
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
pubKeyPem := `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo
4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u
+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh
kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ
0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg
cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc
mwIDAQAB
-----END PUBLIC KEY-----`

// Convert the PEM string to a byte slice
pubKey := []byte(pubKeyPem)

// Verify the token
_, err := parser.Parse(rawJWT, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if err := assertSigningMethod(signingMethod, token); err != nil {
return nil, err
}

return parseJWTKey(signingMethod, pubKey)
})

// Should be able to validate the RS256 key.
assert.NoError(t, err)
}

func BenchmarkJWTSessionRSAWithEncodedJWK(b *testing.B) {
Expand Down

0 comments on commit 7ec7522

Please sign in to comment.