Closed artkoenig closed 1 month ago
Thanks for the report @artkoenig Can you include the full stack trace?
io.jsonwebtoken.security.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
at io.jsonwebtoken.impl.DefaultJwtParser.verifySignature(DefaultJwtParser.java:340)
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:579)
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:364)
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:94)
at io.jsonwebtoken.impl.io.AbstractParser.parse(AbstractParser.java:36)
at io.jsonwebtoken.impl.io.AbstractParser.parse(AbstractParser.java:29)
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:94)
at com.example.jwt.MainActivity.onCreate(MainActivity.kt:64)
at android.app.Activity.performCreate(Activity.java:8595)
at android.app.Activity.performCreate(Activity.java:8573)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1456)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3764)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3922)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:139)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:96)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2443)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8177)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
Try forcing the BC provider by doing something like:
val jwtPS512 = "..."
try {
val parser = Jwts.parser()
+ .provider(BouncyCastleProvider())
.verifyWith(stringToPublicKey())
.build()
val test = parser.parse(jwtPS512)
Log.i("", "$test")
} catch (e: Throwable) {
Log.e("", "", e)
}
If that doesn't help, does your reproduce case require running the tests on an Android device? Try sticking a break point at DefaultJwtParser:336 and check if there is anything suspicious with the provider
that is used at that point.
I turned your example into a quick unit test and it seemed to parse fine (running on a normal JVM), any idea if this effects other versions of Android?
To isolate Java vs Android environment issues, I just ran a Java test on JDK 8 (which does not support PS* algorithms, and therefore requires BouncyCastle to be loaded), and the test worked:
package test;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import org.junit.Test;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
public class PS512Test {
private static final String PUB_PEM = "-----BEGIN PUBLIC KEY-----\n" +
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo\n" +
"4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u\n" +
"+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh\n" +
"kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ\n" +
"0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg\n" +
"cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc\n" +
"mwIDAQAB\n" +
"-----END PUBLIC KEY-----";
private static String pemToB64(String pem) {
return pem.replaceAll("\\n", "")
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("-----END PUBLIC KEY-----", "");
}
private static RSAPublicKey pub(String pem) throws Exception {
String encoded = pemToB64(pem);
KeyFactory kf = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(Decoders.BASE64.decode(encoded));
return (RSAPublicKey) kf.generatePublic(keySpecX509);
}
@Test
public void test() throws Exception {
String jwtPS512 = "eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.GNhJz8YNyIT2e2kpO7jkH8K7z8lCP1Tsn3YO5_W_7BxB0U6VdoOK_1-l3Y8gWQV-XNrObDFsAdpvudTNkF_cQzZO3I3_6LdjU3iQ4NSTbJwaiaDzyaARC8hIWa55K7Hfz_m9btKOahJpqiiZ5RZNeCVC9VII4uxbuZozfC8r0aXsnmd97TH2vdpIcnzuADH_Cu_AhUSF2C1Bsk4RZe6wf_WmopP48WD3EUmZYvnaSuACtZrN3jRIymcvmtQWWOkFlAjHxjSyKYO33MgpPh1wI_jLfOUZY0S8gxylbd8LK3b0YE0jkpjOznsY8M03dAAS8V_pfzLOLB2yMrRR9e0mLA";
Jwts.parser().verifyWith(pub(PUB_PEM)).build().parseSignedClaims(jwtPS512);
}
}
This leads me to believe that BouncyCastle provider is not being loaded correctly (or in time?) in the Android environment before parsing the jws string.
I'm not sure why though. Still need more testing.
.provider(BouncyCastleProvider())
This change fixed my problem. Thank you very much!
.provider(BouncyCastleProvider())
This change fixed my problem. Thank you very much!
Just note that will force ALL operations used by that parser to always use that provider. That's probably ok for Android environments however.
But the bigger question:
Why wasn't BouncyCastleProvider()
replacing the Android default via the following?
companion object {
init {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
Security.addProvider(BouncyCastleProvider())
}
}
That should 'just work'.
I could be wrong, but maybe it's because the sample project has this:
class MainActivity : ComponentActivity() {
instead of this:
class MainActivity : AppCompatActivity() {
as documented here: https://github.com/jwtk/jjwt?tab=readme-ov-file#bouncy-castle
?
@artkoenig
Would you be able to try this:
class MainActivity : AppCompatActivity() { // ... etc ...
}
And see if that registers BC correctly so you don't need to specify it on the parser?
Closing due to inactivity or reply from OP. Feel free to ask to re-open if necessary!
I use the default JWTs generated by https://jwt.io/. One using the RS512 algorithm and the same token using the PS512 algorithm. I'd like to verify the token using the provided public key in my Android app. Here are my gradle includes:
The
MainActivity
replaces the "BC" security provider by:Here is the code for reading the public key (for test purposes stored as local variable):
Here is the code for token validation:
Here is the link to the full project example: https://github.com/artkoenig/JWT-Test
The validation works for the RS512 algorithm, but for the PS512 algorithm I get the following error:
Please let me know if I missed anything.