Merge pull request #359 from jwtk/285-base64-exceptions

Descriptive exception when decoding illegal Base64(Url) input
This commit is contained in:
Les Hazlewood 2018-07-23 15:50:46 -04:00 committed by GitHub
commit fbcc9ab931
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 67 additions and 2 deletions

View File

@ -27,6 +27,7 @@ final class Base64 { //final and package-protected on purpose
private static final char[] BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".toCharArray();
private static final int[] BASE64_IALPHABET = new int[256];
private static final int[] BASE64URL_IALPHABET = new int[256];
private static final int IALPHABET_MAX_INDEX = BASE64_IALPHABET.length - 1;
static {
Arrays.fill(BASE64_IALPHABET, -1);
@ -56,6 +57,10 @@ final class Base64 { //final and package-protected on purpose
// * char[] version
// ****************************************************************************************
private String getName() {
return urlsafe ? "base64url" : "base64"; // RFC 4648 codec names are all lowercase
}
/**
* Encodes a raw byte array into a BASE64 <code>char[]</code> representation in accordance with RFC 2045.
*
@ -194,6 +199,15 @@ final class Base64 { //final and package-protected on purpose
}
*/
private int ctoi(char c) {
int i = c > IALPHABET_MAX_INDEX ? -1 : IALPHABET[c];
if (i < 0) {
String msg = "Illegal " + getName() + " character: '" + c + "'";
throw new DecodingException(msg);
}
return i;
}
/**
* Decodes a BASE64 encoded char array that is known to be reasonably well formatted. The preconditions are:<br>
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
@ -237,7 +251,7 @@ final class Base64 { //final and package-protected on purpose
for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) {
// Assemble three bytes into an int from four "valid" characters.
int i = IALPHABET[sArr[sIx++]] << 18 | IALPHABET[sArr[sIx++]] << 12 | IALPHABET[sArr[sIx++]] << 6 | IALPHABET[sArr[sIx++]];
int i = ctoi(sArr[sIx++]) << 18 | ctoi(sArr[sIx++]) << 12 | ctoi(sArr[sIx++]) << 6 | ctoi(sArr[sIx++]);
// Add the bytes
dArr[d++] = (byte) (i >> 16);
@ -255,7 +269,7 @@ final class Base64 { //final and package-protected on purpose
// Decode last 1-3 bytes (incl '=') into 1-3 bytes
int i = 0;
for (int j = 0; sIx <= eIx - pad; j++) {
i |= IALPHABET[sArr[sIx++]] << (18 - j * 6);
i |= ctoi(sArr[sIx++]) << (18 - j * 6);
}
for (int r = 16; d < len; r -= 8) {

View File

@ -5,6 +5,10 @@ package io.jsonwebtoken.io;
*/
public class CodecException extends IOException {
public CodecException(String message) {
super(message);
}
public CodecException(String message, Throwable cause) {
super(message, cause);
}

View File

@ -5,6 +5,10 @@ package io.jsonwebtoken.io;
*/
public class DecodingException extends CodecException {
public DecodingException(String message) {
super(message);
}
public DecodingException(String message, Throwable cause) {
super(message, cause);
}

View File

@ -23,6 +23,16 @@ class Base64Test {
,
, '''
@Test
void testBase64Name() {
assertEquals 'base64', Base64.DEFAULT.getName() // RFC 4648 codec name is all lowercase
}
@Test
void testBase64UrlName() {
assertEquals 'base64url', Base64.URL_SAFE.getName() // RFC 4648 codec name is all lowercase
}
@Test
void testEncodeToStringWithNullArgument() {
String s = Base64.DEFAULT.encodeToString(null, false)
@ -70,6 +80,39 @@ class Base64Test {
assertEquals expected, result
}
@Test
void testDecodeFastWithIntermediateIllegalInboundCharacters() {
def encoded = 'SGVsbG8g*5LiW55WM'
try {
Base64.DEFAULT.decodeFast(encoded.toCharArray())
fail()
} catch (DecodingException de) {
assertEquals 'Illegal base64 character: \'*\'', de.getMessage()
}
}
@Test
void testDecodeFastWithIntermediateIllegalOutOfBoundCharacters() {
def encoded = 'SGVsbG8g世5LiW55WM'
try {
Base64.DEFAULT.decodeFast(encoded.toCharArray())
fail()
} catch (DecodingException de) {
assertEquals 'Illegal base64 character: \'世\'', de.getMessage()
}
}
@Test
void testDecodeFastWithIntermediateIllegalSpaceCharacters() {
def encoded = 'SGVsbG8g 5LiW55WM'
try {
Base64.DEFAULT.decodeFast(encoded.toCharArray())
fail()
} catch (DecodingException de) {
assertEquals 'Illegal base64 character: \' \'', de.getMessage()
}
}
@Test
void testDecodeFastWithLineSeparators() {