getAudience() {
* @return {@code true} if verified successfully or {@code false} if failed
*/
public boolean verify(IdToken idToken) {
- boolean tokenFieldsValid =
- (issuers == null || idToken.verifyIssuer(issuers))
- && (audience == null || idToken.verifyAudience(audience))
- && idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds);
+ boolean payloadValid = verifyPayload(idToken);
- if (!tokenFieldsValid) {
+ if (!payloadValid) {
return false;
}
@@ -254,6 +251,35 @@ public boolean verify(IdToken idToken) {
}
}
+ /**
+ * Verifies the payload of the given ID token
+ *
+ * It verifies:
+ *
+ *
+ * - The issuer is one of {@link #getIssuers()} by calling {@link
+ * IdToken#verifyIssuer(String)}.
+ *
- The audience is one of {@link #getAudience()} by calling {@link
+ * IdToken#verifyAudience(Collection)}.
+ *
- The current time against the issued at and expiration time, using the {@link #getClock()}
+ * and allowing for a time skew specified in {@link #getAcceptableTimeSkewSeconds()} , by
+ * calling {@link IdToken#verifyTime(long, long)}.
+ *
+ *
+ * Overriding is allowed, but it must call the super implementation.
+ *
+ * @param idToken ID token
+ * @return {@code true} if verified successfully or {@code false} if failed
+ */
+ protected boolean verifyPayload(IdToken idToken) {
+ boolean tokenPayloadValid =
+ (issuers == null || idToken.verifyIssuer(issuers))
+ && (audience == null || idToken.verifyAudience(audience))
+ && idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds);
+
+ return tokenPayloadValid;
+ }
+
@VisibleForTesting
boolean verifySignature(IdToken idToken) throws VerificationException {
if (Boolean.parseBoolean(environment.getVariable(SKIP_SIGNATURE_ENV_VAR))) {
@@ -272,12 +298,12 @@ boolean verifySignature(IdToken idToken) throws VerificationException {
publicKeyToUse = publicKeyCache.get(certificateLocation).get(idToken.getHeader().getKeyId());
} catch (ExecutionException | UncheckedExecutionException e) {
throw new VerificationException(
- "Error fetching PublicKey from certificate location " + certificatesLocation, e);
+ "Error fetching public key from certificate location " + certificatesLocation, e);
}
if (publicKeyToUse == null) {
throw new VerificationException(
- "Could not find PublicKey for provided keyId: " + idToken.getHeader().getKeyId());
+ "Could not find public key for provided keyId: " + idToken.getHeader().getKeyId());
}
try {
@@ -380,7 +406,7 @@ public Builder setIssuer(String issuer) {
}
/**
- * Override the location URL that contains published public keys. Defaults to well-known Google
+ * Overrides the location URL that contains published public keys. Defaults to well-known Google
* locations.
*
* @param certificatesLocation URL to published public keys
@@ -534,7 +560,7 @@ public Map load(String certificateUrl) throws Exception {
Level.WARNING,
"Failed to get a certificate from certificate location " + certificateUrl,
io);
- return ImmutableMap.of();
+ throw io;
}
ImmutableMap.Builder keyCacheBuilder = new ImmutableMap.Builder<>();
@@ -556,6 +582,13 @@ public Map load(String certificateUrl) throws Exception {
}
}
+ ImmutableMap keyCache = keyCacheBuilder.build();
+
+ if (keyCache.isEmpty()) {
+ throw new VerificationException(
+ "No valid public key returned by the keystore: " + certificateUrl);
+ }
+
return keyCacheBuilder.build();
}
diff --git a/google-oauth-client/src/test/java/com/google/api/client/auth/openidconnect/IdTokenVerifierTest.java b/google-oauth-client/src/test/java/com/google/api/client/auth/openidconnect/IdTokenVerifierTest.java
index cf32e8ee8..75f644061 100644
--- a/google-oauth-client/src/test/java/com/google/api/client/auth/openidconnect/IdTokenVerifierTest.java
+++ b/google-oauth-client/src/test/java/com/google/api/client/auth/openidconnect/IdTokenVerifierTest.java
@@ -33,12 +33,15 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
+import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Queue;
import junit.framework.TestCase;
+import org.junit.Assert;
/**
* Tests {@link IdTokenVerifier}.
@@ -101,7 +104,7 @@ public void testBuilder() throws Exception {
assertEquals(TRUSTED_CLIENT_IDS, Lists.newArrayList(verifier.getAudience()));
}
- public void testVerify() throws Exception {
+ public void testVerifyPayload() throws Exception {
MockClock clock = new MockClock();
MockEnvironment testEnvironment = new MockEnvironment();
testEnvironment.setVariable(IdTokenVerifier.SKIP_SIGNATURE_ENV_VAR, "true");
@@ -121,21 +124,31 @@ public void testVerify() throws Exception {
clock.timeMillis = 1500000L;
IdToken idToken = newIdToken(ISSUER, CLIENT_ID);
assertTrue(verifier.verify(idToken));
+ assertTrue(verifier.verifyPayload(idToken));
assertTrue(verifierFlexible.verify(newIdToken(ISSUER2, CLIENT_ID)));
+ assertTrue(verifierFlexible.verifyPayload(newIdToken(ISSUER2, CLIENT_ID)));
assertFalse(verifier.verify(newIdToken(ISSUER2, CLIENT_ID)));
+ assertFalse(verifier.verifyPayload(newIdToken(ISSUER2, CLIENT_ID)));
assertTrue(verifier.verify(newIdToken(ISSUER3, CLIENT_ID)));
+ assertTrue(verifier.verifyPayload(newIdToken(ISSUER3, CLIENT_ID)));
// audience
assertTrue(verifierFlexible.verify(newIdToken(ISSUER, CLIENT_ID2)));
+ assertTrue(verifierFlexible.verifyPayload(newIdToken(ISSUER, CLIENT_ID2)));
assertFalse(verifier.verify(newIdToken(ISSUER, CLIENT_ID2)));
+ assertFalse(verifier.verifyPayload(newIdToken(ISSUER, CLIENT_ID2)));
// time
clock.timeMillis = 700000L;
assertTrue(verifier.verify(idToken));
+ assertTrue(verifier.verifyPayload(idToken));
clock.timeMillis = 2300000L;
assertTrue(verifier.verify(idToken));
+ assertTrue(verifier.verifyPayload(idToken));
clock.timeMillis = 699999L;
assertFalse(verifier.verify(idToken));
+ assertFalse(verifier.verifyPayload(idToken));
clock.timeMillis = 2300001L;
assertFalse(verifier.verify(idToken));
+ assertFalse(verifier.verifyPayload(idToken));
}
public void testEmptyIssuersFails() throws Exception {
@@ -187,28 +200,52 @@ public void testMissingAudience() throws VerificationException {
public void testVerifyEs256TokenPublicKeyMismatch() throws Exception {
// Mock HTTP requests
- HttpTransportFactory httpTransportFactory =
- new HttpTransportFactory() {
+ MockLowLevelHttpRequest failedRequest =
+ new MockLowLevelHttpRequest() {
@Override
- public HttpTransport create() {
- return new MockHttpTransport() {
- @Override
- public LowLevelHttpRequest buildRequest(String method, String url)
- throws IOException {
- return new MockLowLevelHttpRequest() {
- @Override
- public LowLevelHttpResponse execute() throws IOException {
- MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
- response.setStatusCode(200);
- response.setContentType("application/json");
- response.setContent("");
- return response;
- }
- };
- }
- };
+ public LowLevelHttpResponse execute() throws IOException {
+ throw new IOException("test io exception");
+ }
+ };
+
+ MockLowLevelHttpRequest badRequest =
+ new MockLowLevelHttpRequest() {
+ @Override
+ public LowLevelHttpResponse execute() throws IOException {
+ MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
+ response.setStatusCode(404);
+ response.setContentType("application/json");
+ response.setContent("");
+ return response;
+ }
+ };
+
+ MockLowLevelHttpRequest emptyRequest =
+ new MockLowLevelHttpRequest() {
+ @Override
+ public LowLevelHttpResponse execute() throws IOException {
+ MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
+ response.setStatusCode(200);
+ response.setContentType("application/json");
+ response.setContent("{\"keys\":[]}");
+ return response;
+ }
+ };
+
+ MockLowLevelHttpRequest goodRequest =
+ new MockLowLevelHttpRequest() {
+ @Override
+ public LowLevelHttpResponse execute() throws IOException {
+ MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
+ response.setStatusCode(200);
+ response.setContentType("application/json");
+ response.setContent(readResourceAsString("iap_keys.json"));
+ return response;
}
};
+
+ HttpTransportFactory httpTransportFactory =
+ mockTransport(failedRequest, badRequest, emptyRequest, goodRequest);
IdTokenVerifier tokenVerifier =
new IdTokenVerifier.Builder()
.setClock(FIXED_CLOCK)
@@ -219,8 +256,24 @@ public LowLevelHttpResponse execute() throws IOException {
tokenVerifier.verifySignature(IdToken.parse(JSON_FACTORY, ES256_TOKEN));
fail("Should have failed verification");
} catch (VerificationException ex) {
- assertTrue(ex.getMessage().contains("Error fetching PublicKey"));
+ assertTrue(ex.getMessage().contains("Error fetching public key"));
+ }
+
+ try {
+ tokenVerifier.verifySignature(IdToken.parse(JSON_FACTORY, ES256_TOKEN));
+ fail("Should have failed verification");
+ } catch (VerificationException ex) {
+ assertTrue(ex.getMessage().contains("Error fetching public key"));
}
+
+ try {
+ tokenVerifier.verifySignature(IdToken.parse(JSON_FACTORY, ES256_TOKEN));
+ fail("Should have failed verification");
+ } catch (VerificationException ex) {
+ assertTrue(ex.getCause().getMessage().contains("No valid public key returned"));
+ }
+
+ Assert.assertTrue(tokenVerifier.verifySignature(IdToken.parse(JSON_FACTORY, ES256_TOKEN)));
}
public void testVerifyEs256Token() throws VerificationException, IOException {
@@ -284,6 +337,25 @@ static String readResourceAsString(String resourceName) throws IOException {
}
}
+ static HttpTransportFactory mockTransport(LowLevelHttpRequest... requests) {
+ final LowLevelHttpRequest firstRequest = requests[0];
+ final Queue requestQueue = new ArrayDeque<>();
+ for (LowLevelHttpRequest request : requests) {
+ requestQueue.add(request);
+ }
+ return new HttpTransportFactory() {
+ @Override
+ public HttpTransport create() {
+ return new MockHttpTransport() {
+ @Override
+ public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
+ return requestQueue.poll();
+ }
+ };
+ }
+ };
+ }
+
static HttpTransportFactory mockTransport(String url, String certificates) {
final String certificatesContent = certificates;
final String certificatesUrl = url;
diff --git a/pom.xml b/pom.xml
index 951e06e69..24086c6aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.google.oauth-client
google-oauth-client-parent
- 1.33.3
+ 1.34.0
pom
Parent for the Google OAuth Client Library for Java
Google OAuth Client Library for Java
@@ -184,7 +184,7 @@
org.sonatype.plugins
nexus-staging-maven-plugin
- 1.6.12
+ 1.6.13
true
ossrh
@@ -220,7 +220,7 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 3.3.2
+ 3.4.0
attach-javadocs
@@ -272,12 +272,12 @@
org.apache.maven.plugins
maven-project-info-reports-plugin
- 3.2.2
+ 3.3.0
org.apache.maven.plugins
maven-site-plugin
- 3.11.0
+ 3.12.0
@@ -459,9 +459,9 @@
- google-api-java-client/google-api-client-assembly/android-properties (make the filenames match the version here)
- Internally, update the default features.json file
-->
- 2.0.4
+ 2.0.5
UTF-8
- 1.41.7
+ 1.41.8
3.0.2
31.1-android
1.1.4c
@@ -470,7 +470,7 @@
1.1
3.2.15
3.2.8
- 5.2.10
+ 5.2.11
5.2.1
2.5
false
@@ -534,7 +534,7 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 3.3.2
+ 3.4.0
com.microsoft.doclet.DocFxDoclet
false
diff --git a/samples/dailymotion-cmdline-sample/pom.xml b/samples/dailymotion-cmdline-sample/pom.xml
index d167f15c5..28a9e1c9d 100644
--- a/samples/dailymotion-cmdline-sample/pom.xml
+++ b/samples/dailymotion-cmdline-sample/pom.xml
@@ -4,7 +4,7 @@
com.google.oauth-client
google-oauth-client-parent
- 1.33.3
+ 1.34.0
../../pom.xml
dailymotion-simple-cmdline-sample
@@ -76,7 +76,7 @@
org.sonatype.plugins
nexus-staging-maven-plugin
- 1.6.12
+ 1.6.13
true
diff --git a/samples/keycloak-pkce-cmdline-sample/pom.xml b/samples/keycloak-pkce-cmdline-sample/pom.xml
index 617f2da4f..4367cda9e 100644
--- a/samples/keycloak-pkce-cmdline-sample/pom.xml
+++ b/samples/keycloak-pkce-cmdline-sample/pom.xml
@@ -4,7 +4,7 @@
com.google.oauth-client
google-oauth-client-parent
- 1.33.3
+ 1.34.0
../../pom.xml
keycloak-pkce-cmdline-sample
@@ -76,7 +76,7 @@
org.sonatype.plugins
nexus-staging-maven-plugin
- 1.6.12
+ 1.6.13
true
diff --git a/samples/pom.xml b/samples/pom.xml
index d53604cff..e06c7da36 100644
--- a/samples/pom.xml
+++ b/samples/pom.xml
@@ -46,7 +46,7 @@
org.sonatype.plugins
nexus-staging-maven-plugin
- 1.6.12
+ 1.6.13
true
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index 42c5cfb43..eb14ac814 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -30,7 +30,7 @@
com.google.cloud
libraries-bom
- 25.1.0
+ 25.3.0
pom
import
@@ -41,7 +41,7 @@
com.google.oauth-client
google-oauth-client
- 1.33.2
+ 1.33.3
diff --git a/versions.txt b/versions.txt
index 4ba075e33..1607fd6e4 100644
--- a/versions.txt
+++ b/versions.txt
@@ -1,4 +1,4 @@
# Format:
# module:released-version:current-version
-google-oauth-client:1.33.3:1.33.3
+google-oauth-client:1.34.0:1.34.0