Skip to content

Commit 582b5d5

Browse files
author
Laurent YHUEL
committed
Create a wrapper to access Base64 encode/decode to fix bug auth0#131 on Android platform
1 parent f6c4501 commit 582b5d5

13 files changed

+168
-74
lines changed

lib/src/main/java/com/auth0/jwt/JWTCreator.java

+7-4
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
import com.auth0.jwt.impl.ClaimsHolder;
77
import com.auth0.jwt.impl.PayloadSerializer;
88
import com.auth0.jwt.impl.PublicClaims;
9+
import com.auth0.jwt.wrapper.Base64Wrapper;
910
import com.fasterxml.jackson.core.JsonProcessingException;
1011
import com.fasterxml.jackson.databind.MapperFeature;
1112
import com.fasterxml.jackson.databind.ObjectMapper;
1213
import com.fasterxml.jackson.databind.module.SimpleModule;
13-
import org.apache.commons.codec.binary.Base64;
1414

1515
import java.nio.charset.StandardCharsets;
1616
import java.util.Date;
@@ -327,12 +327,15 @@ private void addClaim(String name, Object value) {
327327
}
328328

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

334334
byte[] signatureBytes = algorithm.sign(content.getBytes(StandardCharsets.UTF_8));
335-
String signature = Base64.encodeBase64URLSafeString((signatureBytes));
335+
336+
337+
338+
String signature = Base64Wrapper.getInstance().encode((signatureBytes));
336339

337340
return String.format("%s.%s", content, signature);
338341
}

lib/src/main/java/com/auth0/jwt/JWTDecoder.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import com.auth0.jwt.interfaces.DecodedJWT;
77
import com.auth0.jwt.interfaces.Header;
88
import com.auth0.jwt.interfaces.Payload;
9-
import org.apache.commons.codec.binary.Base64;
9+
import com.auth0.jwt.wrapper.Base64Wrapper;
10+
1011
import org.apache.commons.codec.binary.StringUtils;
1112

1213
import java.util.Date;
@@ -29,8 +30,8 @@ final class JWTDecoder implements DecodedJWT {
2930
String headerJson;
3031
String payloadJson;
3132
try {
32-
headerJson = StringUtils.newStringUtf8(Base64.decodeBase64(parts[0]));
33-
payloadJson = StringUtils.newStringUtf8(Base64.decodeBase64(parts[1]));
33+
headerJson = StringUtils.newStringUtf8(Base64Wrapper.getInstance().decode(parts[0]));
34+
payloadJson = StringUtils.newStringUtf8(Base64Wrapper.getInstance().decode(parts[1]));
3435
} catch (NullPointerException e) {
3536
throw new JWTDecodeException("The UTF-8 Charset isn't initialized.", e);
3637
}

lib/src/main/java/com/auth0/jwt/algorithms/ECDSAAlgorithm.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import com.auth0.jwt.exceptions.SignatureVerificationException;
55
import com.auth0.jwt.interfaces.DecodedJWT;
66
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
7-
import org.apache.commons.codec.binary.Base64;
7+
import com.auth0.jwt.wrapper.Base64Wrapper;
88

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

4242
try {
4343
ECPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId());

lib/src/main/java/com/auth0/jwt/algorithms/HMACAlgorithm.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import com.auth0.jwt.exceptions.SignatureGenerationException;
44
import com.auth0.jwt.exceptions.SignatureVerificationException;
55
import com.auth0.jwt.interfaces.DecodedJWT;
6+
import com.auth0.jwt.wrapper.Base64Wrapper;
7+
68
import org.apache.commons.codec.CharEncoding;
7-
import org.apache.commons.codec.binary.Base64;
89

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

5051
try {
5152
boolean valid = crypto.verifySignatureFor(getDescription(), secret, contentBytes, signatureBytes);

lib/src/main/java/com/auth0/jwt/algorithms/NoneAlgorithm.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import com.auth0.jwt.exceptions.SignatureGenerationException;
44
import com.auth0.jwt.exceptions.SignatureVerificationException;
55
import com.auth0.jwt.interfaces.DecodedJWT;
6-
import org.apache.commons.codec.binary.Base64;
6+
import com.auth0.jwt.wrapper.Base64Wrapper;
77

88
class NoneAlgorithm extends Algorithm {
99

@@ -13,7 +13,7 @@ class NoneAlgorithm extends Algorithm {
1313

1414
@Override
1515
public void verify(DecodedJWT jwt) throws SignatureVerificationException {
16-
byte[] signatureBytes = Base64.decodeBase64(jwt.getSignature());
16+
byte[] signatureBytes = Base64Wrapper.getInstance().decode(jwt.getSignature());
1717
if (signatureBytes.length > 0) {
1818
throw new SignatureVerificationException(this);
1919
}

lib/src/main/java/com/auth0/jwt/algorithms/RSAAlgorithm.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import com.auth0.jwt.exceptions.SignatureVerificationException;
55
import com.auth0.jwt.interfaces.DecodedJWT;
66
import com.auth0.jwt.interfaces.RSAKeyProvider;
7-
import org.apache.commons.codec.binary.Base64;
7+
import com.auth0.jwt.wrapper.Base64Wrapper;
88

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

4040
try {
4141
RSAPublicKey publicKey = keyProvider.getPublicKeyById(jwt.getKeyId());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.auth0.jwt.wrapper;
2+
3+
import com.auth0.jwt.exceptions.JWTCreationException;
4+
import com.auth0.jwt.exceptions.JWTDecodeException;
5+
6+
import org.apache.commons.codec.binary.Base64;
7+
8+
import java.lang.reflect.InvocationTargetException;
9+
import java.lang.reflect.Method;
10+
11+
/**
12+
* Base 64 Wrapper to fix https://github.com./auth0/java-jwt/issues/131
13+
* Use Base64 from Android if on Android environment otherwise use apache commons codec
14+
* Created by yhuel on 09/08/17.
15+
*/
16+
17+
public class Base64Wrapper {
18+
19+
20+
/**
21+
* Android Base64 flag
22+
* https://developer.android.com/reference/android/util/Base64.html#URL_SAFE
23+
*/
24+
private static final int FLAG_URL_SAFE = 8;
25+
/**
26+
* Android Base64 flag
27+
* https://developer.android.com/reference/android/util/Base64.html#NO_PADDING
28+
*/
29+
private static final int FLAG_NO_PADDING = 1;
30+
31+
public static Base64Wrapper instance = new Base64Wrapper();
32+
33+
private boolean androidEnvironment;
34+
private Method androidMethodEncode;
35+
private Method androidMethodDecode;
36+
37+
38+
public static Base64Wrapper getInstance() {
39+
return instance;
40+
}
41+
42+
private Base64Wrapper(){
43+
//check if you are on Android platform
44+
try {
45+
//use reflexion to know if you're on Android platform (not very sexy)
46+
Class<?> androidBase64 = Class.forName("android.util.Base64");
47+
androidMethodDecode = androidBase64.getMethod("decode",String.class, int.class);
48+
androidMethodEncode = androidBase64.getMethod("encodeToString", byte[].class, int.class);
49+
androidEnvironment = true;
50+
} catch (ClassNotFoundException | NoSuchMethodException e) {
51+
//nothing to do : not on Android Environment
52+
}
53+
}
54+
55+
public String encode(byte[] data) throws JWTCreationException{
56+
if(androidEnvironment) {
57+
try {
58+
/*
59+
* use flags to have same behavior as apache commons codec
60+
* see : https://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/binary/Base64.html#encodeBase64URLSafeString(byte[])
61+
*/
62+
return (String)androidMethodEncode.invoke(null,data, FLAG_NO_PADDING|FLAG_URL_SAFE);
63+
} catch (IllegalAccessException | InvocationTargetException e) {
64+
throw new JWTCreationException("Error when encode in Base64 in android environment",e);
65+
}
66+
}else{
67+
return Base64.encodeBase64URLSafeString(data);
68+
}
69+
}
70+
71+
public byte[] decode(String data) throws JWTDecodeException{
72+
if(androidEnvironment) {
73+
try {
74+
//use same flag FLAG_URL_SAFE as encode method, flag FLAG_NO_PADDING only for encode
75+
return (byte[])androidMethodDecode.invoke(null,data, FLAG_URL_SAFE);
76+
} catch (IllegalAccessException | InvocationTargetException e) {
77+
throw new JWTCreationException("Error when encode in Base64 in android environment",e);
78+
}
79+
}else {
80+
return Base64.decodeBase64(data);
81+
}
82+
}
83+
}

lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java

+10-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import com.auth0.jwt.algorithms.Algorithm;
44
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
55
import com.auth0.jwt.interfaces.RSAKeyProvider;
6-
import org.apache.commons.codec.binary.Base64;
6+
import com.auth0.jwt.wrapper.Base64Wrapper;
7+
78
import org.junit.Rule;
89
import org.junit.Test;
910
import org.junit.rules.ExpectedException;
@@ -49,7 +50,7 @@ public void shouldAddHeaderClaim() throws Exception {
4950

5051
assertThat(signed, is(notNullValue()));
5152
String[] parts = signed.split("\\.");
52-
String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8);
53+
String headerJson = new String(Base64Wrapper.getInstance().decode(parts[0]), StandardCharsets.UTF_8);
5354
assertThat(headerJson, JsonMatcher.hasEntry("asd", 123));
5455
}
5556

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

6263
assertThat(signed, is(notNullValue()));
6364
String[] parts = signed.split("\\.");
64-
String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8);
65+
String headerJson = new String(Base64Wrapper.getInstance().decode(parts[0]), StandardCharsets.UTF_8);
6566
assertThat(headerJson, JsonMatcher.hasEntry("kid", "56a8bd44da435300010000015f5ed"));
6667
}
6768

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

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

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

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

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

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

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

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

@@ -227,7 +228,7 @@ public void shouldSetCorrectAlgorithmInTheHeader() throws Exception {
227228

228229
assertThat(signed, is(notNullValue()));
229230
String[] parts = signed.split("\\.");
230-
String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8);
231+
String headerJson = new String(Base64Wrapper.getInstance().decode(parts[0]), StandardCharsets.UTF_8);
231232
assertThat(headerJson, JsonMatcher.hasEntry("alg", "HS256"));
232233
}
233234

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

239240
assertThat(signed, is(notNullValue()));
240241
String[] parts = signed.split("\\.");
241-
String headerJson = new String(Base64.decodeBase64(parts[0]), StandardCharsets.UTF_8);
242+
String headerJson = new String(Base64Wrapper.getInstance().decode(parts[0]), StandardCharsets.UTF_8);
242243
assertThat(headerJson, JsonMatcher.hasEntry("typ", "JWT"));
243244
}
244245

lib/src/test/java/com/auth0/jwt/JWTDecoderTest.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import com.auth0.jwt.impl.NullClaim;
55
import com.auth0.jwt.interfaces.Claim;
66
import com.auth0.jwt.interfaces.DecodedJWT;
7-
import org.apache.commons.codec.binary.Base64;
7+
import com.auth0.jwt.wrapper.Base64Wrapper;
8+
89
import org.hamcrest.collection.IsCollectionWithSize;
910
import org.hamcrest.core.IsCollectionContaining;
1011
import org.junit.Assert;
@@ -292,8 +293,8 @@ public void shouldGetAvailableClaims() throws Exception {
292293
//Helper Methods
293294

294295
private DecodedJWT customJWT(String jsonHeader, String jsonPayload, String signature) {
295-
String header = Base64.encodeBase64URLSafeString(jsonHeader.getBytes(StandardCharsets.UTF_8));
296-
String body = Base64.encodeBase64URLSafeString(jsonPayload.getBytes(StandardCharsets.UTF_8));
296+
String header = Base64Wrapper.getInstance().encode(jsonHeader.getBytes(StandardCharsets.UTF_8));
297+
String body = Base64Wrapper.getInstance().encode(jsonPayload.getBytes(StandardCharsets.UTF_8));
297298
return JWT.decode(String.format("%s.%s.%s", header, body, signature));
298299
}
299300

0 commit comments

Comments
 (0)