-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue #4128 - test the decoding of OpenId Credentials
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
- Loading branch information
1 parent
8b37a8f
commit 153c404
Showing
6 changed files
with
194 additions
and
91 deletions.
There are no files selected for viewing
73 changes: 73 additions & 0 deletions
73
jetty-openid/src/main/java/org/eclipse/jetty/security/openid/CredentialsDecoder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package org.eclipse.jetty.security.openid; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
import java.util.Arrays; | ||
import java.util.Base64; | ||
import java.util.Map; | ||
|
||
import org.eclipse.jetty.util.ajax.JSON; | ||
import org.eclipse.jetty.util.log.Log; | ||
import org.eclipse.jetty.util.log.Logger; | ||
|
||
/** | ||
* Used to decode the ID Token from the base64 encrypted JSON Web Token (JWT). | ||
*/ | ||
public class CredentialsDecoder | ||
{ | ||
private static final Logger LOG = Log.getLogger(CredentialsDecoder.class); | ||
private static final Base64.Decoder decoder = Base64.getUrlDecoder(); | ||
|
||
/** | ||
* Decodes a JSON Web Token (JWT) into a Map of claims. | ||
* @param jwt the JWT to decode. | ||
* @return the map of claims encoded in the JWT. | ||
*/ | ||
public static Map<String, Object> decode(String jwt) | ||
{ | ||
if (LOG.isDebugEnabled()) | ||
LOG.debug("decode {}", jwt); | ||
|
||
String[] sections = jwt.split("\\."); | ||
if (sections.length != 3) | ||
throw new IllegalArgumentException("JWT does not contain 3 sections"); | ||
|
||
String jwtHeaderString = new String(decoder.decode(padJWTSection(sections[0])), StandardCharsets.UTF_8); | ||
String jwtClaimString = new String(decoder.decode(padJWTSection(sections[1])), StandardCharsets.UTF_8); | ||
String jwtSignature = sections[2]; | ||
|
||
Map<String, Object> jwtHeader = (Map)JSON.parse(jwtHeaderString); | ||
if (LOG.isDebugEnabled()) | ||
LOG.debug("JWT Header: {}", jwtHeader); | ||
|
||
/* If the ID Token is received via direct communication between the Client | ||
and the Token Endpoint (which it is in this flow), the TLS server validation | ||
MAY be used to validate the issuer in place of checking the token signature. */ | ||
if (LOG.isDebugEnabled()) | ||
LOG.debug("JWT signature not validated {}", jwtSignature); | ||
|
||
return (Map)JSON.parse(jwtClaimString); | ||
} | ||
|
||
static byte[] padJWTSection(String unpaddedEncodedJwtSection) | ||
{ | ||
int length = unpaddedEncodedJwtSection.length(); | ||
int remainder = length % 4; | ||
|
||
if (remainder == 1) | ||
throw new IllegalArgumentException("Not a valid Base64-encoded string"); | ||
|
||
byte[] paddedEncodedJwtSection; | ||
if (remainder > 0) | ||
{ | ||
int paddingNeeded = (4 - remainder) % 4; | ||
paddedEncodedJwtSection = Arrays.copyOf(unpaddedEncodedJwtSection.getBytes(), length + paddingNeeded); | ||
Arrays.fill(paddedEncodedJwtSection, length, paddedEncodedJwtSection.length, (byte)'='); | ||
} | ||
else | ||
{ | ||
paddedEncodedJwtSection = unpaddedEncodedJwtSection.getBytes(); | ||
} | ||
|
||
return paddedEncodedJwtSection; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
jetty-openid/src/test/java/org/eclipse/jetty/security/openid/CredentialsDecoderTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package org.eclipse.jetty.security.openid; | ||
|
||
import java.util.Map; | ||
import java.util.stream.Stream; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.Arguments; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
|
||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.is; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
|
||
public class CredentialsDecoderTest | ||
{ | ||
public static Stream<Arguments> paddingExamples() | ||
{ | ||
return Stream.of( | ||
Arguments.of("XXXX", "XXXX"), | ||
Arguments.of("XXX", "XXX="), | ||
Arguments.of("XX", "XX==") | ||
); | ||
} | ||
|
||
public static Stream<Arguments> badPaddingExamples() | ||
{ | ||
return Stream.of( | ||
Arguments.of("X"), | ||
Arguments.of("XXXXX") | ||
); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("paddingExamples") | ||
public void testPaddingBase64(String input, String expected) | ||
{ | ||
byte[] actual = CredentialsDecoder.padJWTSection(input); | ||
assertThat(actual, is(expected.getBytes())); | ||
} | ||
|
||
@ParameterizedTest | ||
@MethodSource("badPaddingExamples") | ||
public void testPaddingInvalidBase64(String input) | ||
{ | ||
IllegalArgumentException error = assertThrows(IllegalArgumentException.class, | ||
() -> CredentialsDecoder.padJWTSection(input)); | ||
|
||
assertThat(error.getMessage(), is("Not a valid Base64-encoded string")); | ||
} | ||
|
||
@Test | ||
public void testEncodeDecode() | ||
{ | ||
String issuer = "example.com"; | ||
String subject = "1234"; | ||
String clientId = "1234.client.id"; | ||
String name = "Bob"; | ||
long expiry = 123; | ||
|
||
// Create a fake ID Token. | ||
String claims = JwtEncoder.createIdToken(issuer, clientId, subject, name, expiry); | ||
String idToken = JwtEncoder.encode(claims); | ||
|
||
// Decode the ID Token and verify the claims are the same. | ||
Map<String, Object> decodedClaims = CredentialsDecoder.decode(idToken); | ||
assertThat(decodedClaims.get("iss"), is(issuer)); | ||
assertThat(decodedClaims.get("sub"), is(subject)); | ||
assertThat(decodedClaims.get("aud"), is(clientId)); | ||
assertThat(decodedClaims.get("name"), is(name)); | ||
assertThat(decodedClaims.get("exp"), is(expiry)); | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
jetty-openid/src/test/java/org/eclipse/jetty/security/openid/JwtEncoder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package org.eclipse.jetty.security.openid; | ||
|
||
import java.util.Base64; | ||
|
||
public class JwtEncoder | ||
{ | ||
private static final Base64.Encoder ENCODER = Base64.getUrlEncoder(); | ||
private static final String DEFAULT_HEADER = "{\"INFO\": \"this is not used or checked in our implementation\"}"; | ||
private static final String DEFAULT_SIGNATURE = "we do not validate signature as we use the authorization code flow"; | ||
|
||
public static String encode(String idToken) | ||
{ | ||
return stripPadding(ENCODER.encodeToString(DEFAULT_HEADER.getBytes())) + "." + | ||
stripPadding(ENCODER.encodeToString(idToken.getBytes())) + "." + | ||
stripPadding(ENCODER.encodeToString(DEFAULT_SIGNATURE.getBytes())); | ||
} | ||
|
||
private static String stripPadding(String paddedBase64) | ||
{ | ||
return paddedBase64.split("=")[0]; | ||
} | ||
|
||
public static String createIdToken(String provider, String clientId, String subject, String name, long expiry) | ||
{ | ||
return "{" + | ||
"\"iss\": \"" + provider + "\"," + | ||
"\"sub\": \"" + subject + "\"," + | ||
"\"aud\": \"" + clientId + "\"," + | ||
"\"exp\": " + expiry + "," + | ||
"\"name\": \"" + name + "\"," + | ||
"\"email\": \"" + name + "@fake-email.com" + "\"" + | ||
"}"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters