From 295ca26814f220e15fe5f0381e2fac904ad05834 Mon Sep 17 00:00:00 2001 From: Dolph Flynn <96876199+DolphFlynn@users.noreply.github.com> Date: Sun, 28 Jul 2024 13:10:30 +0100 Subject: [PATCH 1/2] Add time claims to JOSEObject. --- .../jwteditor/model/jose/JOSEObject.java | 6 +- .../blackberry/jwteditor/model/jose/JWE.java | 8 + .../blackberry/jwteditor/model/jose/JWS.java | 8 + .../jwteditor/model/jose/TimeClaim.java | 70 ++++++ .../jwteditor/model/jose/TimeClaimType.java | 48 ++++ .../jwteditor/model/jose/TimeClaimTest.java | 215 ++++++++++++++++++ 6 files changed, 354 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/blackberry/jwteditor/model/jose/TimeClaim.java create mode 100644 src/main/java/com/blackberry/jwteditor/model/jose/TimeClaimType.java create mode 100644 src/test/java/com/blackberry/jwteditor/model/jose/TimeClaimTest.java diff --git a/src/main/java/com/blackberry/jwteditor/model/jose/JOSEObject.java b/src/main/java/com/blackberry/jwteditor/model/jose/JOSEObject.java index 6de74f7..f2bcc68 100644 --- a/src/main/java/com/blackberry/jwteditor/model/jose/JOSEObject.java +++ b/src/main/java/com/blackberry/jwteditor/model/jose/JOSEObject.java @@ -20,6 +20,8 @@ import com.nimbusds.jose.util.Base64URL; +import java.util.List; + /** * Abstract class representing common elements of JWE/JWT */ @@ -47,8 +49,10 @@ public Base64URL getEncodedHeader(){ } /** - * Serialize the JWT/JWE to a string in compact serializiation form + * Serialize the JWT/JWE to a string in compact serialization form * @return the compact serialized JWE/JWS */ public abstract String serialize(); + + public abstract List timeClaims(); } diff --git a/src/main/java/com/blackberry/jwteditor/model/jose/JWE.java b/src/main/java/com/blackberry/jwteditor/model/jose/JWE.java index 4684cbb..80e2729 100644 --- a/src/main/java/com/blackberry/jwteditor/model/jose/JWE.java +++ b/src/main/java/com/blackberry/jwteditor/model/jose/JWE.java @@ -27,6 +27,9 @@ import java.security.Provider; import java.security.Security; import java.text.ParseException; +import java.util.List; + +import static java.util.Collections.emptyList; /** * Class representing a JWE @@ -64,6 +67,11 @@ public String serialize() { return"%s.%s.%s.%s.%s".formatted(header.toString(), encryptedKey.toString(), iv.toString(), ciphertext.toString(), tag.toString()); } + @Override + public List timeClaims() { + return emptyList(); + } + /** * Get the encrypted key component as bytes * diff --git a/src/main/java/com/blackberry/jwteditor/model/jose/JWS.java b/src/main/java/com/blackberry/jwteditor/model/jose/JWS.java index 1c0e5d5..374d091 100644 --- a/src/main/java/com/blackberry/jwteditor/model/jose/JWS.java +++ b/src/main/java/com/blackberry/jwteditor/model/jose/JWS.java @@ -28,6 +28,7 @@ import java.nio.charset.StandardCharsets; import java.security.Provider; import java.security.Security; +import java.util.List; /** * Class representing a JWS @@ -35,6 +36,7 @@ public class JWS extends JOSEObject { private final Base64URL payload; private final Base64URL signature; + private final List timeClaims; /** * Construct a JWS from encoded components @@ -46,6 +48,7 @@ public class JWS extends JOSEObject { super(header); this.payload = payload; this.signature = signature; + this.timeClaims = TimeClaim.from(payload.decodeToString()); } /** @@ -80,6 +83,11 @@ public String serialize() { return "%s.%s.%s".formatted(header.toString(), payload.toString(), signature.toString()); } + @Override + public List timeClaims() { + return timeClaims; + } + /** * Verify JWS with a JWK and an algorithm * diff --git a/src/main/java/com/blackberry/jwteditor/model/jose/TimeClaim.java b/src/main/java/com/blackberry/jwteditor/model/jose/TimeClaim.java new file mode 100644 index 0000000..af1a008 --- /dev/null +++ b/src/main/java/com/blackberry/jwteditor/model/jose/TimeClaim.java @@ -0,0 +1,70 @@ +/* +Author : Dolph Flynn + +Copyright 2024 Dolph Flynn + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package com.blackberry.jwteditor.model.jose; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.List; +import java.util.Optional; + +import static java.util.Arrays.stream; +import static java.util.Collections.emptyList; + +public record TimeClaim(TimeClaimType type, String data, Long value) { + + public boolean isValid() { + return type.isValid(value); + } + + static List from(String payloadJson) { + Optional optional = parsePayload(payloadJson); + + if (optional.isEmpty()) { + return emptyList(); + } + + JSONObject jsonObject = optional.get(); + + return stream(TimeClaimType.values()) + .filter(type -> jsonObject.has(type.name)) + .map(type -> new TimeClaim( + type, + jsonObject.get(type.name).toString(), + numberValue(jsonObject, type.name)) + ) + .toList(); + } + + private static Optional parsePayload(String payloadJson) { + try { + return Optional.of(new JSONObject(payloadJson)); + } catch (JSONException e) { + return Optional.empty(); + } + } + + private static Long numberValue(JSONObject jsonObject, String name) { + try { + return jsonObject.getLong(name); + } catch (JSONException e) { + return null; + } + } +} diff --git a/src/main/java/com/blackberry/jwteditor/model/jose/TimeClaimType.java b/src/main/java/com/blackberry/jwteditor/model/jose/TimeClaimType.java new file mode 100644 index 0000000..19351d6 --- /dev/null +++ b/src/main/java/com/blackberry/jwteditor/model/jose/TimeClaimType.java @@ -0,0 +1,48 @@ +/* +Author : Dolph Flynn + +Copyright 2024 Dolph Flynn + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package com.blackberry.jwteditor.model.jose; + +import java.time.Instant; + +import static java.time.Instant.now; + +public enum TimeClaimType { + EXPIRATION_TIME("exp"), + NOT_BEFORE_TIME("nbf"), + ISSUED_AT_TIME("iat"); + + final String name; + + TimeClaimType(String name) { + this.name = name; + } + + public boolean isValid(Long value) { + if (value == null || value < 0) { + return false; + } + + Instant valueTime = Instant.ofEpochSecond(value); + + return switch (this) { + case EXPIRATION_TIME -> valueTime.isAfter(now()); + case NOT_BEFORE_TIME, ISSUED_AT_TIME -> valueTime.isBefore(now()); + }; + } +} diff --git a/src/test/java/com/blackberry/jwteditor/model/jose/TimeClaimTest.java b/src/test/java/com/blackberry/jwteditor/model/jose/TimeClaimTest.java new file mode 100644 index 0000000..3a6b6dd --- /dev/null +++ b/src/test/java/com/blackberry/jwteditor/model/jose/TimeClaimTest.java @@ -0,0 +1,215 @@ +/* +Author : Dolph Flynn + +Copyright 2024 Dolph Flynn + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package com.blackberry.jwteditor.model.jose; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.text.ParseException; + +import static com.blackberry.jwteditor.model.jose.TimeClaimType.*; +import static org.assertj.core.api.Assertions.assertThat; + +class TimeClaimTest { + + @Test + void givenJWSWithNoTimeClaims_thenTimeClaimsIsEmpty() throws ParseException { + JWS jws = JWSFactory.parse("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJUZXN0In0.WVLalefVZ5Rj991Cjgh0qBjKSIQaqC_CgN3b-30GKpQ"); + + assertThat(jws.timeClaims()).isEmpty(); + } + + @ParameterizedTest + @ValueSource(strings = { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNzE2MjM5MDIyfQ.FuJB22Dq3zXcFXtIAc59tWnmbbFC6jRXzb_2ejbhhoQ", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoiMTcxNjIzOTAyMiJ9.8Tm8e1DEoD92S98fm9Hh_xFMsFVGNN7VjRZppD-bfMM" + }) + void givenJWSWithExpTimeClaims_thenTimeClaimsCorrect(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + assertThat(jws.timeClaims()).containsExactly(new TimeClaim(EXPIRATION_TIME, "1716239022", 1716239022L)); + } + + @ParameterizedTest + @ValueSource(strings = { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoyMzE2MjM5MDIyfQ.nmCDcUHT-yLgrRG1LKMH9E2FQs2xWcB8CncxoqKdQEQ", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoiMjMxNjIzOTAyMiJ9.kDOLZJTcFVwrSVSDeFK31EIx7Q4e4Ya33iNCk4QZ10c" + }) + void givenJWSWithExpTimeClaims_whenExpiryDateInTheFuture_thenTimeClaimValid(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + TimeClaim timeClaim = jws.timeClaims().getFirst(); + + assertThat(timeClaim.type()).isEqualTo(EXPIRATION_TIME); + assertThat(timeClaim.isValid()).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoiemVvbGl0ZSJ9.Vsy0uiJVpA17ys9DRFWldEcgiut_N5QhvHCRRcp8Xow", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjpudWxsfQ.JsGUzIiM3S7cZF3LjYg-S6rYz4VXK-GBpGELzNJKOgY", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjotMTIzNDV9.BCgzT_CEurIxa0MxbS9seJ62lgfJT54P7AQpUkp65GE" + }) + void givenJWSWithExpTimeClaims_whenExpiryDateInvalid_thenTimeClaimNotValid(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + TimeClaim timeClaim = jws.timeClaims().getFirst(); + + assertThat(timeClaim.type()).isEqualTo(EXPIRATION_TIME); + assertThat(timeClaim.isValid()).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJleHAiOjE1MTYyMzkwMjJ9.mVGXFv3OuwtuZPsdaf_oGUYm2uOH-T-JRTDQE1c10q0", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJleHAiOjB9.eVVhRMapPD77WuMWIHKBqYw0cjJHC49On-mHEuonYjk" + }) + void givenJWSWithExpTimeClaims_whenExpiryDateIsInThePast_thenTimeClaimNotValid(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + TimeClaim timeClaim = jws.timeClaims().getFirst(); + + assertThat(timeClaim.type()).isEqualTo(EXPIRATION_TIME); + assertThat(timeClaim.isValid()).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjoxNzE2MjM5MDIyfQ.mZ_wGwRA0BBp_m6LJOPOBfwMosKrhTf9DbnKZYTzBzg", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjoiMTcxNjIzOTAyMiJ9.JD7t6jE6sOzuaFi5Lj5e3RhnoRDDWW9QHv_U3bFgEKM" + }) + void givenJWSWithNbfTimeClaims_thenTimeClaimsCorrect(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + assertThat(jws.timeClaims()).containsExactly(new TimeClaim(NOT_BEFORE_TIME, "1716239022", 1716239022L)); + } + + @ParameterizedTest + @ValueSource(strings = { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjoxNzE2MjM5MDIyfQ.mZ_wGwRA0BBp_m6LJOPOBfwMosKrhTf9DbnKZYTzBzg", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjoiMTcxNjIzOTAyMiJ9.JD7t6jE6sOzuaFi5Lj5e3RhnoRDDWW9QHv_U3bFgEKM", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjE1MTYyMzkwMjJ9.1OhNEYnVM64dytXGZ1kj_Z3fV73xGFxWyba52S5r7wc", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjB9.AqIAHlKSdpcDp7mD6sWsfKCQxI2hyJYsI7Y4Dh6N6pI", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjE3MjIxNjc0Nzd9.2almhOGrigs8lXH9DLKBkkUCS6r5j7zpJXYsXJN39d4" + }) + void givenJWSWithNbfTimeClaims_whenNotBeforeDateInThePast_thenTimeClaimValid(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + TimeClaim timeClaim = jws.timeClaims().getFirst(); + + assertThat(timeClaim.type()).isEqualTo(NOT_BEFORE_TIME); + assertThat(timeClaim.isValid()).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjoiemVvbGl0ZSJ9.kmx4FteQrcATfc2Zx47WypsqrIC-e1IM4opeLJxLyrI", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjpudWxsfQ.Nyai4iIAzjR5OcUwaE8xQYbArYuBIzbKxaJDnvu1J_I", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjotMTIzNDV9.rlnrK7unNEaaghPFhNQnDp1GRbCU0rGORO2yDf5YIZk" + }) + void givenJWSWithNbfTimeClaims_whenNotBeforeDateInvalid_thenTimeClaimNotValid(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + TimeClaim timeClaim = jws.timeClaims().getFirst(); + + assertThat(timeClaim.type()).isEqualTo(NOT_BEFORE_TIME); + assertThat(timeClaim.isValid()).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjIzMjIxNjc0Nzd9.cMocfo6ghvuYBqDwZ9GBdfbCnMLsUcZe2GZRuaah0-c", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjE4MjIxNjc0Nzd9._m-Rhio9DLcVRfWo7S-KIvlTk29QuDgal-tqreN4y4E" + }) + void givenJWSWithNbfTimeClaims_whenNotBeforeDateIsInTheFuture_thenTimeClaimNotValid(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + TimeClaim timeClaim = jws.timeClaims().getFirst(); + + assertThat(timeClaim.type()).isEqualTo(NOT_BEFORE_TIME); + assertThat(timeClaim.isValid()).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3MTYyMzkwMjJ9.y06rqsXv0DMutukwDaUJU0Sf-Ye3qrDkyFpOaj1J08A", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOiIxNzE2MjM5MDIyIn0.c2Ogr8QeTwopupVPqI56VaovZXE3svug2BI-Trft2EA" + }) + void givenJWSWithNIatTimeClaims_thenTimeClaimsCorrect(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + assertThat(jws.timeClaims()).containsExactly(new TimeClaim(ISSUED_AT_TIME, "1716239022", 1716239022L)); + } + + @ParameterizedTest + @ValueSource(strings = { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3MTYyMzkwMjJ9.y06rqsXv0DMutukwDaUJU0Sf-Ye3qrDkyFpOaj1J08A", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOiIxNzE2MjM5MDIyIn0.c2Ogr8QeTwopupVPqI56VaovZXE3svug2BI-Trft2EA", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.hqWGSaFpvbrXkOWc6lrnffhNWR19W_S1YKFBx2arWBk", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjB9.9TaucSjKgR3_gXUlzTGperv3PK9IAXO0ZVbgP9Wx4IY", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3MjIxNjc0Nzd9.SWmvLUBWE5ddBWvSEWnrHM8W3rfzyYmEQVk6-ywFFgQ" + }) + void givenJWSWithIatTimeClaims_whenIssuedAtDateInThePast_thenTimeClaimValid(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + TimeClaim timeClaim = jws.timeClaims().getFirst(); + + assertThat(timeClaim.type()).isEqualTo(ISSUED_AT_TIME); + assertThat(timeClaim.isValid()).isTrue(); + } + + + @ParameterizedTest + @ValueSource(strings = { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOiJ6ZW9saXRlIn0.-IpgDK_M_jQJQ6FDa-wd25xGFJ2bHNthdYn1JlQNxjg", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOm51bGx9.uplfi-hImBCsHTs__K-0LU612y3EW92J1TbINlDGc-k", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOi0xMjM0NX0.gVkVHyEasqw2NZ63un0T9Yd6bW8bmY9uMYndlptiQzQ" + }) + void givenJWSWithIatTimeClaims_whenIssuedAtDateInvalid_thenTimeClaimNotValid(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + TimeClaim timeClaim = jws.timeClaims().getFirst(); + + assertThat(timeClaim.type()).isEqualTo(ISSUED_AT_TIME); + assertThat(timeClaim.isValid()).isFalse(); + } + + + @ParameterizedTest + @ValueSource(strings = { + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjIzMjIxNjc0Nzd9.3UYGoLpdXRhnlai81VFV_iWmW90xnCimcYverY1-Zk4", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE4MjIxNjc0Nzd9._H-G7WjMbg8IVADVbz1Lsd7I_bGQZBYKPf8S2Q9vxf4" + }) + void givenJWSWithIatTimeClaims_whenIssuedAtDateIsInTheFuture_thenTimeClaimNotValid(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + TimeClaim timeClaim = jws.timeClaims().getFirst(); + + assertThat(timeClaim.type()).isEqualTo(ISSUED_AT_TIME); + assertThat(timeClaim.isValid()).isFalse(); + } + + @Test + void givenJWE_thenTimeClaimsIsEmpty() throws ParseException { + JWE jwe = JWEFactory.parse("eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTEyOEtXIn0.H3X6mT5HLgcFfzLoe4ku6Knhh9Ofv1eL.qF5-N_7K8VQ4yMSz.WXUNY6eg5fR4tc8Hqf5XDRM9ALGwcQyYG4IYwwg8Ctkx1UuxoV7t6UnemjzCj2sOYUqi3KYpDzrKVJpzokz0vcIem4lFe5N_ds8FAMpW0GSF9ePA8qvV99WaP0N2ECVPmgihvL6qwNhdptlLKtxcOpE41U5LnU22voPK55VF4_1j0WmTgWgZ7DwLDysp6EIDjrrt-DY.febBmP71KADmKRVfeSnv_g"); + + assertThat(jwe.timeClaims()).isEmpty(); + } +} \ No newline at end of file From bd5fc00d149a3c58165f0bb95ded60ce33e2a186 Mon Sep 17 00:00:00 2001 From: Dolph Flynn <96876199+DolphFlynn@users.noreply.github.com> Date: Sun, 28 Jul 2024 17:26:21 +0100 Subject: [PATCH 2/2] Show warnings when time claims are invalid. --- .../jwteditor/model/jose/JOSEObject.java | 12 + .../model/jose/MutableJOSEObject.java | 4 + .../jwteditor/model/jose/TimeClaim.java | 14 ++ .../jwteditor/model/jose/TimeClaimType.java | 8 +- .../jwteditor/presenter/EditorPresenter.java | 2 + .../jwteditor/view/editor/EditorView.form | 14 +- .../jwteditor/view/editor/EditorView.java | 9 + .../jwteditor/model/jose/TimeClaimTest.java | 230 ++++++++++++++---- 8 files changed, 240 insertions(+), 53 deletions(-) diff --git a/src/main/java/com/blackberry/jwteditor/model/jose/JOSEObject.java b/src/main/java/com/blackberry/jwteditor/model/jose/JOSEObject.java index f2bcc68..67e7cd2 100644 --- a/src/main/java/com/blackberry/jwteditor/model/jose/JOSEObject.java +++ b/src/main/java/com/blackberry/jwteditor/model/jose/JOSEObject.java @@ -22,6 +22,9 @@ import java.util.List; +import static java.util.function.Predicate.not; +import static java.util.stream.Collectors.joining; + /** * Abstract class representing common elements of JWE/JWT */ @@ -55,4 +58,13 @@ public Base64URL getEncodedHeader(){ public abstract String serialize(); public abstract List timeClaims(); + + public String getWarnings() { + String warnings = timeClaims().stream() + .map(TimeClaim::warning) + .filter(not(String::isEmpty)) + .collect(joining(", ")); + + return warnings.isEmpty() ? "" : warnings + "."; + } } diff --git a/src/main/java/com/blackberry/jwteditor/model/jose/MutableJOSEObject.java b/src/main/java/com/blackberry/jwteditor/model/jose/MutableJOSEObject.java index 44bb304..114849d 100644 --- a/src/main/java/com/blackberry/jwteditor/model/jose/MutableJOSEObject.java +++ b/src/main/java/com/blackberry/jwteditor/model/jose/MutableJOSEObject.java @@ -71,4 +71,8 @@ public JOSEObject getModified() { public String getOriginal() { return original; } + + public String getWarnings() { + return modified.getWarnings(); + } } diff --git a/src/main/java/com/blackberry/jwteditor/model/jose/TimeClaim.java b/src/main/java/com/blackberry/jwteditor/model/jose/TimeClaim.java index af1a008..751f15e 100644 --- a/src/main/java/com/blackberry/jwteditor/model/jose/TimeClaim.java +++ b/src/main/java/com/blackberry/jwteditor/model/jose/TimeClaim.java @@ -33,6 +33,20 @@ public boolean isValid() { return type.isValid(value); } + public String warning() { + if (isValid()) { + return ""; + } + + if (value == null || value < 0) { + return "'%s' value is invalid".formatted(type.name); + } + + String futurePast = type.dateInThePastRequired() ? "future" : "past"; + + return "'%s' date is in the %s".formatted(type.name, futurePast); + } + static List from(String payloadJson) { Optional optional = parsePayload(payloadJson); diff --git a/src/main/java/com/blackberry/jwteditor/model/jose/TimeClaimType.java b/src/main/java/com/blackberry/jwteditor/model/jose/TimeClaimType.java index 19351d6..a012996 100644 --- a/src/main/java/com/blackberry/jwteditor/model/jose/TimeClaimType.java +++ b/src/main/java/com/blackberry/jwteditor/model/jose/TimeClaimType.java @@ -40,9 +40,13 @@ public boolean isValid(Long value) { Instant valueTime = Instant.ofEpochSecond(value); + return dateInThePastRequired() ? valueTime.isBefore(now()) : valueTime.isAfter(now()); + } + + public boolean dateInThePastRequired() { return switch (this) { - case EXPIRATION_TIME -> valueTime.isAfter(now()); - case NOT_BEFORE_TIME, ISSUED_AT_TIME -> valueTime.isBefore(now()); + case EXPIRATION_TIME -> false; + case NOT_BEFORE_TIME, ISSUED_AT_TIME -> true; }; } } diff --git a/src/main/java/com/blackberry/jwteditor/presenter/EditorPresenter.java b/src/main/java/com/blackberry/jwteditor/presenter/EditorPresenter.java index c49b359..fa15f4d 100644 --- a/src/main/java/com/blackberry/jwteditor/presenter/EditorPresenter.java +++ b/src/main/java/com/blackberry/jwteditor/presenter/EditorPresenter.java @@ -446,6 +446,8 @@ public void componentChanged() { mutableJoseObject.setModified(joseObject); //Highlight the serialized text as changed if it differs from the original, and the change wasn't triggered by onSelectionChanging view.setSerialized(joseObject.serialize(), mutableJoseObject.changed() && !selectionChanging); + + view.setWarnings(mutableJoseObject.getWarnings()); } /** diff --git a/src/main/java/com/blackberry/jwteditor/view/editor/EditorView.form b/src/main/java/com/blackberry/jwteditor/view/editor/EditorView.form index 2d3cbc6..8b93403 100644 --- a/src/main/java/com/blackberry/jwteditor/view/editor/EditorView.form +++ b/src/main/java/com/blackberry/jwteditor/view/editor/EditorView.form @@ -427,13 +427,15 @@ - + - + + + @@ -464,6 +466,14 @@ + + + + + + + + diff --git a/src/main/java/com/blackberry/jwteditor/view/editor/EditorView.java b/src/main/java/com/blackberry/jwteditor/view/editor/EditorView.java index 34c71d2..2556bf2 100644 --- a/src/main/java/com/blackberry/jwteditor/view/editor/EditorView.java +++ b/src/main/java/com/blackberry/jwteditor/view/editor/EditorView.java @@ -45,6 +45,7 @@ import static java.awt.Color.RED; import static java.awt.EventQueue.invokeLater; +import static java.awt.Font.BOLD; import static java.awt.event.HierarchyEvent.SHOWING_CHANGED; import static org.exbin.deltahex.EditationAllowed.ALLOWED; import static org.exbin.deltahex.EditationAllowed.READ_ONLY; @@ -92,6 +93,7 @@ public abstract class EditorView implements ExtensionProvidedEditor { private JCheckBox checkBoxJWSPayloadCompactJSON; private JSplitPane upperSplitPane; private JSplitPane lowerSplitPane; + private JLabel labelWarnings; private CodeArea codeAreaSignature; private CodeArea codeAreaEncryptedKey; @@ -546,6 +548,13 @@ private void createUIComponents() { textAreaPayload = rstaFactory.buildDefaultTextArea(); } + public void setWarnings(String text) { + invokeLater(() -> { + labelWarnings.setFont(labelWarnings.getFont().deriveFont(BOLD)); + labelWarnings.setText(text); + }); + } + private class ResizeSplitPanesOnFirstRenderHierarchyListener implements HierarchyListener { @Override public void hierarchyChanged(HierarchyEvent e) { diff --git a/src/test/java/com/blackberry/jwteditor/model/jose/TimeClaimTest.java b/src/test/java/com/blackberry/jwteditor/model/jose/TimeClaimTest.java index 3a6b6dd..020b036 100644 --- a/src/test/java/com/blackberry/jwteditor/model/jose/TimeClaimTest.java +++ b/src/test/java/com/blackberry/jwteditor/model/jose/TimeClaimTest.java @@ -20,9 +20,11 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import java.text.ParseException; +import java.util.stream.Stream; import static com.blackberry.jwteditor.model.jose.TimeClaimType.*; import static org.assertj.core.api.Assertions.assertThat; @@ -36,6 +38,13 @@ void givenJWSWithNoTimeClaims_thenTimeClaimsIsEmpty() throws ParseException { assertThat(jws.timeClaims()).isEmpty(); } + @Test + void givenJWSWithNoTimeClaims_thenWarningsAreEmpty() throws ParseException { + JWS jws = JWSFactory.parse("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJUZXN0In0.WVLalefVZ5Rj991Cjgh0qBjKSIQaqC_CgN3b-30GKpQ"); + + assertThat(jws.getWarnings()).isEmpty(); + } + @ParameterizedTest @ValueSource(strings = { "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNzE2MjM5MDIyfQ.FuJB22Dq3zXcFXtIAc59tWnmbbFC6jRXzb_2ejbhhoQ", @@ -47,11 +56,15 @@ void givenJWSWithExpTimeClaims_thenTimeClaimsCorrect(String data) throws ParseEx assertThat(jws.timeClaims()).containsExactly(new TimeClaim(EXPIRATION_TIME, "1716239022", 1716239022L)); } + private static Stream jwsWithValidExpValues() { + return Stream.of( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoyMzE2MjM5MDIyfQ.nmCDcUHT-yLgrRG1LKMH9E2FQs2xWcB8CncxoqKdQEQ", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoiMjMxNjIzOTAyMiJ9.kDOLZJTcFVwrSVSDeFK31EIx7Q4e4Ya33iNCk4QZ10c" + ); + } + @ParameterizedTest - @ValueSource(strings = { - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoyMzE2MjM5MDIyfQ.nmCDcUHT-yLgrRG1LKMH9E2FQs2xWcB8CncxoqKdQEQ", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoiMjMxNjIzOTAyMiJ9.kDOLZJTcFVwrSVSDeFK31EIx7Q4e4Ya33iNCk4QZ10c" - }) + @MethodSource("jwsWithValidExpValues") void givenJWSWithExpTimeClaims_whenExpiryDateInTheFuture_thenTimeClaimValid(String data) throws ParseException { JWS jws = JWSFactory.parse(data); @@ -62,11 +75,23 @@ void givenJWSWithExpTimeClaims_whenExpiryDateInTheFuture_thenTimeClaimValid(Stri } @ParameterizedTest - @ValueSource(strings = { - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoiemVvbGl0ZSJ9.Vsy0uiJVpA17ys9DRFWldEcgiut_N5QhvHCRRcp8Xow", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjpudWxsfQ.JsGUzIiM3S7cZF3LjYg-S6rYz4VXK-GBpGELzNJKOgY", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjotMTIzNDV9.BCgzT_CEurIxa0MxbS9seJ62lgfJT54P7AQpUkp65GE" - }) + @MethodSource("jwsWithValidExpValues") + void givenJWSWithExpTimeClaims_whenExpiryDateInTheFuture_thenWarningsEmpty(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + assertThat(jws.getWarnings()).isEmpty(); + } + + private static Stream jwsWithInvalidExpValues() { + return Stream.of( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoiemVvbGl0ZSJ9.Vsy0uiJVpA17ys9DRFWldEcgiut_N5QhvHCRRcp8Xow", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjpudWxsfQ.JsGUzIiM3S7cZF3LjYg-S6rYz4VXK-GBpGELzNJKOgY", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjotMTIzNDV9.BCgzT_CEurIxa0MxbS9seJ62lgfJT54P7AQpUkp65GE" + ); + } + + @ParameterizedTest + @MethodSource("jwsWithInvalidExpValues") void givenJWSWithExpTimeClaims_whenExpiryDateInvalid_thenTimeClaimNotValid(String data) throws ParseException { JWS jws = JWSFactory.parse(data); @@ -77,10 +102,22 @@ void givenJWSWithExpTimeClaims_whenExpiryDateInvalid_thenTimeClaimNotValid(Strin } @ParameterizedTest - @ValueSource(strings = { - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJleHAiOjE1MTYyMzkwMjJ9.mVGXFv3OuwtuZPsdaf_oGUYm2uOH-T-JRTDQE1c10q0", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJleHAiOjB9.eVVhRMapPD77WuMWIHKBqYw0cjJHC49On-mHEuonYjk" - }) + @MethodSource("jwsWithInvalidExpValues") + void givenJWSWithExpTimeClaims_whenExpiryDateInvalid_thenWarningsAreCorrect(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + assertThat(jws.getWarnings()).isEqualTo("'exp' value is invalid."); + } + + private static Stream jwsWithExpValuesInThePast() { + return Stream.of( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJleHAiOjE1MTYyMzkwMjJ9.mVGXFv3OuwtuZPsdaf_oGUYm2uOH-T-JRTDQE1c10q0", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJleHAiOjB9.eVVhRMapPD77WuMWIHKBqYw0cjJHC49On-mHEuonYjk" + ); + } + + @ParameterizedTest + @MethodSource("jwsWithExpValuesInThePast") void givenJWSWithExpTimeClaims_whenExpiryDateIsInThePast_thenTimeClaimNotValid(String data) throws ParseException { JWS jws = JWSFactory.parse(data); @@ -90,6 +127,14 @@ void givenJWSWithExpTimeClaims_whenExpiryDateIsInThePast_thenTimeClaimNotValid(S assertThat(timeClaim.isValid()).isFalse(); } + @ParameterizedTest + @MethodSource("jwsWithExpValuesInThePast") + void givenJWSWithExpTimeClaims_whenExpiryDateIsInThePast_thenWarningsCorrect(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + assertThat(jws.getWarnings()).isEqualTo("'exp' date is in the past."); + } + @ParameterizedTest @ValueSource(strings = { "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjoxNzE2MjM5MDIyfQ.mZ_wGwRA0BBp_m6LJOPOBfwMosKrhTf9DbnKZYTzBzg", @@ -101,14 +146,18 @@ void givenJWSWithNbfTimeClaims_thenTimeClaimsCorrect(String data) throws ParseEx assertThat(jws.timeClaims()).containsExactly(new TimeClaim(NOT_BEFORE_TIME, "1716239022", 1716239022L)); } + private static Stream jwsWithValidNbfValues() { + return Stream.of( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjoxNzE2MjM5MDIyfQ.mZ_wGwRA0BBp_m6LJOPOBfwMosKrhTf9DbnKZYTzBzg", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjoiMTcxNjIzOTAyMiJ9.JD7t6jE6sOzuaFi5Lj5e3RhnoRDDWW9QHv_U3bFgEKM", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjE1MTYyMzkwMjJ9.1OhNEYnVM64dytXGZ1kj_Z3fV73xGFxWyba52S5r7wc", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjB9.AqIAHlKSdpcDp7mD6sWsfKCQxI2hyJYsI7Y4Dh6N6pI", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjE3MjIxNjc0Nzd9.2almhOGrigs8lXH9DLKBkkUCS6r5j7zpJXYsXJN39d4" + ); + } + @ParameterizedTest - @ValueSource(strings = { - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjoxNzE2MjM5MDIyfQ.mZ_wGwRA0BBp_m6LJOPOBfwMosKrhTf9DbnKZYTzBzg", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjoiMTcxNjIzOTAyMiJ9.JD7t6jE6sOzuaFi5Lj5e3RhnoRDDWW9QHv_U3bFgEKM", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjE1MTYyMzkwMjJ9.1OhNEYnVM64dytXGZ1kj_Z3fV73xGFxWyba52S5r7wc", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjB9.AqIAHlKSdpcDp7mD6sWsfKCQxI2hyJYsI7Y4Dh6N6pI", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjE3MjIxNjc0Nzd9.2almhOGrigs8lXH9DLKBkkUCS6r5j7zpJXYsXJN39d4" - }) + @MethodSource("jwsWithValidNbfValues") void givenJWSWithNbfTimeClaims_whenNotBeforeDateInThePast_thenTimeClaimValid(String data) throws ParseException { JWS jws = JWSFactory.parse(data); @@ -119,11 +168,23 @@ void givenJWSWithNbfTimeClaims_whenNotBeforeDateInThePast_thenTimeClaimValid(Str } @ParameterizedTest - @ValueSource(strings = { - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjoiemVvbGl0ZSJ9.kmx4FteQrcATfc2Zx47WypsqrIC-e1IM4opeLJxLyrI", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjpudWxsfQ.Nyai4iIAzjR5OcUwaE8xQYbArYuBIzbKxaJDnvu1J_I", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjotMTIzNDV9.rlnrK7unNEaaghPFhNQnDp1GRbCU0rGORO2yDf5YIZk" - }) + @MethodSource("jwsWithValidNbfValues") + void givenJWSWithNbfTimeClaims_whenNotBeforeDateInThePast_thenWarningsEmpty(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + assertThat(jws.getWarnings()).isEmpty(); + } + + private static Stream jwsWithInvalidNbfValues() { + return Stream.of( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjoiemVvbGl0ZSJ9.kmx4FteQrcATfc2Zx47WypsqrIC-e1IM4opeLJxLyrI", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjpudWxsfQ.Nyai4iIAzjR5OcUwaE8xQYbArYuBIzbKxaJDnvu1J_I", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwibmJmIjotMTIzNDV9.rlnrK7unNEaaghPFhNQnDp1GRbCU0rGORO2yDf5YIZk" + ); + } + + @ParameterizedTest + @MethodSource("jwsWithInvalidNbfValues") void givenJWSWithNbfTimeClaims_whenNotBeforeDateInvalid_thenTimeClaimNotValid(String data) throws ParseException { JWS jws = JWSFactory.parse(data); @@ -134,10 +195,22 @@ void givenJWSWithNbfTimeClaims_whenNotBeforeDateInvalid_thenTimeClaimNotValid(St } @ParameterizedTest - @ValueSource(strings = { - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjIzMjIxNjc0Nzd9.cMocfo6ghvuYBqDwZ9GBdfbCnMLsUcZe2GZRuaah0-c", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjE4MjIxNjc0Nzd9._m-Rhio9DLcVRfWo7S-KIvlTk29QuDgal-tqreN4y4E" - }) + @MethodSource("jwsWithInvalidNbfValues") + void givenJWSWithNbfTimeClaims_whenNotBeforeDateInvalid_thenWarningsCorrect(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + assertThat(jws.getWarnings()).isEqualTo("'nbf' value is invalid."); + } + + private static Stream jwsWithFutureNbfValues() { + return Stream.of( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjIzMjIxNjc0Nzd9.cMocfo6ghvuYBqDwZ9GBdfbCnMLsUcZe2GZRuaah0-c", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJuYmYiOjE4MjIxNjc0Nzd9._m-Rhio9DLcVRfWo7S-KIvlTk29QuDgal-tqreN4y4E" + ); + } + + @ParameterizedTest + @MethodSource("jwsWithFutureNbfValues") void givenJWSWithNbfTimeClaims_whenNotBeforeDateIsInTheFuture_thenTimeClaimNotValid(String data) throws ParseException { JWS jws = JWSFactory.parse(data); @@ -148,10 +221,22 @@ void givenJWSWithNbfTimeClaims_whenNotBeforeDateIsInTheFuture_thenTimeClaimNotVa } @ParameterizedTest - @ValueSource(strings = { - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3MTYyMzkwMjJ9.y06rqsXv0DMutukwDaUJU0Sf-Ye3qrDkyFpOaj1J08A", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOiIxNzE2MjM5MDIyIn0.c2Ogr8QeTwopupVPqI56VaovZXE3svug2BI-Trft2EA" - }) + @MethodSource("jwsWithFutureNbfValues") + void givenJWSWithNbfTimeClaims_whenNotBeforeDateIsInTheFuture_thenWarningsCorrect(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + assertThat(jws.getWarnings()).isEqualTo("'nbf' date is in the future."); + } + + private static Stream jwsWithValidIatValues() { + return Stream.of( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3MTYyMzkwMjJ9.y06rqsXv0DMutukwDaUJU0Sf-Ye3qrDkyFpOaj1J08A", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOiIxNzE2MjM5MDIyIn0.c2Ogr8QeTwopupVPqI56VaovZXE3svug2BI-Trft2EA" + ); + } + + @ParameterizedTest + @MethodSource("jwsWithValidIatValues") void givenJWSWithNIatTimeClaims_thenTimeClaimsCorrect(String data) throws ParseException { JWS jws = JWSFactory.parse(data); @@ -159,13 +244,25 @@ void givenJWSWithNIatTimeClaims_thenTimeClaimsCorrect(String data) throws ParseE } @ParameterizedTest - @ValueSource(strings = { - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3MTYyMzkwMjJ9.y06rqsXv0DMutukwDaUJU0Sf-Ye3qrDkyFpOaj1J08A", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOiIxNzE2MjM5MDIyIn0.c2Ogr8QeTwopupVPqI56VaovZXE3svug2BI-Trft2EA", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.hqWGSaFpvbrXkOWc6lrnffhNWR19W_S1YKFBx2arWBk", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjB9.9TaucSjKgR3_gXUlzTGperv3PK9IAXO0ZVbgP9Wx4IY", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3MjIxNjc0Nzd9.SWmvLUBWE5ddBWvSEWnrHM8W3rfzyYmEQVk6-ywFFgQ" - }) + @MethodSource("jwsWithValidIatValues") + void givenJWSWithNIatTimeClaims_thenWarningsEmpty(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + assertThat(jws.getWarnings()).isEmpty(); + } + + private static Stream jwsWithValidIatDatesInThePast() { + return Stream.of( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3MTYyMzkwMjJ9.y06rqsXv0DMutukwDaUJU0Sf-Ye3qrDkyFpOaj1J08A", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOiIxNzE2MjM5MDIyIn0.c2Ogr8QeTwopupVPqI56VaovZXE3svug2BI-Trft2EA", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.hqWGSaFpvbrXkOWc6lrnffhNWR19W_S1YKFBx2arWBk", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjB9.9TaucSjKgR3_gXUlzTGperv3PK9IAXO0ZVbgP9Wx4IY", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE3MjIxNjc0Nzd9.SWmvLUBWE5ddBWvSEWnrHM8W3rfzyYmEQVk6-ywFFgQ" + ); + } + + @ParameterizedTest + @MethodSource("jwsWithValidIatDatesInThePast") void givenJWSWithIatTimeClaims_whenIssuedAtDateInThePast_thenTimeClaimValid(String data) throws ParseException { JWS jws = JWSFactory.parse(data); @@ -175,13 +272,23 @@ void givenJWSWithIatTimeClaims_whenIssuedAtDateInThePast_thenTimeClaimValid(Stri assertThat(timeClaim.isValid()).isTrue(); } + @ParameterizedTest + @MethodSource("jwsWithValidIatDatesInThePast") + void givenJWSWithIatTimeClaims_whenIssuedAtDateInThePast_thenWarningsEmpty(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + assertThat(jws.getWarnings()).isEmpty(); + } + + private static Stream jwsWithInvalidIatValues() { + return Stream.of( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOiJ6ZW9saXRlIn0.-IpgDK_M_jQJQ6FDa-wd25xGFJ2bHNthdYn1JlQNxjg", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOm51bGx9.uplfi-hImBCsHTs__K-0LU612y3EW92J1TbINlDGc-k", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOi0xMjM0NX0.gVkVHyEasqw2NZ63un0T9Yd6bW8bmY9uMYndlptiQzQ"); + } @ParameterizedTest - @ValueSource(strings = { - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOiJ6ZW9saXRlIn0.-IpgDK_M_jQJQ6FDa-wd25xGFJ2bHNthdYn1JlQNxjg", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOm51bGx9.uplfi-hImBCsHTs__K-0LU612y3EW92J1TbINlDGc-k", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOi0xMjM0NX0.gVkVHyEasqw2NZ63un0T9Yd6bW8bmY9uMYndlptiQzQ" - }) + @MethodSource("jwsWithInvalidIatValues") void givenJWSWithIatTimeClaims_whenIssuedAtDateInvalid_thenTimeClaimNotValid(String data) throws ParseException { JWS jws = JWSFactory.parse(data); @@ -191,12 +298,22 @@ void givenJWSWithIatTimeClaims_whenIssuedAtDateInvalid_thenTimeClaimNotValid(Str assertThat(timeClaim.isValid()).isFalse(); } + @ParameterizedTest + @MethodSource("jwsWithInvalidIatValues") + void givenJWSWithIatTimeClaims_whenIssuedAtDateInvalid_thenWarningsCorrect(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + assertThat(jws.getWarnings()).isEqualTo("'iat' value is invalid."); + } + private static Stream jwsWithFutureIatDates() { + return Stream.of( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjIzMjIxNjc0Nzd9.3UYGoLpdXRhnlai81VFV_iWmW90xnCimcYverY1-Zk4", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE4MjIxNjc0Nzd9._H-G7WjMbg8IVADVbz1Lsd7I_bGQZBYKPf8S2Q9vxf4" + ); + } @ParameterizedTest - @ValueSource(strings = { - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjIzMjIxNjc0Nzd9.3UYGoLpdXRhnlai81VFV_iWmW90xnCimcYverY1-Zk4", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE4MjIxNjc0Nzd9._H-G7WjMbg8IVADVbz1Lsd7I_bGQZBYKPf8S2Q9vxf4" - }) + @MethodSource("jwsWithFutureIatDates") void givenJWSWithIatTimeClaims_whenIssuedAtDateIsInTheFuture_thenTimeClaimNotValid(String data) throws ParseException { JWS jws = JWSFactory.parse(data); @@ -206,10 +323,25 @@ void givenJWSWithIatTimeClaims_whenIssuedAtDateIsInTheFuture_thenTimeClaimNotVal assertThat(timeClaim.isValid()).isFalse(); } + @ParameterizedTest + @MethodSource("jwsWithFutureIatDates") + void givenJWSWithIatTimeClaims_whenIssuedAtDateIsInTheFuture_thenWarningsCorrect(String data) throws ParseException { + JWS jws = JWSFactory.parse(data); + + assertThat(jws.getWarnings()).isEqualTo("'iat' date is in the future."); + } + @Test void givenJWE_thenTimeClaimsIsEmpty() throws ParseException { JWE jwe = JWEFactory.parse("eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTEyOEtXIn0.H3X6mT5HLgcFfzLoe4ku6Knhh9Ofv1eL.qF5-N_7K8VQ4yMSz.WXUNY6eg5fR4tc8Hqf5XDRM9ALGwcQyYG4IYwwg8Ctkx1UuxoV7t6UnemjzCj2sOYUqi3KYpDzrKVJpzokz0vcIem4lFe5N_ds8FAMpW0GSF9ePA8qvV99WaP0N2ECVPmgihvL6qwNhdptlLKtxcOpE41U5LnU22voPK55VF4_1j0WmTgWgZ7DwLDysp6EIDjrrt-DY.febBmP71KADmKRVfeSnv_g"); assertThat(jwe.timeClaims()).isEmpty(); } + + @Test + void givenJWE_thenWarningsAreEmpty() throws ParseException { + JWE jwe = JWEFactory.parse("eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTEyOEtXIn0.H3X6mT5HLgcFfzLoe4ku6Knhh9Ofv1eL.qF5-N_7K8VQ4yMSz.WXUNY6eg5fR4tc8Hqf5XDRM9ALGwcQyYG4IYwwg8Ctkx1UuxoV7t6UnemjzCj2sOYUqi3KYpDzrKVJpzokz0vcIem4lFe5N_ds8FAMpW0GSF9ePA8qvV99WaP0N2ECVPmgihvL6qwNhdptlLKtxcOpE41U5LnU22voPK55VF4_1j0WmTgWgZ7DwLDysp6EIDjrrt-DY.febBmP71KADmKRVfeSnv_g"); + + assertThat(jwe.getWarnings()).isEmpty(); + } } \ No newline at end of file