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 char[] BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".toCharArray();
private static final int[] BASE64_IALPHABET = new int[256]; private static final int[] BASE64_IALPHABET = new int[256];
private static final int[] BASE64URL_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 { static {
Arrays.fill(BASE64_IALPHABET, -1); Arrays.fill(BASE64_IALPHABET, -1);
@ -56,6 +57,10 @@ final class Base64 { //final and package-protected on purpose
// * char[] version // * 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. * 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> * 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> * + 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; ) { for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) {
// Assemble three bytes into an int from four "valid" characters. // 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 // Add the bytes
dArr[d++] = (byte) (i >> 16); 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 // Decode last 1-3 bytes (incl '=') into 1-3 bytes
int i = 0; int i = 0;
for (int j = 0; sIx <= eIx - pad; j++) { 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) { for (int r = 16; d < len; r -= 8) {

View File

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

View File

@ -5,6 +5,10 @@ package io.jsonwebtoken.io;
*/ */
public class DecodingException extends CodecException { public class DecodingException extends CodecException {
public DecodingException(String message) {
super(message);
}
public DecodingException(String message, Throwable cause) { public DecodingException(String message, Throwable cause) {
super(message, 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 @Test
void testEncodeToStringWithNullArgument() { void testEncodeToStringWithNullArgument() {
String s = Base64.DEFAULT.encodeToString(null, false) String s = Base64.DEFAULT.encodeToString(null, false)
@ -70,6 +80,39 @@ class Base64Test {
assertEquals expected, result 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 @Test
void testDecodeFastWithLineSeparators() { void testDecodeFastWithLineSeparators() {