- Implemented new Base64 encoder forked from MigBase64 to guarantee deterministic behavior on all JDK and Android platforms

- Allowed pluggable Encoder/Decoder for JWT building and parsing via new Encoder/Decoder and JwtBuilder#base64UrlEncodeWith
  and JwtParser#base64UrlDecodeWith methods respectively
- added RFC 4648 Base64 test vectors per code review
- Added tests for all new code to retain 100% code coverage, verified by Clover and Coveralls
- Enabled oraclejdk10 and openjdk10 builds in TravisCI
- Replaced gmaven plugin with gmavenplus to work on JDK >= 9
- Upgraded surefire and failsafe plugins to 2.22.0 to ensure build works on JDK >= 10
- Ensured JavaDoc linter wouldn't fail the build for JDK >= 8 (was previously only 1.8)
- Updated changelog doc to reflect new Base64 functionality
This commit is contained in:
Les Hazlewood 2018-07-07 15:28:23 -04:00
parent 130a841011
commit 6e1415c441
53 changed files with 1765 additions and 271 deletions

View File

@ -7,6 +7,10 @@ jdk:
- openjdk7
- oraclejdk8
- oraclejdk9
- oraclejdk10
- openjdk10
- oraclejdk-ea
- openjdk11
before_install:
- export BUILD_COVERAGE="$([ $TRAVIS_JDK_VERSION == 'oraclejdk8' ] && echo 'true')"

View File

@ -1,5 +1,40 @@
## Release Notes
### 0.10.0
This is a minor feature enhancement that ensures Base64 encoding is identical and deterministic on all JDK and
Android platforms. JJWT previously relied on the underlying platform encoder (JAXB xml DataBinding or Android's native
Base64), however in some cases this produced unexpected results across platforms, including when compared against the
JDK's >= 8 Base64 implementation.
JJWT now embeds a super lightweight (1 class) and *extremely* fast RFC-compliant Base64 implementation to guarantee
portability across all supported platforms. It is now enabled automatically and there is nothing you need to
do to enable it.
However, if the default implementation isn't sufficient for your purposes, you may now specify your own
encoder during JWT building or decoder during JWT parsing as you see fit.
For example, an encoder during building:
```java
Encoder<byte[], String> encoder = new MyBase64UrlEncoder(); //implement me
Jwts.builder()
.base64UrlEncodeWith(encoder)
// ... etc ...
.compact();
```
Or a decoder during parsing:
```java
Decoder<String, byte[]> decoder = new MyBase64UrlDecoder(); //implement me
Jwts.parser()
.base64UrlDecodeWith(decoder)
// ... etc ...
.parseClaimsJws(jws);
```
### 0.9.1
This is a minor patch release that updates the Jackson dependency to 2.9.6 to address Jackson CVE-2017-17485.
@ -136,7 +171,7 @@ Jwts.builder().claim("foo", "someReallyLongDataString...")
.compact();
```
This will set a new `calg` header with the name of the compression algorithm used so that parsers can see that value and decompress accordingly.
This will set a new `zip` header with the name of the compression algorithm used so that parsers can see that value and decompress accordingly.
The default parser implementation will automatically decompress DEFLATE or GZIP compressed bodies, so you don't need to set anything on the parser - it looks like normal:
@ -155,7 +190,7 @@ Jwts.builder().claim("foo", "someReallyLongDataString...")
.compact();
```
You will then need to specify a `CompressionCodecResolver` on the parser, so you can inspect the `calg` header and return your custom codec when discovered:
You will then need to specify a `CompressionCodecResolver` on the parser, so you can inspect the `zip` header and return your custom codec when discovered:
```java
Jwts.parser().setSigningKey(key)

32
NOTICE Normal file
View File

@ -0,0 +1,32 @@
The io.jsonwebtoken.codec.impl.Base64 implementation is based on MigBase64 with modifications for Base64 URL support. This
class's copyright and license notice have been retained and are repeated here per that code's requirements:
**** BEGIN MIGBASE64 NOTICE *****
Licence (BSD):
==============
Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list
of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
Neither the name of the MiG InfoCom AB nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.
**** END MIGBASE64 NOTICE *****

39
pom.xml
View File

@ -91,13 +91,13 @@
<bouncycastle.version>1.56</bouncycastle.version>
<!-- Test Dependencies: Only required for testing when building. Not required by users at runtime: -->
<groovy.version>2.4.11</groovy.version>
<groovy.version>2.4.15</groovy.version>
<logback.version>1.2.3</logback.version>
<easymock.version>3.5</easymock.version>
<junit.version>4.12</junit.version>
<powermock.version>2.0.0-beta.5</powermock.version> <!-- necessary for Java 9 support -->
<failsafe.plugin.version>2.20.1</failsafe.plugin.version>
<surefire.plugin.version>2.20.1</surefire.plugin.version>
<failsafe.plugin.version>2.22.0</failsafe.plugin.version>
<surefire.plugin.version>2.22.0</surefire.plugin.version>
<clover.version>4.2.0</clover.version>
</properties>
@ -233,35 +233,24 @@
</plugin>
<!-- Allow for writing tests in Groovy: -->
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<version>1.5</version>
<configuration>
<providerSelection>2.0</providerSelection>
<source />
</configuration>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.6.1</version>
<executions>
<execution>
<goals>
<goal>addSources</goal>
<goal>addTestSources</goal>
<goal>generateStubs</goal>
<goal>compile</goal>
<goal>generateTestStubs</goal>
<goal>testCompile</goal>
<goal>compileTests</goal>
<goal>removeStubs</goal>
<goal>removeTestStubs</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.codehaus.gmaven.runtime</groupId>
<artifactId>gmaven-runtime-2.0</artifactId>
<version>1.5</version>
<exclusions>
<exclusion>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
@ -406,12 +395,12 @@
</build>
<profiles>
<profile>
<id>jdk8</id>
<id>nonJDK7</id>
<activation>
<jdk>1.8</jdk>
<jdk>[1.8,)</jdk>
</activation>
<properties>
<!-- Turn off JDK 8's lint checks: -->
<!-- Turn off JDK >= 8's lint checks: -->
<additionalparam>-Xdoclint:none</additionalparam>
</properties>
</profile>

View File

@ -25,9 +25,9 @@ package io.jsonwebtoken;
public interface CompressionCodec {
/**
* The algorithm name to use as the JWT's {@code calg} header value.
* The algorithm name to use as the JWT's {@code zip} header value.
*
* @return the algorithm name to use as the JWT's {@code calg} header value.
* @return the algorithm name to use as the JWT's {@code zip} header value.
*/
String getAlgorithmName();

View File

@ -16,7 +16,7 @@
package io.jsonwebtoken;
/**
* Looks for a JWT {@code calg} header, and if found, returns the corresponding {@link CompressionCodec} the parser
* Looks for a JWT {@code zip} header, and if found, returns the corresponding {@link CompressionCodec} the parser
* can use to decompress the JWT body.
*
* <p>JJWT's default {@link JwtParser} implementation supports both the
@ -34,12 +34,12 @@ package io.jsonwebtoken;
public interface CompressionCodecResolver {
/**
* Looks for a JWT {@code calg} header, and if found, returns the corresponding {@link CompressionCodec} the parser
* Looks for a JWT {@code zip} header, and if found, returns the corresponding {@link CompressionCodec} the parser
* can use to decompress the JWT body.
*
* @param header of the JWT
* @return CompressionCodec matching the {@code calg} header, or null if there is no {@code calg} header.
* @throws CompressionException if a {@code calg} header value is found and not supported.
* @return CompressionCodec matching the {@code zip} header, or null if there is no {@code zip} header.
* @throws CompressionException if a {@code zip} header value is found and not supported.
*/
CompressionCodec resolveCompressionCodec(Header header) throws CompressionException;

View File

@ -109,24 +109,24 @@ public interface Header<T extends Header<T>> extends Map<String,Object> {
T setContentType(String cty);
/**
* Returns the JWT <code>calg</code> (Compression Algorithm) header value or {@code null} if not present.
* Returns the JWT <code>zip</code> (Compression Algorithm) header value or {@code null} if not present.
*
* @return the {@code calg} header parameter value or {@code null} if not present.
* @return the {@code zip} header parameter value or {@code null} if not present.
* @since 0.6.0
*/
String getCompressionAlgorithm();
/**
* Sets the JWT <code>calg</code> (Compression Algorithm) header parameter value. A {@code null} value will remove
* Sets the JWT <code>zip</code> (Compression Algorithm) header parameter value. A {@code null} value will remove
* the property from the JSON map.
* <p>
* <p>The compression algorithm is NOT part of the <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25">JWT specification</a>
* and must be used carefully since, is not expected that other libraries (including previous versions of this one)
* be able to deserialize a compressed JTW body correctly. </p>
*
* @param calg the JWT compression algorithm {@code calg} value or {@code null} to remove the property from the JSON map.
* @param zip the JWT compression algorithm {@code zip} value or {@code null} to remove the property from the JSON map.
* @since 0.6.0
*/
T setCompressionAlgorithm(String calg);
T setCompressionAlgorithm(String zip);
}

View File

@ -15,6 +15,8 @@
*/
package io.jsonwebtoken;
import io.jsonwebtoken.codec.Encoder;
import java.security.Key;
import java.util.Date;
import java.util.Map;
@ -345,11 +347,36 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* <p>This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting
* byte array is used to invoke {@link #signWith(SignatureAlgorithm, byte[])}.</p>
*
* <h4>Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0</h4>
*
* <p>This method has been deprecated because the {@code key} argument for this method can be confusing: keys for
* cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were
* obtained from the String argument.</p>
*
* <p>This method always expected a String argument that was effectively the same as the result of the following
* (pseudocode):</p>
*
* <p>{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}</p>
*
* <p>However, a non-trivial number of JJWT users were confused by the method signature and attempted to
* use raw password strings as the key argument - for example {@code signWith(HS256, myPassword)} - which is
* almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.</p>
*
* <p>See this
* <a href="https://stackoverflow.com/questions/40252903/static-secret-as-byte-key-or-string/40274325#40274325">
* StackOverflow answer</a> explaining why raw (non-base64-encoded) strings are almost always incorrect for
* signature operations.</p>
*
* <p>Finally, please use the {@link #signWith(SignatureAlgorithm, Key)} method, as this method and the
* {@code byte[]} variant will be removed before the 1.0.0 release.</p>
*
* @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS.
* @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signing key to use to digitally sign the
* JWT.
* @return the builder for method chaining.
* @deprecated as of 0.10.0 - use {@link #signWith(SignatureAlgorithm, Key)} instead.
*/
@Deprecated
JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey);
/**
@ -387,6 +414,18 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
*/
JwtBuilder compressWith(CompressionCodec codec);
/**
* Perform Base64Url encoding with the specified Encoder.
*
* <p>JJWT uses a spec-compliant encoder that works on all supported JDK versions, but you may call this method
* to specify a different encoder if you desire.</p>
*
* @param base64UrlEncoder the encoder to use when Base64Url-encoding
* @return the builder for method chaining.
* @since 0.10.0
*/
JwtBuilder base64UrlEncodeWith(Encoder<byte[], String> base64UrlEncoder);
/**
* Actually builds the JWT and serializes it to a compact, URL-safe string according to the
* <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-7">JWT Compact Serialization</a>

View File

@ -15,6 +15,7 @@
*/
package io.jsonwebtoken;
import io.jsonwebtoken.codec.Decoder;
import io.jsonwebtoken.impl.DefaultClock;
import java.security.Key;
@ -164,20 +165,43 @@ public interface JwtParser {
/**
* Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not
* a JWS (no signature), this key is not used.
* <p>
*
* <p>Note that this key <em>MUST</em> be a valid key for the signature algorithm found in the JWT header
* (as the {@code alg} header parameter).</p>
* <p>
*
* <p>This method overwrites any previously set key.</p>
* <p>
*
* <p>This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting
* byte array is used to invoke {@link #setSigningKey(byte[])}.</p>
*
* @param base64EncodedKeyBytes the BASE64-encoded algorithm-specific signature verification key to use to validate
* <h4>Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0</h4>
*
* <p>This method has been deprecated because the {@code key} argument for this method can be confusing: keys for
* cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were
* obtained from the String argument.</p>
*
* <p>This method always expected a String argument that was effectively the same as the result of the following
* (pseudocode):</p>
*
* <p>{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}</p>
*
* <p>However, a non-trivial number of JJWT users were confused by the method signature and attempted to
* use raw password strings as the key argument - for example {@code setSigningKey(myPassword)} - which is
* almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.</p>
*
* <p>See this
* <a href="https://stackoverflow.com/questions/40252903/static-secret-as-byte-key-or-string/40274325#40274325">
* StackOverflow answer</a> explaining why raw (non-base64-encoded) strings are almost always incorrect for
* signature operations.</p>
*
* <p>Finally, please use the {@link #setSigningKey(Key) setSigningKey(Key)} instead, as this method and the
* {@code byte[]} variant will be removed before the 1.0.0 release.</p>
*
* @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signature verification key to use to validate
* any discovered JWS digital signature.
* @return the parser for method chaining.
*/
JwtParser setSigningKey(String base64EncodedKeyBytes);
JwtParser setSigningKey(String base64EncodedSecretKey);
/**
* Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not
@ -246,6 +270,18 @@ public interface JwtParser {
*/
JwtParser setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver);
/**
* Perform Base64Url decoding with the specified Decoder
*
* <p>JJWT uses a spec-compliant decoder that works on all supported JDK versions, but you may call this method
* to specify a different decoder if you desire.</p>
*
* @param base64UrlDecoder the decoder to use when Base64Url-decoding
* @return the parser for method chaining.
* @since 0.10.0
*/
JwtParser base64UrlDecodeWith(Decoder<String, byte[]> base64UrlDecoder);
/**
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
* otherwise.

View File

@ -0,0 +1,13 @@
package io.jsonwebtoken.codec;
import io.jsonwebtoken.JwtException;
/**
* @since 0.10.0
*/
public class CodecException extends JwtException {
public CodecException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,18 @@
package io.jsonwebtoken.codec;
import io.jsonwebtoken.codec.impl.Base64Decoder;
import io.jsonwebtoken.codec.impl.Base64UrlDecoder;
import io.jsonwebtoken.codec.impl.ExceptionPropagatingDecoder;
/**
* @param <T>
* @param <R>
* @since 0.10.0
*/
public interface Decoder<T, R> {
Decoder<String, byte[]> BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder());
Decoder<String, byte[]> BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder());
R decode(T t) throws DecodingException;
}

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.codec;
/**
* @since 0.10.0
*/
public class DecodingException extends CodecException {
public DecodingException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,19 @@
package io.jsonwebtoken.codec;
import io.jsonwebtoken.codec.impl.Base64Encoder;
import io.jsonwebtoken.codec.impl.Base64UrlEncoder;
import io.jsonwebtoken.codec.impl.ExceptionPropagatingEncoder;
/**
* @param <T>
* @param <R>
* @since 0.10.0
*/
public interface Encoder<T, R> {
Encoder<byte[], String> BASE64 = new ExceptionPropagatingEncoder<>(new Base64Encoder());
Encoder<byte[], String> BASE64URL = new ExceptionPropagatingEncoder<>(new Base64UrlEncoder());
R encode(T t) throws EncodingException;
}

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.codec;
/**
* @since 0.10.0
*/
public class EncodingException extends CodecException {
public EncodingException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,649 @@
package io.jsonwebtoken.codec.impl;
import java.util.Arrays;
/**
* A very fast and memory efficient class to encode and decode to and from BASE64 or BASE64URL in full accordance
* with <a href="https://tools.ietf.org/html/rfc4648">RFC 4648</a>.
*
* <p>Based initially on MigBase64 with continued modifications for Base64 URL support and JDK-standard code formatting.</p>
*
* <p>This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only
* allocates the resulting array. This produces less garbage and it is possible to handle arrays twice
* as large as algorithms that create a temporary array.</p>
*
* <p>There is also a "fast" version of all decode methods that works the same way as the normal ones, but
* has a few demands on the decoded input. Normally though, these fast versions should be used if the source if
* the input is known and it hasn't bee tampered with.</p>
*
* @author Mikael Grev
* @author Les Hazlewood
*/
@SuppressWarnings("Duplicates")
final class Base64 { //final and package-protected on purpose
private static final char[] BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
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];
static {
Arrays.fill(BASE64_IALPHABET, -1);
System.arraycopy(BASE64_IALPHABET, 0, BASE64URL_IALPHABET, 0, BASE64_IALPHABET.length);
for (int i = 0, iS = BASE64_ALPHABET.length; i < iS; i++) {
BASE64_IALPHABET[BASE64_ALPHABET[i]] = i;
BASE64URL_IALPHABET[BASE64URL_ALPHABET[i]] = i;
}
BASE64_IALPHABET['='] = 0;
BASE64URL_IALPHABET['='] = 0;
}
static final Base64 DEFAULT = new Base64(false);
static final Base64 URL_SAFE = new Base64(true);
private final boolean urlsafe;
private final char[] ALPHABET;
private final int[] IALPHABET;
private Base64(boolean urlsafe) {
this.urlsafe = urlsafe;
this.ALPHABET = urlsafe ? BASE64URL_ALPHABET : BASE64_ALPHABET;
this.IALPHABET = urlsafe ? BASE64URL_IALPHABET : BASE64_IALPHABET;
}
// ****************************************************************************************
// * char[] version
// ****************************************************************************************
/**
* Encodes a raw byte array into a BASE64 <code>char[]</code> representation in accordance with RFC 2045.
*
* @param sArr The bytes to convert. If <code>null</code> or length 0 an empty array will be returned.
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
* little faster.
* @return A BASE64 encoded array. Never <code>null</code>.
*/
private char[] encodeToChar(byte[] sArr, boolean lineSep) {
// Check special case
int sLen = sArr != null ? sArr.length : 0;
if (sLen == 0) {
return new char[0];
}
int eLen = (sLen / 3) * 3; // # of bytes that can encode evenly into 24-bit chunks
int left = sLen - eLen; // # of bytes that remain after 24-bit chunking. Always 0, 1 or 2
int cCnt = (((sLen - 1) / 3 + 1) << 2); // # of base64-encoded characters including padding
int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned char array with padding and any line separators
int padCount = 0;
if (left == 2) {
padCount = 1;
} else if (left == 1) {
padCount = 2;
}
char[] dArr = new char[urlsafe ? (dLen - padCount) : dLen];
// Encode even 24-bits
for (int s = 0, d = 0, cc = 0; s < eLen; ) {
// Copy next three bytes into lower 24 bits of int, paying attension to sign.
int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
// Encode the int into four chars
dArr[d++] = ALPHABET[(i >>> 18) & 0x3f];
dArr[d++] = ALPHABET[(i >>> 12) & 0x3f];
dArr[d++] = ALPHABET[(i >>> 6) & 0x3f];
dArr[d++] = ALPHABET[i & 0x3f];
// Add optional line separator
if (lineSep && ++cc == 19 && d < dLen - 2) {
dArr[d++] = '\r';
dArr[d++] = '\n';
cc = 0;
}
}
// Pad and encode last bits if source isn't even 24 bits.
if (left > 0) {
// Prepare the int
int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
// Set last four chars
dArr[dLen - 4] = ALPHABET[i >> 12];
dArr[dLen - 3] = ALPHABET[(i >>> 6) & 0x3f];
//dArr[dLen - 2] = left == 2 ? ALPHABET[i & 0x3f] : '=';
//dArr[dLen - 1] = '=';
if (left == 2) {
dArr[dLen - 2] = ALPHABET[i & 0x3f];
} else if (!urlsafe) { // if not urlsafe, we need to include the padding characters
dArr[dLen - 2] = '=';
}
if (!urlsafe) { // include padding
dArr[dLen - 1] = '=';
}
}
return dArr;
}
/*
* Decodes a BASE64 encoded char array. All illegal characters will be ignored and can handle both arrays with
* and without line separators.
*
* @param sArr The source array. <code>null</code> or length 0 will return an empty array.
* @return The decoded array of bytes. May be of length 0. Will be <code>null</code> if the legal characters
* (including '=') isn't divideable by 4. (I.e. definitely corrupted).
*
public final byte[] decode(char[] sArr) {
// Check special case
int sLen = sArr != null ? sArr.length : 0;
if (sLen == 0) {
return new byte[0];
}
// Count illegal characters (including '\r', '\n') to know what size the returned array will be,
// so we don't have to reallocate & copy it later.
int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
if (IALPHABET[sArr[i]] < 0) {
sepCnt++;
}
}
// Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
if ((sLen - sepCnt) % 4 != 0) {
return null;
}
int pad = 0;
for (int i = sLen; i > 1 && IALPHABET[sArr[--i]] <= 0; ) {
if (sArr[i] == '=') {
pad++;
}
}
int len = ((sLen - sepCnt) * 6 >> 3) - pad;
byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
for (int s = 0, d = 0; d < len; ) {
// Assemble three bytes into an int from four "valid" characters.
int i = 0;
for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
int c = IALPHABET[sArr[s++]];
if (c >= 0) {
i |= c << (18 - j * 6);
} else {
j--;
}
}
// Add the bytes
dArr[d++] = (byte) (i >> 16);
if (d < len) {
dArr[d++] = (byte) (i >> 8);
if (d < len) {
dArr[d++] = (byte) i;
}
}
}
return dArr;
}
*/
/**
* 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>
* + Line separator must be "\r\n", as specified in RFC 2045
* + The array must not contain illegal characters within the encoded string<br>
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
*
* @param sArr The source array. Length 0 will return an empty array. <code>null</code> will throw an exception.
* @return The decoded array of bytes. May be of length 0.
*/
final byte[] decodeFast(char[] sArr) {
// Check special case
int sLen = sArr != null ? sArr.length : 0;
if (sLen == 0) {
return new byte[0];
}
int sIx = 0, eIx = sLen - 1; // Start and end index after trimming.
// Trim illegal chars from start
while (sIx < eIx && IALPHABET[sArr[sIx]] < 0) {
sIx++;
}
// Trim illegal chars from end
while (eIx > 0 && IALPHABET[sArr[eIx]] < 0) {
eIx--;
}
// get the padding count (=) (0, 1 or 2)
int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end.
int cCnt = eIx - sIx + 1; // Content count including possible separators
int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
// Decode all but the last 0 - 2 bytes.
int d = 0;
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++]];
// Add the bytes
dArr[d++] = (byte) (i >> 16);
dArr[d++] = (byte) (i >> 8);
dArr[d++] = (byte) i;
// If line separator, jump over it.
if (sepCnt > 0 && ++cc == 19) {
sIx += 2;
cc = 0;
}
}
if (d < len) {
// 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);
}
for (int r = 16; d < len; r -= 8) {
dArr[d++] = (byte) (i >> r);
}
}
return dArr;
}
// ****************************************************************************************
// * byte[] version
// ****************************************************************************************
/*
* Encodes a raw byte array into a BASE64 <code>byte[]</code> representation i accordance with RFC 2045.
*
* @param sArr The bytes to convert. If <code>null</code> or length 0 an empty array will be returned.
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
* little faster.
* @return A BASE64 encoded array. Never <code>null</code>.
*
public final byte[] encodeToByte(byte[] sArr, boolean lineSep) {
return encodeToByte(sArr, 0, sArr != null ? sArr.length : 0, lineSep);
}
/**
* Encodes a raw byte array into a BASE64 <code>byte[]</code> representation i accordance with RFC 2045.
*
* @param sArr The bytes to convert. If <code>null</code> an empty array will be returned.
* @param sOff The starting position in the bytes to convert.
* @param sLen The number of bytes to convert. If 0 an empty array will be returned.
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
* little faster.
* @return A BASE64 encoded array. Never <code>null</code>.
*
public final byte[] encodeToByte(byte[] sArr, int sOff, int sLen, boolean lineSep) {
// Check special case
if (sArr == null || sLen == 0) {
return new byte[0];
}
int eLen = (sLen / 3) * 3; // Length of even 24-bits.
int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count
int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array
byte[] dArr = new byte[dLen];
// Encode even 24-bits
for (int s = sOff, d = 0, cc = 0; s < sOff + eLen; ) {
// Copy next three bytes into lower 24 bits of int, paying attension to sign.
int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
// Encode the int into four chars
dArr[d++] = (byte) ALPHABET[(i >>> 18) & 0x3f];
dArr[d++] = (byte) ALPHABET[(i >>> 12) & 0x3f];
dArr[d++] = (byte) ALPHABET[(i >>> 6) & 0x3f];
dArr[d++] = (byte) ALPHABET[i & 0x3f];
// Add optional line separator
if (lineSep && ++cc == 19 && d < dLen - 2) {
dArr[d++] = '\r';
dArr[d++] = '\n';
cc = 0;
}
}
// Pad and encode last bits if source isn't an even 24 bits.
int left = sLen - eLen; // 0 - 2.
if (left > 0) {
// Prepare the int
int i = ((sArr[sOff + eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sOff + sLen - 1] & 0xff) << 2) : 0);
// Set last four chars
dArr[dLen - 4] = (byte) ALPHABET[i >> 12];
dArr[dLen - 3] = (byte) ALPHABET[(i >>> 6) & 0x3f];
dArr[dLen - 2] = left == 2 ? (byte) ALPHABET[i & 0x3f] : (byte) '=';
dArr[dLen - 1] = '=';
}
return dArr;
}
/**
* Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with
* and without line separators.
*
* @param sArr The source array. Length 0 will return an empty array. <code>null</code> will throw an exception.
* @return The decoded array of bytes. May be of length 0. Will be <code>null</code> if the legal characters
* (including '=') isn't divideable by 4. (I.e. definitely corrupted).
*
public final byte[] decode(byte[] sArr) {
return decode(sArr, 0, sArr.length);
}
/**
* Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with
* and without line separators.
*
* @param sArr The source array. <code>null</code> will throw an exception.
* @param sOff The starting position in the source array.
* @param sLen The number of bytes to decode from the source array. Length 0 will return an empty array.
* @return The decoded array of bytes. May be of length 0. Will be <code>null</code> if the legal characters
* (including '=') isn't divideable by 4. (I.e. definitely corrupted).
*
public final byte[] decode(byte[] sArr, int sOff, int sLen) {
// Count illegal characters (including '\r', '\n') to know what size the returned array will be,
// so we don't have to reallocate & copy it later.
int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
if (IALPHABET[sArr[sOff + i] & 0xff] < 0) {
sepCnt++;
}
}
// Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
if ((sLen - sepCnt) % 4 != 0) {
return null;
}
int pad = 0;
for (int i = sLen; i > 1 && IALPHABET[sArr[sOff + --i] & 0xff] <= 0; ) {
if (sArr[sOff + i] == '=') {
pad++;
}
}
int len = ((sLen - sepCnt) * 6 >> 3) - pad;
byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
for (int s = 0, d = 0; d < len; ) {
// Assemble three bytes into an int from four "valid" characters.
int i = 0;
for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
int c = IALPHABET[sArr[sOff + s++] & 0xff];
if (c >= 0) {
i |= c << (18 - j * 6);
} else {
j--;
}
}
// Add the bytes
dArr[d++] = (byte) (i >> 16);
if (d < len) {
dArr[d++] = (byte) (i >> 8);
if (d < len) {
dArr[d++] = (byte) i;
}
}
}
return dArr;
}
/*
* Decodes a BASE64 encoded byte array that is known to be resonably well formatted. The method is about twice as
* fast as {@link #decode(byte[])}. The preconditions are:<br>
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
* + Line separator must be "\r\n", as specified in RFC 2045
* + The array must not contain illegal characters within the encoded string<br>
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
*
* @param sArr The source array. Length 0 will return an empty array. <code>null</code> will throw an exception.
* @return The decoded array of bytes. May be of length 0.
*
public final byte[] decodeFast(byte[] sArr) {
// Check special case
int sLen = sArr.length;
if (sLen == 0) {
return new byte[0];
}
int sIx = 0, eIx = sLen - 1; // Start and end index after trimming.
// Trim illegal chars from start
while (sIx < eIx && IALPHABET[sArr[sIx] & 0xff] < 0) {
sIx++;
}
// Trim illegal chars from end
while (eIx > 0 && IALPHABET[sArr[eIx] & 0xff] < 0) {
eIx--;
}
// get the padding count (=) (0, 1 or 2)
int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end.
int cCnt = eIx - sIx + 1; // Content count including possible separators
int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
// Decode all but the last 0 - 2 bytes.
int d = 0;
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++]];
// Add the bytes
dArr[d++] = (byte) (i >> 16);
dArr[d++] = (byte) (i >> 8);
dArr[d++] = (byte) i;
// If line separator, jump over it.
if (sepCnt > 0 && ++cc == 19) {
sIx += 2;
cc = 0;
}
}
if (d < len) {
// 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);
}
for (int r = 16; d < len; r -= 8) {
dArr[d++] = (byte) (i >> r);
}
}
return dArr;
}
*/
// ****************************************************************************************
// * String version
// ****************************************************************************************
/**
* Encodes a raw byte array into a BASE64 <code>String</code> representation i accordance with RFC 2045.
*
* @param sArr The bytes to convert. If <code>null</code> or length 0 an empty array will be returned.
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
* little faster.
* @return A BASE64 encoded array. Never <code>null</code>.
*/
final String encodeToString(byte[] sArr, boolean lineSep) {
// Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower.
return new String(encodeToChar(sArr, lineSep));
}
/*
* Decodes a BASE64 encoded <code>String</code>. All illegal characters will be ignored and can handle both strings with
* and without line separators.<br>
* <b>Note!</b> It can be up to about 2x the speed to call <code>decode(str.toCharArray())</code> instead. That
* will create a temporary array though. This version will use <code>str.charAt(i)</code> to iterate the string.
*
* @param str The source string. <code>null</code> or length 0 will return an empty array.
* @return The decoded array of bytes. May be of length 0. Will be <code>null</code> if the legal characters
* (including '=') isn't divideable by 4. (I.e. definitely corrupted).
*
public final byte[] decode(String str) {
// Check special case
int sLen = str != null ? str.length() : 0;
if (sLen == 0) {
return new byte[0];
}
// Count illegal characters (including '\r', '\n') to know what size the returned array will be,
// so we don't have to reallocate & copy it later.
int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
for (int i = 0; i < sLen; i++) { // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
if (IALPHABET[str.charAt(i)] < 0) {
sepCnt++;
}
}
// Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
if ((sLen - sepCnt) % 4 != 0) {
return null;
}
// Count '=' at end
int pad = 0;
for (int i = sLen; i > 1 && IALPHABET[str.charAt(--i)] <= 0; ) {
if (str.charAt(i) == '=') {
pad++;
}
}
int len = ((sLen - sepCnt) * 6 >> 3) - pad;
byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
for (int s = 0, d = 0; d < len; ) {
// Assemble three bytes into an int from four "valid" characters.
int i = 0;
for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
int c = IALPHABET[str.charAt(s++)];
if (c >= 0) {
i |= c << (18 - j * 6);
} else {
j--;
}
}
// Add the bytes
dArr[d++] = (byte) (i >> 16);
if (d < len) {
dArr[d++] = (byte) (i >> 8);
if (d < len) {
dArr[d++] = (byte) i;
}
}
}
return dArr;
}
/**
* Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as
* fast as {@link #decode(String)}. The preconditions are:<br>
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
* + Line separator must be "\r\n", as specified in RFC 2045
* + The array must not contain illegal characters within the encoded string<br>
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
*
* @param s The source string. Length 0 will return an empty array. <code>null</code> will throw an exception.
* @return The decoded array of bytes. May be of length 0.
*
public final byte[] decodeFast(String s) {
// Check special case
int sLen = s.length();
if (sLen == 0) {
return new byte[0];
}
int sIx = 0, eIx = sLen - 1; // Start and end index after trimming.
// Trim illegal chars from start
while (sIx < eIx && IALPHABET[s.charAt(sIx) & 0xff] < 0) {
sIx++;
}
// Trim illegal chars from end
while (eIx > 0 && IALPHABET[s.charAt(eIx) & 0xff] < 0) {
eIx--;
}
// get the padding count (=) (0, 1 or 2)
int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end.
int cCnt = eIx - sIx + 1; // Content count including possible separators
int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0;
int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
// Decode all but the last 0 - 2 bytes.
int d = 0;
for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) {
// Assemble three bytes into an int from four "valid" characters.
int i = IALPHABET[s.charAt(sIx++)] << 18 | IALPHABET[s.charAt(sIx++)] << 12 | IALPHABET[s.charAt(sIx++)] << 6 | IALPHABET[s.charAt(sIx++)];
// Add the bytes
dArr[d++] = (byte) (i >> 16);
dArr[d++] = (byte) (i >> 8);
dArr[d++] = (byte) i;
// If line separator, jump over it.
if (sepCnt > 0 && ++cc == 19) {
sIx += 2;
cc = 0;
}
}
if (d < len) {
// Decode last 1-3 bytes (incl '=') into 1-3 bytes
int i = 0;
for (int j = 0; sIx <= eIx - pad; j++) {
i |= IALPHABET[s.charAt(sIx++)] << (18 - j * 6);
}
for (int r = 16; d < len; r -= 8) {
dArr[d++] = (byte) (i >> r);
}
}
return dArr;
}
*/
}

View File

@ -0,0 +1,22 @@
package io.jsonwebtoken.codec.impl;
import io.jsonwebtoken.codec.Decoder;
import io.jsonwebtoken.codec.DecodingException;
import io.jsonwebtoken.lang.Assert;
public class Base64Decoder extends Base64Support implements Decoder<String, byte[]> {
public Base64Decoder() {
super(Base64.DEFAULT);
}
Base64Decoder(Base64 base64) {
super(base64);
}
@Override
public byte[] decode(String s) throws DecodingException {
Assert.notNull(s, "String argument cannot be null");
return this.base64.decodeFast(s.toCharArray());
}
}

View File

@ -0,0 +1,22 @@
package io.jsonwebtoken.codec.impl;
import io.jsonwebtoken.codec.Encoder;
import io.jsonwebtoken.codec.EncodingException;
import io.jsonwebtoken.lang.Assert;
public class Base64Encoder extends Base64Support implements Encoder<byte[], String> {
public Base64Encoder() {
super(Base64.DEFAULT);
}
Base64Encoder(Base64 base64) {
super(base64);
}
@Override
public String encode(byte[] bytes) throws EncodingException {
Assert.notNull(bytes, "byte array argument cannot be null");
return this.base64.encodeToString(bytes, false);
}
}

View File

@ -0,0 +1,13 @@
package io.jsonwebtoken.codec.impl;
import io.jsonwebtoken.lang.Assert;
public class Base64Support {
protected final Base64 base64;
Base64Support(Base64 base64) {
Assert.notNull(base64, "Base64 argument cannot be null");
this.base64 = base64;
}
}

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.codec.impl;
/**
* @since 0.10.0
*/
public class Base64UrlDecoder extends Base64Decoder {
public Base64UrlDecoder() {
super(Base64.URL_SAFE);
}
}

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.codec.impl;
/**
* @since 0.10.0
*/
public class Base64UrlEncoder extends Base64Encoder {
public Base64UrlEncoder() {
super(Base64.URL_SAFE);
}
}

View File

@ -0,0 +1,28 @@
package io.jsonwebtoken.codec.impl;
import io.jsonwebtoken.codec.Decoder;
import io.jsonwebtoken.codec.DecodingException;
import io.jsonwebtoken.lang.Assert;
public class ExceptionPropagatingDecoder<T, R> implements Decoder<T, R> {
private final Decoder<T, R> decoder;
public ExceptionPropagatingDecoder(Decoder<T, R> decoder) {
Assert.notNull(decoder, "Decoder cannot be null.");
this.decoder = decoder;
}
@Override
public R decode(T t) throws DecodingException {
Assert.notNull(t, "Decode argument cannot be null.");
try {
return decoder.decode(t);
} catch (DecodingException e) {
throw e; //propagate
} catch (Exception e) {
String msg = "Unable to decode input: " + e.getMessage();
throw new DecodingException(msg, e);
}
}
}

View File

@ -0,0 +1,28 @@
package io.jsonwebtoken.codec.impl;
import io.jsonwebtoken.codec.Encoder;
import io.jsonwebtoken.codec.EncodingException;
import io.jsonwebtoken.lang.Assert;
public class ExceptionPropagatingEncoder<T,R> implements Encoder<T,R> {
private final Encoder<T,R> encoder;
public ExceptionPropagatingEncoder(Encoder<T,R> encoder) {
Assert.notNull(encoder, "Encoder cannot be null.");
this.encoder = encoder;
}
@Override
public R encode(T t) throws EncodingException {
Assert.notNull(t, "Encode argument cannot be null.");
try {
return this.encoder.encode(t);
} catch (EncodingException e) {
throw e; //propagate
} catch (Exception e) {
String msg = "Unable to encode input: " + e.getMessage();
throw new EncodingException(msg, e);
}
}
}

View File

@ -15,13 +15,19 @@
*/
package io.jsonwebtoken.impl;
import io.jsonwebtoken.codec.Decoder;
import io.jsonwebtoken.codec.Encoder;
import io.jsonwebtoken.lang.Assert;
import java.nio.charset.Charset;
/**
* @deprecated since 0.10.0 - will be removed before 1.0.0. Use {@link Encoder} orr {@link Decoder} instead.
*/
@Deprecated
public abstract class AbstractTextCodec implements TextCodec {
protected static final Charset UTF8 = Charset.forName("UTF-8");
protected static final Charset UTF8 = Charset.forName("UTF-8");
protected static final Charset US_ASCII = Charset.forName("US-ASCII");
@Override

View File

@ -15,6 +15,14 @@
*/
package io.jsonwebtoken.impl;
import io.jsonwebtoken.codec.Decoder;
import io.jsonwebtoken.codec.Encoder;
/**
* @deprecated since 0.10.0 - will be removed before 1.0.0. Use {@link Encoder#BASE64 Encoder.BASE64}
* or {@link Decoder#BASE64 Decoder.BASE64} instead.
*/
@Deprecated
public class AndroidBase64Codec extends AbstractTextCodec {
@Override

View File

@ -15,14 +15,22 @@
*/
package io.jsonwebtoken.impl;
import io.jsonwebtoken.codec.Decoder;
import io.jsonwebtoken.codec.Encoder;
/**
* @deprecated since 0.10.0 - will be removed before 1.0.0. Use {@link Encoder#BASE64 Encoder.BASE64}
* or {@link Decoder#BASE64 Decoder.BASE64} instead.
*/
@Deprecated
public class Base64Codec extends AbstractTextCodec {
public String encode(byte[] data) {
return javax.xml.bind.DatatypeConverter.printBase64Binary(data);
return Encoder.BASE64.encode(data);
}
@Override
public byte[] decode(String encoded) {
return javax.xml.bind.DatatypeConverter.parseBase64Binary(encoded);
return Decoder.BASE64.decode(encoded);
}
}

View File

@ -15,90 +15,23 @@
*/
package io.jsonwebtoken.impl;
import io.jsonwebtoken.codec.Decoder;
import io.jsonwebtoken.codec.Encoder;
/**
* @deprecated since 0.10.0 - will be removed before 1.0.0. Use {@link Encoder#BASE64URL Encoder.BASE64URL}
* or {@link Decoder#BASE64URL Decoder.BASE64URL} instead.
*/
@Deprecated
public class Base64UrlCodec extends AbstractTextCodec {
@Override
public String encode(byte[] data) {
String base64Text = TextCodec.BASE64.encode(data);
byte[] bytes = base64Text.getBytes(US_ASCII);
//base64url encoding doesn't use padding chars:
bytes = removePadding(bytes);
//replace URL-unfriendly Base64 chars to url-friendly ones:
for (int i = 0; i < bytes.length; i++) {
if (bytes[i] == '+') {
bytes[i] = '-';
} else if (bytes[i] == '/') {
bytes[i] = '_';
}
}
return new String(bytes, US_ASCII);
}
protected byte[] removePadding(byte[] bytes) {
byte[] result = bytes;
int paddingCount = 0;
for (int i = bytes.length - 1; i > 0; i--) {
if (bytes[i] == '=') {
paddingCount++;
} else {
break;
}
}
if (paddingCount > 0) {
result = new byte[bytes.length - paddingCount];
System.arraycopy(bytes, 0, result, 0, bytes.length - paddingCount);
}
return result;
return Encoder.BASE64URL.encode(data);
}
@Override
public byte[] decode(String encoded) {
char[] chars = encoded.toCharArray(); //always ASCII - one char == 1 byte
//Base64 requires padding to be in place before decoding, so add it if necessary:
chars = ensurePadding(chars);
//Replace url-friendly chars back to normal Base64 chars:
for (int i = 0; i < chars.length; i++) {
if (chars[i] == '-') {
chars[i] = '+';
} else if (chars[i] == '_') {
chars[i] = '/';
}
}
String base64Text = new String(chars);
return TextCodec.BASE64.decode(base64Text);
return Decoder.BASE64URL.decode(encoded);
}
protected char[] ensurePadding(char[] chars) {
char[] result = chars; //assume argument in case no padding is necessary
int paddingCount = 0;
//fix for https://github.com/jwtk/jjwt/issues/31
int remainder = chars.length % 4;
if (remainder == 2 || remainder == 3) {
paddingCount = 4 - remainder;
}
if (paddingCount > 0) {
result = new char[chars.length + paddingCount];
System.arraycopy(chars, 0, result, 0, chars.length);
for (int i = 0; i < paddingCount; i++) {
result[chars.length + i] = '=';
}
}
return result;
}
}

View File

@ -17,12 +17,20 @@ package io.jsonwebtoken.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.*;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.codec.Decoder;
import io.jsonwebtoken.codec.Encoder;
import io.jsonwebtoken.impl.crypto.DefaultJwtSigner;
import io.jsonwebtoken.impl.crypto.JwtSigner;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Strings;
import javax.crypto.spec.SecretKeySpec;
@ -39,11 +47,19 @@ public class DefaultJwtBuilder implements JwtBuilder {
private String payload;
private SignatureAlgorithm algorithm;
private Key key;
private byte[] keyBytes;
private Key key;
private Encoder<byte[], String> base64UrlEncoder = Encoder.BASE64URL;
private CompressionCodec compressionCodec;
@Override
public JwtBuilder base64UrlEncodeWith(Encoder<byte[], String> base64UrlEncoder) {
Assert.notNull(base64UrlEncoder, "base64UrlEncoder cannot be null.");
this.base64UrlEncoder = base64UrlEncoder;
return this;
}
@Override
public JwtBuilder setHeader(Header header) {
this.header = header;
@ -88,7 +104,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
Assert.notEmpty(secretKey, "secret key byte array cannot be null or empty.");
Assert.isTrue(alg.isHmac(), "Key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.");
this.algorithm = alg;
this.keyBytes = secretKey;
this.key = new SecretKeySpec(secretKey, alg.getJcaName());
return this;
}
@ -96,7 +112,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
public JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) {
Assert.hasText(base64EncodedSecretKey, "base64-encoded secret key cannot be null or empty.");
Assert.isTrue(alg.isHmac(), "Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.");
byte[] bytes = TextCodec.BASE64.decode(base64EncodedSecretKey);
byte[] bytes = Decoder.BASE64.decode(base64EncodedSecretKey);
return signWith(alg, bytes);
}
@ -262,22 +278,13 @@ public class DefaultJwtBuilder implements JwtBuilder {
throw new IllegalStateException("Both 'payload' and 'claims' cannot both be specified. Choose either one.");
}
if (key != null && keyBytes != null) {
throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either one.");
}
Header header = ensureHeader();
Key key = this.key;
if (key == null && !Objects.isEmpty(keyBytes)) {
key = new SecretKeySpec(keyBytes, algorithm.getJcaName());
}
JwsHeader jwsHeader;
if (header instanceof JwsHeader) {
jwsHeader = (JwsHeader)header;
jwsHeader = (JwsHeader) header;
} else {
//noinspection unchecked
jwsHeader = new DefaultJwsHeader(header);
}
@ -294,25 +301,19 @@ public class DefaultJwtBuilder implements JwtBuilder {
String base64UrlEncodedHeader = base64UrlEncode(jwsHeader, "Unable to serialize header to json.");
String base64UrlEncodedBody;
byte[] bytes;
try {
bytes = this.payload != null ? payload.getBytes(Strings.UTF_8) : toJson(claims);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Unable to serialize claims object to json.");
}
if (compressionCodec != null) {
byte[] bytes;
try {
bytes = this.payload != null ? payload.getBytes(Strings.UTF_8) : toJson(claims);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Unable to serialize claims object to json.");
}
base64UrlEncodedBody = TextCodec.BASE64URL.encode(compressionCodec.compress(bytes));
} else {
base64UrlEncodedBody = this.payload != null ?
TextCodec.BASE64URL.encode(this.payload) :
base64UrlEncode(claims, "Unable to serialize claims object to json.");
bytes = compressionCodec.compress(bytes);
}
String base64UrlEncodedBody = base64UrlEncoder.encode(bytes);
String jwt = base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + base64UrlEncodedBody;
if (key != null) { //jwt must be signed:
@ -335,7 +336,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
* @since 0.5 mostly to allow testing overrides
*/
protected JwtSigner createSigner(SignatureAlgorithm alg, Key key) {
return new DefaultJwtSigner(alg, key);
return new DefaultJwtSigner(alg, key, base64UrlEncoder);
}
protected String base64UrlEncode(Object o, String errMsg) {
@ -346,11 +347,10 @@ public class DefaultJwtBuilder implements JwtBuilder {
throw new IllegalStateException(errMsg, e);
}
return TextCodec.BASE64URL.encode(bytes);
return base64UrlEncoder.encode(bytes);
}
protected byte[] toJson(Object object) throws JsonProcessingException {
protected byte[] toJson(Object object) throws JsonProcessingException {
return OBJECT_MAPPER.writeValueAsBytes(object);
}
}

View File

@ -38,6 +38,7 @@ import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.codec.Decoder;
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
@ -69,12 +70,21 @@ public class DefaultJwtParser implements JwtParser {
private CompressionCodecResolver compressionCodecResolver = new DefaultCompressionCodecResolver();
Claims expectedClaims = new DefaultClaims();
private Decoder<String, byte[]> base64UrlDecoder = Decoder.BASE64URL;
private Claims expectedClaims = new DefaultClaims();
private Clock clock = DefaultClock.INSTANCE;
private long allowedClockSkewMillis = 0;
@Override
public JwtParser base64UrlDecodeWith(Decoder<String, byte[]> base64UrlDecoder) {
Assert.notNull(base64UrlDecoder, "base64UrlDecoder cannot be null.");
this.base64UrlDecoder = base64UrlDecoder;
return this;
}
@Override
public JwtParser requireIssuedAt(Date issuedAt) {
expectedClaims.setIssuedAt(issuedAt);
@ -146,9 +156,9 @@ public class DefaultJwtParser implements JwtParser {
}
@Override
public JwtParser setSigningKey(String base64EncodedKeyBytes) {
Assert.hasText(base64EncodedKeyBytes, "signing key cannot be null or empty.");
this.keyBytes = TextCodec.BASE64.decode(base64EncodedKeyBytes);
public JwtParser setSigningKey(String base64EncodedSecretKey) {
Assert.hasText(base64EncodedSecretKey, "signing key cannot be null or empty.");
this.keyBytes = Decoder.BASE64.decode(base64EncodedSecretKey);
return this;
}
@ -215,7 +225,7 @@ public class DefaultJwtParser implements JwtParser {
if (c == SEPARATOR_CHAR) {
CharSequence tokenSeq = Strings.clean(sb);
String token = tokenSeq!=null?tokenSeq.toString():null;
String token = tokenSeq != null ? tokenSeq.toString() : null;
if (delimiterCount == 0) {
base64UrlEncodedHeader = token;
@ -248,7 +258,8 @@ public class DefaultJwtParser implements JwtParser {
CompressionCodec compressionCodec = null;
if (base64UrlEncodedHeader != null) {
String origValue = TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);
byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedHeader);
String origValue = new String(bytes, Strings.UTF_8);
Map<String, Object> m = readValue(origValue);
if (base64UrlEncodedDigest != null) {
@ -261,13 +272,11 @@ public class DefaultJwtParser implements JwtParser {
}
// =============== Body =================
String payload;
byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedPayload);
if (compressionCodec != null) {
byte[] decompressed = compressionCodec.decompress(TextCodec.BASE64URL.decode(base64UrlEncodedPayload));
payload = new String(decompressed, Strings.UTF_8);
} else {
payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
bytes = compressionCodec.decompress(bytes);
}
String payload = new String(bytes, Strings.UTF_8);
Claims claims = null;
@ -293,7 +302,7 @@ public class DefaultJwtParser implements JwtParser {
if (algorithm == null || algorithm == SignatureAlgorithm.NONE) {
//it is plaintext, but it has a signature. This is invalid:
String msg = "JWT string has a digest/signature, but the header does not reference a valid signature " +
"algorithm.";
"algorithm.";
throw new MalformedJwtException(msg);
}
@ -322,7 +331,7 @@ public class DefaultJwtParser implements JwtParser {
if (!Objects.isEmpty(keyBytes)) {
Assert.isTrue(algorithm.isHmac(),
"Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance.");
"Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance.");
key = new SecretKeySpec(keyBytes, algorithm.getJcaName());
}
@ -338,19 +347,19 @@ public class DefaultJwtParser implements JwtParser {
validator = createSignatureValidator(algorithm, key);
} catch (IllegalArgumentException e) {
String algName = algorithm.getValue();
String msg = "The parsed JWT indicates it was signed with the " + algName + " signature " +
"algorithm, but the specified signing key of type " + key.getClass().getName() +
" may not be used to validate " + algName + " signatures. Because the specified " +
"signing key reflects a specific and expected algorithm, and the JWT does not reflect " +
"this algorithm, it is likely that the JWT was not expected and therefore should not be " +
"trusted. Another possibility is that the parser was configured with the incorrect " +
"signing key, but this cannot be assumed for security reasons.";
String msg = "The parsed JWT indicates it was signed with the " + algName + " signature " +
"algorithm, but the specified signing key of type " + key.getClass().getName() +
" may not be used to validate " + algName + " signatures. Because the specified " +
"signing key reflects a specific and expected algorithm, and the JWT does not reflect " +
"this algorithm, it is likely that the JWT was not expected and therefore should not be " +
"trusted. Another possibility is that the parser was configured with the incorrect " +
"signing key, but this cannot be assumed for security reasons.";
throw new UnsupportedJwtException(msg, e);
}
if (!validator.isValid(jwtWithoutSignature, base64UrlEncodedDigest)) {
String msg = "JWT signature does not match locally computed signature. JWT validity cannot be " +
"asserted and should not be trusted.";
"asserted and should not be trusted.";
throw new SignatureException(msg);
}
}
@ -426,34 +435,31 @@ public class DefaultJwtParser implements JwtParser {
Object expectedClaimValue = expectedClaims.get(expectedClaimName);
Object actualClaimValue = claims.get(expectedClaimName);
if (
Claims.ISSUED_AT.equals(expectedClaimName) ||
Claims.EXPIRATION.equals(expectedClaimName) ||
Claims.NOT_BEFORE.equals(expectedClaimName)
) {
if (Claims.ISSUED_AT.equals(expectedClaimName) || Claims.EXPIRATION.equals(expectedClaimName) ||
Claims.NOT_BEFORE.equals(expectedClaimName)) {
expectedClaimValue = expectedClaims.get(expectedClaimName, Date.class);
actualClaimValue = claims.get(expectedClaimName, Date.class);
} else if (
expectedClaimValue instanceof Date &&
actualClaimValue != null &&
actualClaimValue instanceof Long
) {
actualClaimValue = new Date((Long)actualClaimValue);
} else if (expectedClaimValue instanceof Date && actualClaimValue instanceof Long) {
actualClaimValue = new Date((Long) actualClaimValue);
}
InvalidClaimException invalidClaimException = null;
if (actualClaimValue == null) {
String msg = String.format(
ClaimJwtException.MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE,
expectedClaimName, expectedClaimValue
);
String msg = String.format(ClaimJwtException.MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE,
expectedClaimName, expectedClaimValue);
invalidClaimException = new MissingClaimException(header, claims, msg);
} else if (!expectedClaimValue.equals(actualClaimValue)) {
String msg = String.format(
ClaimJwtException.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE,
expectedClaimName, expectedClaimValue, actualClaimValue
);
String msg = String.format(ClaimJwtException.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE,
expectedClaimName, expectedClaimValue, actualClaimValue);
invalidClaimException = new IncorrectClaimException(header, claims, msg);
}
@ -469,7 +475,7 @@ public class DefaultJwtParser implements JwtParser {
* @since 0.5 mostly to allow testing overrides
*/
protected JwtSignatureValidator createSignatureValidator(SignatureAlgorithm alg, Key key) {
return new DefaultJwtSignatureValidator(alg, key);
return new DefaultJwtSignatureValidator(alg, key, base64UrlDecoder);
}
@Override

View File

@ -15,6 +15,10 @@
*/
package io.jsonwebtoken.impl;
/**
* @deprecated since 0.10.0
*/
@Deprecated
public class DefaultTextCodecFactory implements TextCodecFactory {
protected String getSystemProperty(String key) {

View File

@ -15,10 +15,26 @@
*/
package io.jsonwebtoken.impl;
/**
* @deprecated since 0.10.0. Use an {@link io.jsonwebtoken.codec.Encoder} or {@link io.jsonwebtoken.codec.Decoder}
* as needed. This class will be removed before 1.0.0
*/
@Deprecated
public interface TextCodec {
public static final TextCodec BASE64 = new DefaultTextCodecFactory().getTextCodec();
public static final TextCodec BASE64URL = new Base64UrlCodec();
/**
* @deprecated since 0.10.0. Use {@link io.jsonwebtoken.codec.Encoder#BASE64 Encoder.BASE64} or
* {@link io.jsonwebtoken.codec.Decoder#BASE64 Decoder.BASE64} instead. This class will be removed before 1.0.0
*/
@Deprecated
TextCodec BASE64 = new Base64Codec();
/**
* @deprecated since 0.10.0. Use {@link io.jsonwebtoken.codec.Encoder#BASE64URL Encoder.BASE64URL} or
* {@link io.jsonwebtoken.codec.Decoder#BASE64URL Decoder.BASE64URL} instead. This class will be removed before 1.0.0
*/
@Deprecated
TextCodec BASE64URL = new Base64UrlCodec();
String encode(String data);

View File

@ -15,6 +15,10 @@
*/
package io.jsonwebtoken.impl;
/**
* @deprecated since 0.10.0
*/
@Deprecated
public interface TextCodecFactory {
TextCodec getTextCodec();

View File

@ -26,11 +26,11 @@ import io.jsonwebtoken.lang.Strings;
* Default implementation of {@link CompressionCodecResolver} that supports the following:
* <p>
* <ul>
* <li>If the specified JWT {@link Header} does not have a {@code calg} header, this implementation does
* <li>If the specified JWT {@link Header} does not have a {@code zip} header, this implementation does
* nothing and returns {@code null} to the caller, indicating no compression was used.</li>
* <li>If the header has a {@code calg} value of {@code DEF}, a {@link DeflateCompressionCodec} will be returned.</li>
* <li>If the header has a {@code calg} value of {@code GZIP}, a {@link GzipCompressionCodec} will be returned.</li>
* <li>If the header has any other {@code calg} value, a {@link CompressionException} is thrown to reflect an
* <li>If the header has a {@code zip} value of {@code DEF}, a {@link DeflateCompressionCodec} will be returned.</li>
* <li>If the header has a {@code zip} value of {@code GZIP}, a {@link GzipCompressionCodec} will be returned.</li>
* <li>If the header has any other {@code zip} value, a {@link CompressionException} is thrown to reflect an
* unrecognized algorithm.</li>
* </ul>
*

View File

@ -16,7 +16,7 @@
package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.TextCodec;
import io.jsonwebtoken.codec.Decoder;
import io.jsonwebtoken.lang.Assert;
import java.nio.charset.Charset;
@ -27,14 +27,27 @@ public class DefaultJwtSignatureValidator implements JwtSignatureValidator {
private static final Charset US_ASCII = Charset.forName("US-ASCII");
private final SignatureValidator signatureValidator;
private final Decoder<String, byte[]> base64UrlDecoder;
@Deprecated
public DefaultJwtSignatureValidator(SignatureAlgorithm alg, Key key) {
this(DefaultSignatureValidatorFactory.INSTANCE, alg, key);
this(DefaultSignatureValidatorFactory.INSTANCE, alg, key, Decoder.BASE64URL);
}
public DefaultJwtSignatureValidator(SignatureAlgorithm alg, Key key, Decoder<String, byte[]> base64UrlDecoder) {
this(DefaultSignatureValidatorFactory.INSTANCE, alg, key, base64UrlDecoder);
}
@Deprecated
public DefaultJwtSignatureValidator(SignatureValidatorFactory factory, SignatureAlgorithm alg, Key key) {
this(factory, alg, key, Decoder.BASE64URL);
}
public DefaultJwtSignatureValidator(SignatureValidatorFactory factory, SignatureAlgorithm alg, Key key, Decoder<String, byte[]> base64UrlDecoder) {
Assert.notNull(factory, "SignerFactory argument cannot be null.");
Assert.notNull(base64UrlDecoder, "Base64Url decoder argument cannot be null.");
this.signatureValidator = factory.createSignatureValidator(alg, key);
this.base64UrlDecoder = base64UrlDecoder;
}
@Override
@ -42,7 +55,7 @@ public class DefaultJwtSignatureValidator implements JwtSignatureValidator {
byte[] data = jwtWithoutSignature.getBytes(US_ASCII);
byte[] signature = TextCodec.BASE64URL.decode(base64UrlEncodedSignature);
byte[] signature = base64UrlDecoder.decode(base64UrlEncodedSignature);
return this.signatureValidator.isValid(data, signature);
}

View File

@ -16,7 +16,8 @@
package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.TextCodec;
import io.jsonwebtoken.codec.Encoder;
import io.jsonwebtoken.codec.impl.Base64UrlEncoder;
import io.jsonwebtoken.lang.Assert;
import java.nio.charset.Charset;
@ -27,13 +28,26 @@ public class DefaultJwtSigner implements JwtSigner {
private static final Charset US_ASCII = Charset.forName("US-ASCII");
private final Signer signer;
private final Encoder<byte[], String> base64UrlEncoder;
@Deprecated
public DefaultJwtSigner(SignatureAlgorithm alg, Key key) {
this(DefaultSignerFactory.INSTANCE, alg, key);
this(DefaultSignerFactory.INSTANCE, alg, key, Encoder.BASE64URL);
}
public DefaultJwtSigner(SignatureAlgorithm alg, Key key, Encoder<byte[], String> base64UrlEncoder) {
this(DefaultSignerFactory.INSTANCE, alg, key, base64UrlEncoder);
}
@Deprecated
public DefaultJwtSigner(SignerFactory factory, SignatureAlgorithm alg, Key key) {
this(factory, alg, key, Encoder.BASE64URL);
}
public DefaultJwtSigner(SignerFactory factory, SignatureAlgorithm alg, Key key, Encoder<byte[], String> base64UrlEncoder) {
Assert.notNull(factory, "SignerFactory argument cannot be null.");
Assert.notNull(base64UrlEncoder, "Base64Url Encoder cannot be null.");
this.base64UrlEncoder = base64UrlEncoder;
this.signer = factory.createSigner(alg, key);
}
@ -44,6 +58,6 @@ public class DefaultJwtSigner implements JwtSigner {
byte[] signature = signer.sign(bytesToSign);
return TextCodec.BASE64URL.encode(signature);
return base64UrlEncoder.encode(signature);
}
}

View File

@ -15,9 +15,10 @@
*/
package io.jsonwebtoken
import io.jsonwebtoken.codec.Encoder
import io.jsonwebtoken.impl.DefaultClock
import io.jsonwebtoken.impl.FixedClock
import io.jsonwebtoken.impl.TextCodec
import io.jsonwebtoken.lang.Strings
import org.junit.Test
import javax.crypto.spec.SecretKeySpec
@ -38,6 +39,11 @@ class JwtParserTest {
return key
}
protected static String base64Url(String s) {
byte[] bytes = s.getBytes(Strings.UTF_8)
return Encoder.BASE64URL.encode(bytes)
}
@Test
void testSetDuplicateSigningKeys() {
@ -70,8 +76,7 @@ class JwtParserTest {
String junkPayload = '{;aklsjd;fkajsd;fkjasd;lfkj}'
String bad = TextCodec.BASE64.encode('{"alg":"none"}') + '.' +
TextCodec.BASE64.encode(junkPayload) + '.'
String bad = base64Url('{"alg":"none"}') + '.' + base64Url(junkPayload) + '.'
try {
Jwts.parser().parse(bad)
@ -92,9 +97,7 @@ class JwtParserTest {
String badSig = ";aklsjdf;kajsd;fkjas;dklfj"
String bad = TextCodec.BASE64.encode(header) + '.' +
TextCodec.BASE64.encode(payload) + '.' +
TextCodec.BASE64.encode(badSig)
String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig)
try {
Jwts.parser().setSigningKey(randomKey()).parse(bad)
@ -113,9 +116,7 @@ class JwtParserTest {
String badSig = ";aklsjdf;kajsd;fkjas;dklfj"
String bad = TextCodec.BASE64.encode(header) + '.' +
TextCodec.BASE64.encode(payload) + '.' +
TextCodec.BASE64.encode(badSig)
String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig)
try {
Jwts.parser().setSigningKey(randomKey()).parse(bad)
@ -129,15 +130,13 @@ class JwtParserTest {
@Test
void testParsePlaintextJwsWithIncorrectAlg() {
String header = '{"alg":"none"}'
def header = '{"alg":"none"}'
String payload = '{"subject":"Joe"}'
def payload = '{"subject":"Joe"}'
String badSig = ";aklsjdf;kajsd;fkjas;dklfj"
def badSig = ";aklsjdf;kajsd;fkjas;dklfj"
String bad = TextCodec.BASE64.encode(header) + '.' +
TextCodec.BASE64.encode(payload) + '.' +
TextCodec.BASE64.encode(badSig)
String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig)
try {
Jwts.parser().setSigningKey(randomKey()).parse(bad)
@ -150,8 +149,11 @@ class JwtParserTest {
@Test
void testParseWithBase64EncodedSigningKey() {
byte[] key = randomKey()
String base64Encodedkey = TextCodec.BASE64.encode(key)
String base64Encodedkey = Encoder.BASE64.encode(key)
String payload = 'Hello world!'
String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, base64Encodedkey).compact()
@ -1530,10 +1532,7 @@ class JwtParserTest {
String bogus = 'bogus'
String bad = TextCodec.BASE64.encode(header) + '.' +
TextCodec.BASE64.encode(payload) + '.' +
TextCodec.BASE64.encode(badSig) + '.' +
TextCodec.BASE64.encode(bogus)
String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig) + '.' + base64Url(bogus)
try {
@ -1549,7 +1548,7 @@ class JwtParserTest {
void testNoHeaderNoSig() {
String payload = '{"subject":"Joe"}'
String jwtStr = '.' + TextCodec.BASE64.encode(payload) + '.'
String jwtStr = '.' + base64Url(payload) + '.'
Jwt jwt = Jwts.parser().parse(jwtStr)
@ -1559,14 +1558,15 @@ class JwtParserTest {
@Test
void testNoHeaderSig() {
String payload = '{"subject":"Joe"}'
String sig = ";aklsjdf;kajsd;fkjas;dklfj"
String jwtStr = '.' + TextCodec.BASE64.encode(payload) + '.' + TextCodec.BASE64.encode(sig)
String jwtStr = '.' + base64Url(payload) + '.' + base64Url(sig)
try {
Jwt jwt = Jwts.parser().parse(jwtStr)
Jwts.parser().parse(jwtStr)
fail()
} catch (MalformedJwtException se) {
assertEquals 'JWT string has a digest/signature, but the header does not reference a valid signature algorithm.', se.message
@ -1581,7 +1581,7 @@ class JwtParserTest {
String sig = ";aklsjdf;kajsd;fkjas;dklfj"
String jwtStr = TextCodec.BASE64.encode(payload) + '.' + TextCodec.BASE64.encode(payload) + '.' + TextCodec.BASE64.encode(sig)
String jwtStr = base64Url(payload) + '.' + base64Url(payload) + '.' + base64Url(sig)
try {
Jwt jwt = Jwts.parser().parse(jwtStr)

View File

@ -16,9 +16,9 @@
package io.jsonwebtoken
import com.fasterxml.jackson.databind.ObjectMapper
import io.jsonwebtoken.codec.Encoder
import io.jsonwebtoken.impl.DefaultHeader
import io.jsonwebtoken.impl.DefaultJwsHeader
import io.jsonwebtoken.impl.TextCodec
import io.jsonwebtoken.impl.compression.CompressionCodecs
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver
import io.jsonwebtoken.impl.compression.GzipCompressionCodec
@ -39,6 +39,11 @@ import static org.junit.Assert.*
class JwtsTest {
protected static String base64Url(String s) {
byte[] bytes = s.getBytes(Strings.UTF_8)
return Encoder.BASE64URL.encode(bytes)
}
@Test
void testSubclass() {
new Jwts()
@ -613,8 +618,8 @@ class JwtsTest {
PrivateKey privateKey = kp.getPrivate();
ObjectMapper om = new ObjectMapper()
String header = TextCodec.BASE64URL.encode(om.writeValueAsString(['alg': 'HS256']))
String body = TextCodec.BASE64URL.encode(om.writeValueAsString('foo'))
String header = base64Url(om.writeValueAsString(['alg': 'HS256']))
String body = base64Url(om.writeValueAsString('foo'))
String compact = header + '.' + body + '.'
// Now for the forgery: simulate an attacker using the RSA public key to sign a token, but
@ -622,7 +627,7 @@ class JwtsTest {
Mac mac = Mac.getInstance('HmacSHA256');
mac.init(new SecretKeySpec(publicKey.getEncoded(), 'HmacSHA256'));
byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII')))
String encodedSignature = TextCodec.BASE64URL.encode(signatureBytes);
String encodedSignature = Encoder.BASE64URL.encode(signatureBytes)
//Finally, the forged token is the header + body + forged signature:
String forged = compact + encodedSignature;
@ -646,8 +651,8 @@ class JwtsTest {
//PrivateKey privateKey = kp.getPrivate();
ObjectMapper om = new ObjectMapper()
String header = TextCodec.BASE64URL.encode(om.writeValueAsString(['alg': 'HS256']))
String body = TextCodec.BASE64URL.encode(om.writeValueAsString('foo'))
String header = base64Url(om.writeValueAsString(['alg': 'HS256']))
String body = base64Url(om.writeValueAsString('foo'))
String compact = header + '.' + body + '.'
// Now for the forgery: simulate an attacker using the RSA public key to sign a token, but
@ -655,7 +660,7 @@ class JwtsTest {
Mac mac = Mac.getInstance('HmacSHA256');
mac.init(new SecretKeySpec(publicKey.getEncoded(), 'HmacSHA256'));
byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII')))
String encodedSignature = TextCodec.BASE64URL.encode(signatureBytes);
String encodedSignature = Encoder.BASE64URL.encode(signatureBytes);
//Finally, the forged token is the header + body + forged signature:
String forged = compact + encodedSignature;
@ -679,8 +684,8 @@ class JwtsTest {
//PrivateKey privateKey = kp.getPrivate();
ObjectMapper om = new ObjectMapper()
String header = TextCodec.BASE64URL.encode(om.writeValueAsString(['alg': 'HS256']))
String body = TextCodec.BASE64URL.encode(om.writeValueAsString('foo'))
String header = base64Url(om.writeValueAsString(['alg': 'HS256']))
String body = base64Url(om.writeValueAsString('foo'))
String compact = header + '.' + body + '.'
// Now for the forgery: simulate an attacker using the Elliptic Curve public key to sign a token, but
@ -688,7 +693,7 @@ class JwtsTest {
Mac mac = Mac.getInstance('HmacSHA256');
mac.init(new SecretKeySpec(publicKey.getEncoded(), 'HmacSHA256'));
byte[] signatureBytes = mac.doFinal(compact.getBytes(Charset.forName('US-ASCII')))
String encodedSignature = TextCodec.BASE64URL.encode(signatureBytes);
String encodedSignature = Encoder.BASE64URL.encode(signatureBytes);
//Finally, the forged token is the header + body + forged signature:
String forged = compact + encodedSignature;

View File

@ -0,0 +1,16 @@
package io.jsonwebtoken.codec
import org.junit.Test
import static org.junit.Assert.assertEquals
class CodecExceptionTest {
@Test
void testConstructorWithCause() {
def ioException = new IOException("root error")
def exception = new CodecException("wrapping", ioException)
assertEquals "wrapping", exception.getMessage()
assertEquals ioException, exception.getCause()
}
}

View File

@ -0,0 +1,16 @@
package io.jsonwebtoken.codec
import org.junit.Test
import static org.junit.Assert.assertEquals
class DecodingExceptionTest {
@Test
void testConstructorWithCause() {
def ioException = new IOException("root error")
def exception = new DecodingException("wrapping", ioException)
assertEquals "wrapping", exception.getMessage()
assertEquals ioException, exception.getCause()
}
}

View File

@ -0,0 +1,16 @@
package io.jsonwebtoken.codec
import org.junit.Test
import static org.junit.Assert.assertEquals
class EncodingExceptionTest {
@Test
void testConstructorWithCause() {
def ioException = new IOException("root error")
def exception = new EncodingException("wrapping", ioException)
assertEquals "wrapping", exception.getMessage()
assertEquals ioException, exception.getCause()
}
}

View File

@ -0,0 +1,22 @@
package io.jsonwebtoken.codec.impl
import io.jsonwebtoken.lang.Strings
import org.junit.Test
import static org.junit.Assert.assertEquals
class Base64DecoderTest {
@Test(expected = IllegalArgumentException)
void testDecodeWithNullArgument() {
new Base64Decoder().decode(null)
}
@Test
void testDecode() {
String encoded = 'SGVsbG8g5LiW55WM' // Hello
byte[] bytes = new Base64Decoder().decode(encoded)
String result = new String(bytes, Strings.UTF_8)
assertEquals 'Hello 世界', result
}
}

View File

@ -0,0 +1,22 @@
package io.jsonwebtoken.codec.impl
import io.jsonwebtoken.lang.Strings
import org.junit.Test
import static org.junit.Assert.assertEquals
class Base64EncoderTest {
@Test(expected = IllegalArgumentException)
void testEncodeWithNullArgument() {
new Base64Encoder().encode(null)
}
@Test
void testDecode() {
String input = 'Hello 世界'
byte[] bytes = input.getBytes(Strings.UTF_8)
String encoded = new Base64Encoder().encode(bytes)
assertEquals 'SGVsbG8g5LiW55WM', encoded
}
}

View File

@ -0,0 +1,141 @@
package io.jsonwebtoken.codec.impl
import io.jsonwebtoken.lang.Strings
import org.junit.Test
import static org.junit.Assert.*
class Base64Test {
private static final String PLAINTEXT =
'''Bacon ipsum dolor amet venison beef pork chop, doner jowl pastrami ground round alcatra.
Beef leberkas filet mignon ball tip pork spare ribs kevin short loin ribeye ground round
biltong jerky short ribs corned beef. Strip steak turducken meatball porchetta beef ribs
shoulder pork belly doner salami corned beef kielbasa cow filet mignon drumstick. Bacon
tenderloin pancetta flank frankfurter ham kevin leberkas meatball turducken beef ribs.
Cupim short loin short ribs shankle tenderloin. Ham ribeye hamburger flank tenderloin
cupim t-bone, shank tri-tip venison salami sausage pancetta. Pork belly chuck salami
alcatra sirloin.
,
,
,
,
, '''
@Test
void testEncodeToStringWithNullArgument() {
String s = Base64.DEFAULT.encodeToString(null, false)
assertEquals 0, s.toCharArray().length
}
@Test
void testEncodeToStringWithEmptyByteArray() {
byte[] bytes = new byte[0]
String s = Base64.DEFAULT.encodeToString(bytes, false)
assertEquals 0, s.toCharArray().length
}
@Test
void testLineSeparators() {
byte[] bytes = PLAINTEXT.getBytes(Strings.UTF_8)
String encoded = Base64.DEFAULT.encodeToString(bytes, true)
def r = new StringReader(encoded)
String line = ''
while ((line = r.readLine()) != null) {
assertTrue line.length() <= 76
}
}
@Test
void testDecodeFastWithNullArgument() {
byte[] bytes = Base64.DEFAULT.decodeFast(null)
assertEquals 0, bytes.length
}
@Test
void testDecodeFastWithEmptyCharArray() {
byte[] bytes = Base64.DEFAULT.decodeFast(new char[0])
assertEquals 0, bytes.length
}
@Test
void testDecodeFastWithSurroundingIllegalCharacters() {
String expected = 'Hello 世界'
def encoded = '***SGVsbG8g5LiW55WM!!!'
byte[] bytes = Base64.DEFAULT.decodeFast(encoded.toCharArray())
String result = new String(bytes, Strings.UTF_8)
assertEquals expected, result
}
@Test
void testDecodeFastWithLineSeparators() {
byte[] bytes = PLAINTEXT.getBytes(Strings.UTF_8)
String encoded = Base64.DEFAULT.encodeToString(bytes, true)
byte[] resultBytes = Base64.DEFAULT.decodeFast(encoded.toCharArray())
assertTrue Arrays.equals(bytes, resultBytes)
assertEquals PLAINTEXT, new String(resultBytes, Strings.UTF_8)
}
private static String BASE64(String s) {
byte[] bytes = s.getBytes(Strings.UTF_8);
return Base64.DEFAULT.encodeToString(bytes, false)
}
@Test // https://tools.ietf.org/html/rfc4648#page-12
void testRfc4648Base64TestVectors() {
assertEquals "", BASE64("")
assertEquals "Zg==", BASE64("f")
assertEquals "Zm8=", BASE64("fo")
assertEquals "Zm9v", BASE64("foo")
assertEquals "Zm9vYg==", BASE64("foob")
assertEquals "Zm9vYmE=", BASE64("fooba")
assertEquals "Zm9vYmFy", BASE64("foobar")
def input = 'special: [\r\n \t], ascii[32..126]: [ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~]\n'
def expected = "c3BlY2lhbDogWw0KIAldLCBhc2NpaVszMi4uMTI2XTogWyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+XQo="
assertEquals expected, BASE64(input)
}
private static String BASE64URL(String s) {
byte[] bytes = s.getBytes(Strings.UTF_8);
return Base64.URL_SAFE.encodeToString(bytes, false)
}
@Test //same test vectors above, but with padding removed & some specials swapped: https://brockallen.com/2014/10/17/base64url-encoding/
void testRfc4648Base64UrlTestVectors() {
assertEquals "", BASE64URL("")
assertEquals "Zg", BASE64URL("f") //base64 = 2 padding chars, base64url = no padding needed
assertEquals "Zm8", BASE64URL("fo") //base64 = 1 padding char, base64url = no padding needed
assertEquals "Zm9v", BASE64URL("foo")
assertEquals "Zm9vYg", BASE64URL("foob") //base64 = 2 padding chars, base64url = no padding needed
assertEquals "Zm9vYmE", BASE64URL("fooba") //base64 = 1 padding char, base64url = no padding needed
assertEquals "Zm9vYmFy", BASE64URL("foobar")
def input = 'special: [\r\n \t], ascii[32..126]: [ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~]\n'
def expected = "c3BlY2lhbDogWw0KIAldLCBhc2NpaVszMi4uMTI2XTogWyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+XQo="
.replace("=", "")
.replace("+", "-")
.replace("/", "_")
assertEquals expected, BASE64URL(input)
}
}

View File

@ -0,0 +1,58 @@
package io.jsonwebtoken.codec.impl
import io.jsonwebtoken.codec.Decoder
import io.jsonwebtoken.codec.DecodingException
import io.jsonwebtoken.codec.EncodingException
import org.junit.Test
import static org.junit.Assert.*
class ExceptionPropagatingDecoderTest {
@Test(expected = IllegalArgumentException)
void testWithNullConstructorArgument() {
new ExceptionPropagatingDecoder(null)
}
@Test(expected = IllegalArgumentException)
void testEncodeWithNullArgument() {
def decoder = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder())
decoder.decode(null)
}
@Test
void testEncodePropagatesDecodingException() {
def decoder = new ExceptionPropagatingDecoder(new Decoder() {
@Override
Object decode(Object o) throws DecodingException {
throw new DecodingException("problem", new IOException("dummy"))
}
})
try {
decoder.decode("hello")
fail()
} catch (DecodingException ex) {
assertEquals "problem", ex.getMessage()
}
}
@Test
void testEncodeWithNonEncodingExceptionIsWrappedAsEncodingException() {
def causeEx = new RuntimeException("whatevs")
def decoder = new ExceptionPropagatingDecoder(new Decoder() {
@Override
Object decode(Object o) throws EncodingException {
throw causeEx
}
})
try {
decoder.decode("hello")
fail()
} catch (DecodingException ex) {
assertEquals "Unable to decode input: whatevs", ex.getMessage()
assertSame causeEx, ex.getCause()
}
}
}

View File

@ -0,0 +1,58 @@
package io.jsonwebtoken.codec.impl
import io.jsonwebtoken.codec.Encoder
import io.jsonwebtoken.codec.EncodingException
import org.junit.Test
import static org.junit.Assert.*
class ExceptionPropagatingEncoderTest {
@Test(expected = IllegalArgumentException)
void testWithNullConstructorArgument() {
new ExceptionPropagatingEncoder(null)
}
@Test(expected = IllegalArgumentException)
void testEncodeWithNullArgument() {
def encoder = new ExceptionPropagatingEncoder<>(new Base64UrlEncoder())
encoder.encode(null)
}
@Test
void testEncodePropagatesEncodingException() {
def encoder = new ExceptionPropagatingEncoder(new Encoder() {
@Override
Object encode(Object o) throws EncodingException {
throw new EncodingException("problem", new IOException("dummy"))
}
})
try {
encoder.encode("hello")
fail()
} catch (EncodingException ex) {
assertEquals "problem", ex.getMessage()
}
}
@Test
void testEncodeWithNonEncodingExceptionIsWrappedAsEncodingException() {
def causeEx = new RuntimeException("whatevs")
def encoder = new ExceptionPropagatingEncoder(new Encoder() {
@Override
Object encode(Object o) throws EncodingException {
throw causeEx;
}
})
try {
encoder.encode("hello")
fail()
} catch (EncodingException ex) {
assertEquals "Unable to encode input: whatevs", ex.getMessage()
assertSame causeEx, ex.getCause()
}
}
}

View File

@ -27,6 +27,7 @@ import static org.powermock.api.easymock.PowerMock.mockStatic
import static org.powermock.api.easymock.PowerMock.replayAll
import static org.powermock.api.easymock.PowerMock.verifyAll
@Deprecated //remove just before 1.0.0 release
@RunWith(PowerMockRunner.class)
@PrepareForTest([Base64.class])
class AndroidBase64CodecTest {

View File

@ -0,0 +1,22 @@
package io.jsonwebtoken.impl
import org.junit.Test
import static org.junit.Assert.assertEquals
@Deprecated //remove just before 1.0.0 release
class Base64CodecTest {
@Test
void testEncodeDecode() {
String s = "Hello 世界"
def codec = new Base64Codec()
String encoded = codec.encode(s)
assertEquals s, codec.decodeToString(encoded)
}
}

View File

@ -1,19 +1,25 @@
package io.jsonwebtoken.impl
import io.jsonwebtoken.lang.Strings
import org.junit.Test
import static org.junit.Assert.*
@Deprecated //remove just before 1.0.0 release
class Base64UrlCodecTest {
@Test
void testRemovePaddingWithEmptyByteArray() {
void testEncodeDecode() {
String s = "Hello 世界"
def codec = new Base64UrlCodec()
byte[] empty = new byte[0];
String base64url = codec.encode(s.getBytes(Strings.UTF_8))
def result = codec.removePadding(empty)
byte[] decoded = codec.decode(base64url)
assertSame empty, result
String result = new String(decoded, Strings.UTF_8)
assertEquals s, result
}
}

View File

@ -19,6 +19,8 @@ import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.JsonMappingException
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.codec.Encoder
import io.jsonwebtoken.codec.EncodingException
import io.jsonwebtoken.impl.compression.CompressionCodecs
import io.jsonwebtoken.impl.crypto.MacProvider
import org.junit.Test
@ -164,21 +166,6 @@ class DefaultJwtBuilderTest {
}
}
@Test
void testCompactWithBothKeyAndKeyBytes() {
def b = new DefaultJwtBuilder()
b.setPayload('foo')
def key = MacProvider.generateKey()
b.signWith(SignatureAlgorithm.HS256, key)
b.signWith(SignatureAlgorithm.HS256, key.encoded)
try {
b.compact()
fail()
} catch (IllegalStateException ise) {
assertEquals ise.message, "A key object and key bytes cannot both be specified. Choose either one."
}
}
@Test
void testCompactWithJwsHeader() {
def b = new DefaultJwtBuilder()
@ -320,4 +307,21 @@ class DefaultJwtBuilderTest {
assertNull b.claims
}
@Test(expected = IllegalArgumentException)
void testBase64UrlEncodeWithNullArgument() {
new DefaultJwtBuilder().base64UrlEncodeWith(null)
}
@Test
void testBase64UrlEncodeWithCustomEncoder() {
def encoder = new Encoder() {
@Override
Object encode(Object o) throws EncodingException {
return null
}
}
def b = new DefaultJwtBuilder().base64UrlEncodeWith(encoder)
assertSame encoder, b.base64UrlEncoder
}
}

View File

@ -0,0 +1,30 @@
package io.jsonwebtoken.impl
import io.jsonwebtoken.codec.Decoder
import io.jsonwebtoken.codec.DecodingException
import org.junit.Test
import static org.junit.Assert.*
// NOTE to the casual reader: even though this test class appears mostly empty, the DefaultJwtParser
// implementation is tested to 100% coverage. The vast majority of its tests are in the JwtsTest class. This class
// just fills in any remaining test gaps.
class DefaultJwtParserTest {
@Test(expected = IllegalArgumentException)
void testBase64UrlDecodeWithNullArgument() {
new DefaultJwtBuilder().base64UrlEncodeWith(null)
}
@Test
void testBase64UrlEncodeWithCustomEncoder() {
def decoder = new Decoder() {
@Override
Object decode(Object o) throws DecodingException {
return null
}
}
def b = new DefaultJwtParser().base64UrlDecodeWith(decoder)
assertSame decoder, b.base64UrlDecoder
}
}

View File

@ -26,6 +26,12 @@ import static org.junit.Assert.*
@PrepareForTest([System.class])
class DefaultTextCodecFactoryTest {
@Test
void testGetSystemProperty() {
def factory = new DefaultTextCodecFactory()
assertNotNull factory.getSystemProperty("java.version")
}
@Test
void testIsAndroidByVmName() {

View File

@ -0,0 +1,33 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.codec.Decoder
import org.junit.Test
import static org.junit.Assert.*
class DefaultJwtSignatureValidatorTest {
@Test //TODO: remove this before 1.0 since it tests a deprecated method
@Deprecated
void testDeprecatedTwoArgCtor() {
def alg = SignatureAlgorithm.HS256
def key = MacProvider.generateKey(alg)
def validator = new DefaultJwtSignatureValidator(alg, key)
assertNotNull validator.signatureValidator
assertSame Decoder.BASE64URL, validator.base64UrlDecoder
}
@Test //TODO: remove this before 1.0 since it tests a deprecated method
@Deprecated
void testDeprecatedThreeArgCtor() {
def alg = SignatureAlgorithm.HS256
def key = MacProvider.generateKey(alg)
def validator = new DefaultJwtSignatureValidator(DefaultSignatureValidatorFactory.INSTANCE, alg, key)
assertNotNull validator.signatureValidator
assertSame Decoder.BASE64URL, validator.base64UrlDecoder
}
}

View File

@ -0,0 +1,34 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.codec.Encoder
import org.junit.Test
import static org.junit.Assert.*
class DefaultJwtSignerTest {
@Test //TODO: remove this before 1.0 since it tests a deprecated method
@Deprecated //remove just before 1.0.0 release
void testDeprecatedTwoArgCtor() {
def alg = SignatureAlgorithm.HS256
def key = MacProvider.generateKey(alg)
def signer = new DefaultJwtSigner(alg, key)
assertNotNull signer.signer
assertSame Encoder.BASE64URL, signer.base64UrlEncoder
}
@Test //TODO: remove this before 1.0 since it tests a deprecated method
@Deprecated //remove just before 1.0.0 release
void testDeprecatedThreeArgCtor() {
def alg = SignatureAlgorithm.HS256
def key = MacProvider.generateKey(alg)
def signer = new DefaultJwtSigner(DefaultSignerFactory.INSTANCE, alg, key)
assertNotNull signer.signer
assertSame Encoder.BASE64URL, signer.base64UrlEncoder
}
}

View File

@ -18,6 +18,7 @@ package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.JwtException
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.SignatureException
import io.jsonwebtoken.codec.Decoder
import io.jsonwebtoken.impl.TextCodec
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.Test
@ -64,13 +65,13 @@ class EllipticCurveSignatureValidatorTest {
void ecdsaSignatureComplianceTest() {
def fact = KeyFactory.getInstance("ECDSA", "BC");
def publicKey = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQASisgweVL1tAtIvfmpoqvdXF8sPKTV9YTKNxBwkdkm+/auh4pR8TbaIfsEzcsGUVv61DFNFXb0ozJfurQ59G2XcgAn3vROlSSnpbIvuhKrzL5jwWDTaYa5tVF1Zjwia/5HUhKBkcPuWGXg05nMjWhZfCuEetzMLoGcHmtvabugFrqsAg="
def pub = fact.generatePublic(new X509EncodedKeySpec(TextCodec.BASE64.decode(publicKey)))
def pub = fact.generatePublic(new X509EncodedKeySpec(Decoder.BASE64.decode(publicKey)))
def v = new EllipticCurveSignatureValidator(SignatureAlgorithm.ES512, pub)
def verifier = { token ->
def signatureStart = token.lastIndexOf('.')
def withoutSignature = token.substring(0, signatureStart)
def signature = token.substring(signatureStart + 1)
assert v.isValid(withoutSignature.getBytes("US-ASCII"), TextCodec.BASE64URL.decode(signature)), "Signature do not match that of other implementations"
assert v.isValid(withoutSignature.getBytes("US-ASCII"), Decoder.BASE64URL.decode(signature)), "Signature do not match that of other implementations"
}
//Test verification for token created using https://github.com/auth0/node-jsonwebtoken/tree/v7.0.1
verifier("eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30.Aab4x7HNRzetjgZ88AMGdYV2Ml7kzFbl8Ql2zXvBores7iRqm2nK6810ANpVo5okhHa82MQf2Q_Zn4tFyLDR9z4GAcKFdcAtopxq1h8X58qBWgNOc0Bn40SsgUc8wOX4rFohUCzEtnUREePsvc9EfXjjAH78WD2nq4tn-N94vf14SncQ")
@ -146,7 +147,7 @@ class EllipticCurveSignatureValidatorTest {
@Test
void edgeCaseSignatureToConcatLengthTest() {
try {
def signature = TextCodec.BASE64.decode("MIEAAGg3OVb/ZeX12cYrhK3c07TsMKo7Kc6SiqW++4CAZWCX72DkZPGTdCv2duqlupsnZL53hiG3rfdOLj8drndCU+KHGrn5EotCATdMSLCXJSMMJoHMM/ZPG+QOHHPlOWnAvpC1v4lJb32WxMFNz1VAIWrl9Aa6RPG1GcjCTScKjvEE")
def signature = Decoder.BASE64.decode("MIEAAGg3OVb/ZeX12cYrhK3c07TsMKo7Kc6SiqW++4CAZWCX72DkZPGTdCv2duqlupsnZL53hiG3rfdOLj8drndCU+KHGrn5EotCATdMSLCXJSMMJoHMM/ZPG+QOHHPlOWnAvpC1v4lJb32WxMFNz1VAIWrl9Aa6RPG1GcjCTScKjvEE")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
fail()
} catch (JwtException e) {
@ -157,7 +158,7 @@ class EllipticCurveSignatureValidatorTest {
@Test
void edgeCaseSignatureToConcatInvalidSignatureTest() {
try {
def signature = TextCodec.BASE64.decode("MIGBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
def signature = Decoder.BASE64.decode("MIGBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
fail()
} catch (JwtException e) {
@ -168,7 +169,7 @@ class EllipticCurveSignatureValidatorTest {
@Test
void edgeCaseSignatureToConcatInvalidSignatureBranchTest() {
try {
def signature = TextCodec.BASE64.decode("MIGBAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
def signature = Decoder.BASE64.decode("MIGBAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
fail()
} catch (JwtException e) {
@ -179,7 +180,7 @@ class EllipticCurveSignatureValidatorTest {
@Test
void edgeCaseSignatureToConcatInvalidSignatureBranch2Test() {
try {
def signature = TextCodec.BASE64.decode("MIGBAj4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
def signature = Decoder.BASE64.decode("MIGBAj4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
EllipticCurveProvider.transcodeSignatureToConcat(signature, 132)
fail()
} catch (JwtException e) {