Skip to content

Fix bug when sign JWT on Android platform #193

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Temporary Items
# Intellij
.idea/
*.iml
/local.properties

## File-based project format:
*.iws
Expand Down
11 changes: 7 additions & 4 deletions lib/src/main/java/com/auth0/jwt/JWTCreator.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import com.auth0.jwt.impl.ClaimsHolder;
import com.auth0.jwt.impl.PayloadSerializer;
import com.auth0.jwt.impl.PublicClaims;
import com.auth0.jwt.wrapper.Base64Wrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.apache.commons.codec.binary.Base64;

import java.nio.charset.StandardCharsets;
import java.util.Date;
Expand Down Expand Up @@ -327,12 +327,15 @@ private void addClaim(String name, Object value) {
}

private String sign() throws SignatureGenerationException {
String header = Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.UTF_8));
String payload = Base64.encodeBase64URLSafeString(payloadJson.getBytes(StandardCharsets.UTF_8));
String header = Base64Wrapper.getInstance().encode(headerJson.getBytes(StandardCharsets.UTF_8));
String payload = Base64Wrapper.getInstance().encode(payloadJson.getBytes(StandardCharsets.UTF_8));
String content = String.format("%s.%s", header, payload);

byte[] signatureBytes = algorithm.sign(content.getBytes(StandardCharsets.UTF_8));
String signature = Base64.encodeBase64URLSafeString((signatureBytes));



String signature = Base64Wrapper.getInstance().encode((signatureBytes));

return String.format("%s.%s", content, signature);
}
Expand Down
7 changes: 4 additions & 3 deletions lib/src/main/java/com/auth0/jwt/JWTDecoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Header;
import com.auth0.jwt.interfaces.Payload;
import org.apache.commons.codec.binary.Base64;
import com.auth0.jwt.wrapper.Base64Wrapper;

import org.apache.commons.codec.binary.StringUtils;

import java.util.Date;
Expand All @@ -29,8 +30,8 @@ final class JWTDecoder implements DecodedJWT {
String headerJson;
String payloadJson;
try {
headerJson = StringUtils.newStringUtf8(Base64.decodeBase64(parts[0]));
payloadJson = StringUtils.newStringUtf8(Base64.decodeBase64(parts[1]));
headerJson = StringUtils.newStringUtf8(Base64Wrapper.getInstance().decode(parts[0]));
payloadJson = StringUtils.newStringUtf8(Base64Wrapper.getInstance().decode(parts[1]));
} catch (NullPointerException e) {
throw new JWTDecodeException("The UTF-8 Charset isn't initialized.", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
import org.apache.commons.codec.binary.Base64;
import com.auth0.jwt.wrapper.Base64Wrapper;

import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
Expand Down Expand Up @@ -37,7 +37,7 @@ class ECDSAAlgorithm extends Algorithm {
@Override
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8);
byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature());
byte[] signatureBytes = Base64Wrapper.getInstance().decode(jwt.getSignature());

try {
ECPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import com.auth0.jwt.exceptions.SignatureGenerationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.wrapper.Base64Wrapper;

import org.apache.commons.codec.CharEncoding;
import org.apache.commons.codec.binary.Base64;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -45,7 +46,7 @@ static byte[] getSecretBytes(String secret) throws IllegalArgumentException, Uns
@Override
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8);
byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature());
byte[] signatureBytes = Base64Wrapper.getInstance().decode(jwt.getSignature());

try {
boolean valid = crypto.verifySignatureFor(getDescription(), secret, contentBytes, signatureBytes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.auth0.jwt.exceptions.SignatureGenerationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.codec.binary.Base64;
import com.auth0.jwt.wrapper.Base64Wrapper;

class NoneAlgorithm extends Algorithm {

Expand All @@ -13,7 +13,7 @@ class NoneAlgorithm extends Algorithm {

@Override
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature());
byte[] signatureBytes = Base64Wrapper.getInstance().decode(jwt.getSignature());
if (signatureBytes.length > 0) {
throw new SignatureVerificationException(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.RSAKeyProvider;
import org.apache.commons.codec.binary.Base64;
import com.auth0.jwt.wrapper.Base64Wrapper;

import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
Expand Down Expand Up @@ -35,7 +35,7 @@ class RSAAlgorithm extends Algorithm {
@Override
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
byte[] contentBytes = String.format("%s.%s", jwt.getHeader(), jwt.getPayload()).getBytes(StandardCharsets.UTF_8);
byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature());
byte[] signatureBytes = Base64Wrapper.getInstance().decode(jwt.getSignature());

try {
RSAPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId());
Expand Down
83 changes: 83 additions & 0 deletions lib/src/main/java/com/auth0/jwt/wrapper/Base64Wrapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.auth0.jwt.wrapper;

import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTDecodeException;

import org.apache.commons.codec.binary.Base64;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* Base 64 Wrapper to fix https://github.com/auth0/java-jwt/issues/131
* Use Base64 from Android if on Android environment otherwise use apache commons codec
* Created by yhuel on 09/08/17.
*/

public class Base64Wrapper {


/**
* Android Base64 flag
* https://developer.android.com/reference/android/util/Base64.html#URL_SAFE
*/
private static final int FLAG_URL_SAFE = 8;
/**
* Android Base64 flag
* https://developer.android.com/reference/android/util/Base64.html#NO_PADDING
*/
private static final int FLAG_NO_PADDING = 1;

public static Base64Wrapper instance = new Base64Wrapper();

private boolean androidEnvironment;
private Method androidMethodEncode;
private Method androidMethodDecode;


public static Base64Wrapper getInstance() {
return instance;
}

private Base64Wrapper(){
//check if you are on Android platform
try {
//use reflexion to know if you're on Android platform (not very sexy)
Class<?> androidBase64 = Class.forName("android.util.Base64");
androidMethodDecode = androidBase64.getMethod("decode",String.class, int.class);
androidMethodEncode = androidBase64.getMethod("encodeToString", byte[].class, int.class);
androidEnvironment = true;
} catch (ClassNotFoundException | NoSuchMethodException e) {
//nothing to do : not on Android Environment
}
}

public String encode(byte[] data) throws JWTCreationException{
if(androidEnvironment) {
try {
/*
* use flags to have same behavior as apache commons codec
* see : https://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/binary/Base64.html#encodeBase64URLSafeString(byte[])
*/
return (String)androidMethodEncode.invoke(null,data, FLAG_NO_PADDING|FLAG_URL_SAFE);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new JWTCreationException("Error when encode in Base64 in android environment",e);
}
}else{
return Base64.encodeBase64URLSafeString(data);
}
}

public byte[] decode(String data) throws JWTDecodeException{
if(androidEnvironment) {
try {
//use same flag FLAG_URL_SAFE as encode method, flag FLAG_NO_PADDING only for encode
return (byte[])androidMethodDecode.invoke(null,data, FLAG_URL_SAFE);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new JWTCreationException("Error when encode in Base64 in android environment",e);
}
}else {
return Base64.decodeBase64(data);
}
}
}
19 changes: 10 additions & 9 deletions lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
import com.auth0.jwt.interfaces.RSAKeyProvider;
import org.apache.commons.codec.binary.Base64;
import com.auth0.jwt.wrapper.Base64Wrapper;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
Expand Down Expand Up @@ -49,7 +50,7 @@ public void shouldAddHeaderClaim() throws Exception {

assertThat(signed, is(notNullValue()));
String[] parts = signed.split("\\.");
String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8);
String headerJson = new String(Base64Wrapper.getInstance().decode(parts[0]), StandardCharsets.UTF_8);
assertThat(headerJson, JsonMatcher.hasEntry("asd", 123));
}

Expand All @@ -61,7 +62,7 @@ public void shouldAddKeyId() throws Exception {

assertThat(signed, is(notNullValue()));
String[] parts = signed.split("\\.");
String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8);
String headerJson = new String(Base64Wrapper.getInstance().decode(parts[0]), StandardCharsets.UTF_8);
assertThat(headerJson, JsonMatcher.hasEntry("kid", "56a8bd44da435300010000015f5ed"));
}

Expand All @@ -77,7 +78,7 @@ public void shouldAddKeyIdIfAvailableFromRSAAlgorithms() throws Exception {

assertThat(signed, is(notNullValue()));
String[] parts = signed.split("\\.");
String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8);
String headerJson = new String(Base64Wrapper.getInstance().decode(parts[0]), StandardCharsets.UTF_8);
assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id"));
}

Expand All @@ -94,7 +95,7 @@ public void shouldNotOverwriteKeyIdIfAddedFromRSAAlgorithms() throws Exception {

assertThat(signed, is(notNullValue()));
String[] parts = signed.split("\\.");
String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8);
String headerJson = new String(Base64Wrapper.getInstance().decode(parts[0]), StandardCharsets.UTF_8);
assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id"));
}

Expand All @@ -110,7 +111,7 @@ public void shouldAddKeyIdIfAvailableFromECDSAAlgorithms() throws Exception {

assertThat(signed, is(notNullValue()));
String[] parts = signed.split("\\.");
String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8);
String headerJson = new String(Base64Wrapper.getInstance().decode(parts[0]), StandardCharsets.UTF_8);
assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id"));
}

Expand All @@ -127,7 +128,7 @@ public void shouldNotOverwriteKeyIdIfAddedFromECDSAAlgorithms() throws Exception

assertThat(signed, is(notNullValue()));
String[] parts = signed.split("\\.");
String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8);
String headerJson = new String(Base64Wrapper.getInstance().decode(parts[0]), StandardCharsets.UTF_8);
assertThat(headerJson, JsonMatcher.hasEntry("kid", "my-key-id"));
}

Expand Down Expand Up @@ -227,7 +228,7 @@ public void shouldSetCorrectAlgorithmInTheHeader() throws Exception {

assertThat(signed, is(notNullValue()));
String[] parts = signed.split("\\.");
String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8);
String headerJson = new String(Base64Wrapper.getInstance().decode(parts[0]), StandardCharsets.UTF_8);
assertThat(headerJson, JsonMatcher.hasEntry("alg", "HS256"));
}

Expand All @@ -238,7 +239,7 @@ public void shouldSetCorrectTypeInTheHeader() throws Exception {

assertThat(signed, is(notNullValue()));
String[] parts = signed.split("\\.");
String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8);
String headerJson = new String(Base64Wrapper.getInstance().decode(parts[0]), StandardCharsets.UTF_8);
assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT"));
}

Expand Down
7 changes: 4 additions & 3 deletions lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import com.auth0.jwt.impl.NullClaim;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.codec.binary.Base64;
import com.auth0.jwt.wrapper.Base64Wrapper;

import org.hamcrest.collection.IsCollectionWithSize;
import org.hamcrest.core.IsCollectionContaining;
import org.junit.Assert;
Expand Down Expand Up @@ -292,8 +293,8 @@ public void shouldGetAvailableClaims() throws Exception {
//Helper Methods

private DecodedJWT customJWT(String jsonHeader, String jsonPayload, String signature) {
String header = Base64.encodeBase64URLSafeString(jsonHeader.getBytes(StandardCharsets.UTF_8));
String body = Base64.encodeBase64URLSafeString(jsonPayload.getBytes(StandardCharsets.UTF_8));
String header = Base64Wrapper.getInstance().encode(jsonHeader.getBytes(StandardCharsets.UTF_8));
String body = Base64Wrapper.getInstance().encode(jsonPayload.getBytes(StandardCharsets.UTF_8));
return JWT.decode(String.format("%s.%s.%s", header, body, signature));
}

Expand Down
Loading