From acb985f4323c6e6e4cf8e6a176018aa803c369ca Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Wed, 23 Mar 2022 03:16:42 -0700 Subject: [PATCH 01/17] verifier signature fix port --- .gitignore | 7 + google-oauth-client/pom.xml | 4 + .../openidconnect/HttpTransportFactory.java | 50 ++++ .../auth/openidconnect/IdTokenVerifier.java | 260 +++++++++++++++++- .../openidconnect/IdTokenVerifierTest.java | 149 +++++++++- .../resources/aws_security_credentials.json | 9 + .../src/test/resources/client_secret.json | 16 ++ .../src/test/resources/federated_keys.json | 20 ++ .../src/test/resources/iap_keys.json | 49 ++++ .../test/resources/legacy_federated_keys.json | 4 + .../test/resources/service_account_keys.json | 4 + 11 files changed, 569 insertions(+), 3 deletions(-) create mode 100644 google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/HttpTransportFactory.java create mode 100644 google-oauth-client/src/test/resources/aws_security_credentials.json create mode 100644 google-oauth-client/src/test/resources/client_secret.json create mode 100644 google-oauth-client/src/test/resources/federated_keys.json create mode 100644 google-oauth-client/src/test/resources/iap_keys.json create mode 100644 google-oauth-client/src/test/resources/legacy_federated_keys.json create mode 100644 google-oauth-client/src/test/resources/service_account_keys.json diff --git a/.gitignore b/.gitignore index 967c2ef30..bbc39b84f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,11 @@ bin/ .idea/ *.iml .checkstyle +.factorypath + +# MacOS +.DS_Store + +# VS Code +.vscode/ diff --git a/google-oauth-client/pom.xml b/google-oauth-client/pom.xml index 213bf4bc9..38aac68e5 100644 --- a/google-oauth-client/pom.xml +++ b/google-oauth-client/pom.xml @@ -93,5 +93,9 @@ com.google.guava guava + + com.google.http-client + google-http-client-gson + diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/HttpTransportFactory.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/HttpTransportFactory.java new file mode 100644 index 000000000..c3d7f46a5 --- /dev/null +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/HttpTransportFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright 2022, Google Inc. 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 Google Inc. 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. + */ + +package com.google.api.client.auth.openidconnect; + +import com.google.api.client.http.HttpTransport; + +/** + * A base interface for all {@link HttpTransport} factories. + * + *

Implementation must provide a public no-arg constructor. Loading of a factory implementation + * is done via {@link java.util.ServiceLoader}. + */ +public interface HttpTransportFactory { + + /** + * Creates a {@code HttpTransport} instance. + * + * @return The HttpTransport instance. + */ + HttpTransport create(); +} \ No newline at end of file diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index 6f97418b2..813f93f59 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -14,11 +14,51 @@ package com.google.api.client.auth.openidconnect; +import com.google.api.client.http.GenericUrl; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.GenericJson; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.client.json.webtoken.JsonWebSignature; +import com.google.api.client.json.webtoken.JsonWebSignature.Header; +import com.google.api.client.util.Base64; import com.google.api.client.util.Beta; import com.google.api.client.util.Clock; +import com.google.api.client.util.Key; import com.google.api.client.util.Preconditions; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.UncheckedExecutionException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.InvalidParameterSpecException; +import java.security.spec.RSAPublicKeySpec; import java.util.Collection; import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; /** * {@link Beta}
@@ -48,12 +88,18 @@ */ @Beta public class IdTokenVerifier { + private static final String IAP_CERT_URL = "https://www.gstatic.com/iap/verify/public_key-jwk"; + private static final String FEDERATED_SIGNON_CERT_URL = + "https://www.googleapis.com/oauth2/v3/certs"; + private static final Set SUPPORTED_ALGORITHMS = ImmutableSet.of("RS256", "ES256"); /** Default value for seconds of time skew to accept when verifying time (5 minutes). */ public static final long DEFAULT_TIME_SKEW_SECONDS = 300; /** Clock to use for expiration checks. */ private final Clock clock; + private final String certificatesLocation; + private final LoadingCache> publicKeyCache; /** Seconds of time skew to accept when verifying time. */ private final long acceptableTimeSkewSeconds; @@ -76,11 +122,16 @@ public IdTokenVerifier() { /** @param builder builder */ protected IdTokenVerifier(Builder builder) { + this.certificatesLocation = builder.certificatesLocation; clock = builder.clock; acceptableTimeSkewSeconds = builder.acceptableTimeSkewSeconds; issuers = builder.issuers == null ? null : Collections.unmodifiableCollection(builder.issuers); audience = builder.audience == null ? null : Collections.unmodifiableCollection(builder.audience); + this.publicKeyCache = + CacheBuilder.newBuilder() + .expireAfterWrite(1, TimeUnit.HOURS) + .build(new PublicKeyLoader(builder.httpTransportFactory)); } /** Returns the clock. */ @@ -141,10 +192,52 @@ public final Collection getAudience() { * @param idToken ID token * @return {@code true} if verified successfully or {@code false} if failed */ - public boolean verify(IdToken idToken) { - return (issuers == null || idToken.verifyIssuer(issuers)) + public boolean verify(IdToken idToken) throws VerificationException { + boolean simpleChecks = (issuers == null || idToken.verifyIssuer(issuers)) && (audience == null || idToken.verifyAudience(audience)) && idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds); + + if (!simpleChecks) { + return false; + } + + PublicKey publicKeyToUse = null; + try { + String certificateLocation = getCertificateLocation(idToken.getHeader()); + publicKeyToUse = + publicKeyCache.get(certificateLocation).get(idToken.getHeader().getKeyId()); + } catch (ExecutionException | UncheckedExecutionException e) { + throw new VerificationException("Error fetching PublicKey from certificate location", e); + } + + if (publicKeyToUse == null) { + throw new VerificationException( + "Could not find PublicKey for provided keyId: " + + idToken.getHeader().getKeyId()); + } + + try { + if (idToken.verifySignature(publicKeyToUse)) { + return true; + } + throw new VerificationException("Invalid signature"); + } catch (GeneralSecurityException e) { + throw new VerificationException("Error validating token", e); + } + } + + private String getCertificateLocation(Header header) + throws VerificationException { + if (certificatesLocation != null) return certificatesLocation; + + switch (header.getAlgorithm()) { + case "RS256": + return FEDERATED_SIGNON_CERT_URL; + case "ES256": + return IAP_CERT_URL; + } + + throw new VerificationException("Unknown algorithm"); } /** @@ -161,6 +254,8 @@ public static class Builder { /** Clock. */ Clock clock = Clock.SYSTEM; + String certificatesLocation; + /** Seconds of time skew to accept when verifying time. */ long acceptableTimeSkewSeconds = DEFAULT_TIME_SKEW_SECONDS; @@ -169,6 +264,7 @@ public static class Builder { /** List of trusted audience client IDs or {@code null} to suppress the audience check. */ Collection audience; + HttpTransportFactory httpTransportFactory; /** Builds a new instance of {@link IdTokenVerifier}. */ public IdTokenVerifier build() { @@ -216,6 +312,18 @@ public Builder setIssuer(String issuer) { } } + /** + * Override the location URL that contains published public keys. Defaults to well-known Google + * locations. + * + * @param certificatesLocation URL to published public keys + * @return the builder + */ + public Builder setCertificatesLocation(String certificatesLocation) { + this.certificatesLocation = certificatesLocation; + return this; + } + /** * Returns the equivalent expected issuers or {@code null} if issuer check suppressed. * @@ -280,5 +388,153 @@ public Builder setAcceptableTimeSkewSeconds(long acceptableTimeSkewSeconds) { this.acceptableTimeSkewSeconds = acceptableTimeSkewSeconds; return this; } + + /** + * Set the HttpTransportFactory used for requesting public keys from the certificate URL. Used + * mostly for testing. + * + * @param httpTransportFactory the HttpTransportFactory used to build certificate URL requests + * @return the builder + */ + public Builder setHttpTransportFactory(HttpTransportFactory httpTransportFactory) { + this.httpTransportFactory = httpTransportFactory; + return this; + } + } + + /** Custom CacheLoader for mapping certificate urls to the contained public keys. */ + static class PublicKeyLoader extends CacheLoader> { + private final HttpTransportFactory httpTransportFactory; + + /** + * Data class used for deserializing a JSON Web Key Set (JWKS) from an external HTTP request. + */ + public static class JsonWebKeySet extends GenericJson { + @Key + public List keys; + } + + /** Data class used for deserializing a single JSON Web Key. */ + public static class JsonWebKey { + @Key public String alg; + + @Key public String crv; + + @Key public String kid; + + @Key public String kty; + + @Key public String use; + + @Key public String x; + + @Key public String y; + + @Key public String e; + + @Key public String n; + } + + PublicKeyLoader(HttpTransportFactory httpTransportFactory) { + super(); + this.httpTransportFactory = httpTransportFactory; + } + + @Override + public Map load(String certificateUrl) throws Exception { + HttpTransport httpTransport = new NetHttpTransport(); + JsonWebKeySet jwks; + try { + HttpRequest request = + httpTransport + .createRequestFactory() + .buildGetRequest(new GenericUrl(certificateUrl)) + .setParser(GsonFactory.getDefaultInstance().createJsonObjectParser()); + HttpResponse response = request.execute(); + jwks = response.parseAs(JsonWebKeySet.class); + } catch (IOException io) { + return ImmutableMap.of(); + } + + ImmutableMap.Builder keyCacheBuilder = new ImmutableMap.Builder<>(); + if (jwks.keys == null) { + // Fall back to x509 formatted specification + for (String keyId : jwks.keySet()) { + String publicKeyPem = (String) jwks.get(keyId); + keyCacheBuilder.put(keyId, buildPublicKey(publicKeyPem)); + } + } else { + for (JsonWebKey key : jwks.keys) { + try { + keyCacheBuilder.put(key.kid, buildPublicKey(key)); + } catch (NoSuchAlgorithmException + | InvalidKeySpecException + | InvalidParameterSpecException ignored) { + ignored.printStackTrace(); + } + } + } + + return keyCacheBuilder.build(); + } + + private PublicKey buildPublicKey(JsonWebKey key) + throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException { + if ("ES256".equals(key.alg)) { + return buildEs256PublicKey(key); + } else if ("RS256".equals((key.alg))) { + return buildRs256PublicKey(key); + } else { + return null; + } + } + + private PublicKey buildPublicKey(String publicPem) + throws CertificateException, UnsupportedEncodingException { + return CertificateFactory.getInstance("X.509") + .generateCertificate(new ByteArrayInputStream(publicPem.getBytes("UTF-8"))) + .getPublicKey(); + } + + private PublicKey buildRs256PublicKey(JsonWebKey key) + throws NoSuchAlgorithmException, InvalidKeySpecException { + com.google.common.base.Preconditions.checkArgument("RSA".equals(key.kty)); + com.google.common.base.Preconditions.checkNotNull(key.e); + com.google.common.base.Preconditions.checkNotNull(key.n); + + BigInteger modulus = new BigInteger(1, Base64.decodeBase64(key.n)); + BigInteger exponent = new BigInteger(1, Base64.decodeBase64(key.e)); + + RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent); + KeyFactory factory = KeyFactory.getInstance("RSA"); + return factory.generatePublic(spec); + } + + private PublicKey buildEs256PublicKey(JsonWebKey key) + throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException { + com.google.common.base.Preconditions.checkArgument("EC".equals(key.kty)); + com.google.common.base.Preconditions.checkArgument("P-256".equals(key.crv)); + + BigInteger x = new BigInteger(1, Base64.decodeBase64(key.x)); + BigInteger y = new BigInteger(1, Base64.decodeBase64(key.y)); + ECPoint pubPoint = new ECPoint(x, y); + AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC"); + parameters.init(new ECGenParameterSpec("secp256r1")); + ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class); + ECPublicKeySpec pubSpec = new ECPublicKeySpec(pubPoint, ecParameters); + KeyFactory kf = KeyFactory.getInstance("EC"); + return kf.generatePublic(pubSpec); + } + } + + /** Custom exception for wrapping all verification errors. */ + public static class VerificationException extends Exception { + public VerificationException(String message) { + super(message); + } + + public VerificationException(String message, Throwable cause) { + super(message, cause); + } } } 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 9db0e982a..c8dd34240 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 @@ -15,9 +15,19 @@ package com.google.api.client.auth.openidconnect; import com.google.api.client.auth.openidconnect.IdToken.Payload; +import com.google.api.client.auth.openidconnect.IdTokenVerifier.VerificationException; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.LowLevelHttpRequest; +import com.google.api.client.http.LowLevelHttpResponse; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.gson.GsonFactory; import com.google.api.client.json.webtoken.JsonWebSignature.Header; +import com.google.api.client.testing.http.MockHttpTransport; +import com.google.api.client.testing.http.MockLowLevelHttpRequest; +import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.api.client.util.Clock; import com.google.api.client.util.Lists; +import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -39,6 +49,24 @@ public class IdTokenVerifierTest extends TestCase { private static final String ISSUER2 = ISSUER + "2"; private static final String ISSUER3 = ISSUER + "3"; + private static final String ES256_TOKEN = + "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im1wZjBEQSJ9.eyJhdWQiOiIvcHJvamVjdHMvNjUyNTYyNzc2Nzk4L2FwcHMvY2xvdWQtc2FtcGxlcy10ZXN0cy1waHAtaWFwIiwiZW1haWwiOiJjaGluZ29yQGdvb2dsZS5jb20iLCJleHAiOjE1ODQwNDc2MTcsImdvb2dsZSI6eyJhY2Nlc3NfbGV2ZWxzIjpbImFjY2Vzc1BvbGljaWVzLzUxODU1MTI4MDkyNC9hY2Nlc3NMZXZlbHMvcmVjZW50U2VjdXJlQ29ubmVjdERhdGEiLCJhY2Nlc3NQb2xpY2llcy81MTg1NTEyODA5MjQvYWNjZXNzTGV2ZWxzL3Rlc3ROb09wIiwiYWNjZXNzUG9saWNpZXMvNTE4NTUxMjgwOTI0L2FjY2Vzc0xldmVscy9ldmFwb3JhdGlvblFhRGF0YUZ1bGx5VHJ1c3RlZCJdfSwiaGQiOiJnb29nbGUuY29tIiwiaWF0IjoxNTg0MDQ3MDE3LCJpc3MiOiJodHRwczovL2Nsb3VkLmdvb2dsZS5jb20vaWFwIiwic3ViIjoiYWNjb3VudHMuZ29vZ2xlLmNvbToxMTIxODE3MTI3NzEyMDE5NzI4OTEifQ.yKNtdFY5EKkRboYNexBdfugzLhC3VuGyFcuFYA8kgpxMqfyxa41zkML68hYKrWu2kOBTUW95UnbGpsIi_u1fiA"; + + private static final String FEDERATED_SIGNON_RS256_TOKEN = + "eyJhbGciOiJSUzI1NiIsImtpZCI6ImY5ZDk3YjRjYWU5MGJjZDc2YWViMjAwMjZmNmI3NzBjYWMyMjE3ODMiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2V4YW1wbGUuY29tL3BhdGgiLCJhenAiOiJpbnRlZ3JhdGlvbi10ZXN0c0BjaGluZ29yLXRlc3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJlbWFpbCI6ImludGVncmF0aW9uLXRlc3RzQGNoaW5nb3ItdGVzdC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1ODc2Mjk4ODgsImlhdCI6MTU4NzYyNjI4OCwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTA0MDI5MjkyODUzMDk5OTc4MjkzIn0.Pj4KsJh7riU7ZIbPMcHcHWhasWEcbVjGP4yx_5E0iOpeDalTdri97E-o0dSSkuVX2FeBIgGUg_TNNgJ3YY97T737jT5DUYwdv6M51dDlLmmNqlu_P6toGCSRC8-Beu5gGmqS2Y82TmpHH9Vhoh5PsK7_rVHk8U6VrrVVKKTWm_IzTFhqX1oYKPdvfyaNLsXPbCt_NFE0C3DNmFkgVhRJu7LtzQQN-ghaqd3Ga3i6KH222OEI_PU4BUTvEiNOqRGoMlT_YOsyFN3XwqQ6jQGWhhkArL1z3CG2BVQjHTKpgVsRyy_H6WTZiju2Q-XWobgH-UPSZbyymV8-cFT9XKEtZQ"; + private static final String LEGACY_FEDERATED_SIGNON_CERT_URL = + "https://www.googleapis.com/oauth2/v1/certs"; + + private static final String SERVICE_ACCOUNT_RS256_TOKEN = + "eyJhbGciOiJSUzI1NiIsImtpZCI6IjJlZjc3YjM4YTFiMDM3MDQ4NzA0MzkxNmFjYmYyN2Q3NGVkZDA4YjEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2V4YW1wbGUuY29tL2F1ZGllbmNlIiwiZXhwIjoxNTg3NjMwNTQzLCJpYXQiOjE1ODc2MjY5NDMsImlzcyI6InNvbWUgaXNzdWVyIiwic3ViIjoic29tZSBzdWJqZWN0In0.gGOQW0qQgs4jGUmCsgRV83RqsJLaEy89-ZOG6p1u0Y26FyY06b6Odgd7xXLsSTiiSnch62dl0Lfi9D0x2ByxvsGOCbovmBl2ZZ0zHr1wpc4N0XS9lMUq5RJQbonDibxXG4nC2zroDfvD0h7i-L8KMXeJb9pYwW7LkmrM_YwYfJnWnZ4bpcsDjojmPeUBlACg7tjjOgBFbyQZvUtaERJwSRlaWibvNjof7eCVfZChE0PwBpZc_cGqSqKXv544L4ttqdCnmONjqrTATXwC4gYxruevkjHfYI5ojcQmXoWDJJ0-_jzfyPE4MFFdCFgzLgnfIOwe5ve0MtquKuv2O0pgvg"; + private static final String SERVICE_ACCOUNT_CERT_URL = + "https://www.googleapis.com/robot/v1/metadata/x509/integration-tests%40chingor-test.iam.gserviceaccount.com"; + + private static final List ALL_TOKENS = + Arrays.asList(ES256_TOKEN, FEDERATED_SIGNON_RS256_TOKEN, SERVICE_ACCOUNT_RS256_TOKEN); + + static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); + private static IdToken newIdToken(String issuer, String audience) { Payload payload = new Payload(); payload.setIssuer(issuer); @@ -138,7 +166,7 @@ public void testBuilderSetNullIssuers() throws Exception { assertNull(verifier.getIssuer()); } - public void testMissingAudience() { + public void testMissingAudience() throws VerificationException { IdToken idToken = newIdToken(ISSUER, null); MyClock clock = new MyClock(); @@ -151,4 +179,123 @@ public void testMissingAudience() { .build(); assertFalse(verifier.verify(idToken)); } + + public void testVerifyEs256TokenPublicKeyMismatch() throws Exception { + // Mock HTTP requests + HttpTransportFactory httpTransportFactory = + new HttpTransportFactory() { + @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; + } + }; + } + }; + } + }; + MyClock clock = new MyClock(); + clock.timeMillis = 1582704000000L; + IdTokenVerifier tokenVerifier = + new IdTokenVerifier.Builder() + .setClock(clock) + .setHttpTransportFactory(httpTransportFactory) + .build(); + + try { + tokenVerifier.verify(IdToken.parse(JSON_FACTORY, ES256_TOKEN)); + } catch (VerificationException ex) { + assertTrue(ex.getMessage().contains("Error fetching PublicKey")); + } + throw new Exception("Should have failed verification"); + } + + // @Test + // void verifyEs256Token() throws TokenVerifier.VerificationException, IOException { + // HttpTransportFactory httpTransportFactory = + // mockTransport( + // "https://www.gstatic.com/iap/verify/public_key-jwk", + // readResourceAsString("iap_keys.json")); + // TokenVerifier tokenVerifier = + // TokenVerifier.newBuilder() + // .setClock(FIXED_CLOCK) + // .setHttpTransportFactory(httpTransportFactory) + // .build(); + // assertNotNull(tokenVerifier.verify(ES256_TOKEN)); + // } + // + // @Test + // void verifyRs256Token() throws TokenVerifier.VerificationException, IOException { + // HttpTransportFactory httpTransportFactory = + // mockTransport( + // "https://www.googleapis.com/oauth2/v3/certs", + // readResourceAsString("federated_keys.json")); + // TokenVerifier tokenVerifier = + // TokenVerifier.newBuilder() + // .setClock(FIXED_CLOCK) + // .setHttpTransportFactory(httpTransportFactory) + // .build(); + // assertNotNull(tokenVerifier.verify(FEDERATED_SIGNON_RS256_TOKEN)); + // } + // + // @Test + // void verifyRs256TokenWithLegacyCertificateUrlFormat() + // throws TokenVerifier.VerificationException, IOException { + // HttpTransportFactory httpTransportFactory = + // mockTransport( + // LEGACY_FEDERATED_SIGNON_CERT_URL, readResourceAsString("legacy_federated_keys.json")); + // TokenVerifier tokenVerifier = + // TokenVerifier.newBuilder() + // .setCertificatesLocation(LEGACY_FEDERATED_SIGNON_CERT_URL) + // .setClock(FIXED_CLOCK) + // .setHttpTransportFactory(httpTransportFactory) + // .build(); + // assertNotNull(tokenVerifier.verify(FEDERATED_SIGNON_RS256_TOKEN)); + // } + // + // @Test + // void verifyServiceAccountRs256Token() throws TokenVerifier.VerificationException, IOException { + // TokenVerifier tokenVerifier = + // TokenVerifier.newBuilder() + // .setClock(FIXED_CLOCK) + // .setCertificatesLocation(SERVICE_ACCOUNT_CERT_URL) + // .build(); + // assertNotNull(tokenVerifier.verify(SERVICE_ACCOUNT_RS256_TOKEN)); + // } + + static HttpTransportFactory mockTransport(String url, String certificates) { + final String certificatesContent = certificates; + final String certificatesUrl = url; + return new HttpTransportFactory() { + @Override + public HttpTransport create() { + return new MockHttpTransport() { + @Override + public LowLevelHttpRequest buildRequest(String method, String url) throws IOException { + assertEquals(certificatesUrl, url); + return new MockLowLevelHttpRequest() { + @Override + public LowLevelHttpResponse execute() throws IOException { + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + response.setStatusCode(200); + response.setContentType("application/json"); + response.setContent(certificatesContent); + return response; + } + }; + } + }; + } + }; + } } diff --git a/google-oauth-client/src/test/resources/aws_security_credentials.json b/google-oauth-client/src/test/resources/aws_security_credentials.json new file mode 100644 index 000000000..76e7688a3 --- /dev/null +++ b/google-oauth-client/src/test/resources/aws_security_credentials.json @@ -0,0 +1,9 @@ +{ + "Code" : "Success", + "LastUpdated" : "2020-08-11T19:33:07Z", + "Type" : "AWS-HMAC", + "AccessKeyId" : "ASIARD4OQDT6A77FR3CL", + "SecretAccessKey" : "Y8AfSaucF37G4PpvfguKZ3/l7Id4uocLXxX0+VTx", + "Token" : "IQoJb3JpZ2luX2VjEIz//////////wEaCXVzLWVhc3QtMiJGMEQCIH7MHX/Oy/OB8OlLQa9GrqU1B914+iMikqWQW7vPCKlgAiA/Lsv8Jcafn14owfxXn95FURZNKaaphj0ykpmS+Ki+CSq0AwhlEAAaDDA3NzA3MTM5MTk5NiIMx9sAeP1ovlMTMKLjKpEDwuJQg41/QUKx0laTZYjPlQvjwSqS3OB9P1KAXPWSLkliVMMqaHqelvMF/WO/glv3KwuTfQsavRNs3v5pcSEm4SPO3l7mCs7KrQUHwGP0neZhIKxEXy+Ls//1C/Bqt53NL+LSbaGv6RPHaX82laz2qElphg95aVLdYgIFY6JWV5fzyjgnhz0DQmy62/Vi8pNcM2/VnxeCQ8CC8dRDSt52ry2v+nc77vstuI9xV5k8mPtnaPoJDRANh0bjwY5Sdwkbp+mGRUJBAQRlNgHUJusefXQgVKBCiyJY4w3Csd8Bgj9IyDV+Azuy1jQqfFZWgP68LSz5bURyIjlWDQunO82stZ0BgplKKAa/KJHBPCp8Qi6i99uy7qh76FQAqgVTsnDuU6fGpHDcsDSGoCls2HgZjZFPeOj8mmRhFk1Xqvkbjuz8V1cJk54d3gIJvQt8gD2D6yJQZecnuGWd5K2e2HohvCc8Fc9kBl1300nUJPV+k4tr/A5R/0QfEKOZL1/k5lf1g9CREnrM8LVkGxCgdYMxLQow1uTL+QU67AHRRSp5PhhGX4Rek+01vdYSnJCMaPhSEgcLqDlQkhk6MPsyT91QMXcWmyO+cAZwUPwnRamFepuP4K8k2KVXs/LIJHLELwAZ0ekyaS7CptgOqS7uaSTFG3U+vzFZLEnGvWQ7y9IPNQZ+Dffgh4p3vF4J68y9049sI6Sr5d5wbKkcbm8hdCDHZcv4lnqohquPirLiFQ3q7B17V9krMPu3mz1cg4Ekgcrn/E09NTsxAqD8NcZ7C7ECom9r+X3zkDOxaajW6hu3Az8hGlyylDaMiFfRbBJpTIlxp7jfa7CxikNgNtEKLH9iCzvuSg2vhA==", + "Expiration" : "2020-08-11T07:35:49Z" +} \ No newline at end of file diff --git a/google-oauth-client/src/test/resources/client_secret.json b/google-oauth-client/src/test/resources/client_secret.json new file mode 100644 index 000000000..66fae0a6e --- /dev/null +++ b/google-oauth-client/src/test/resources/client_secret.json @@ -0,0 +1,16 @@ +{ + "web": { + "client_id":"ya29.1.AADtN_UtlxN3PuGAxrN2XQnZTVRvDyVWnYq4I6dws", + "auth_uri":"https://accounts.google.com/o/oauth2/auth", + "token_uri":"https://accounts.google.com/o/oauth2/token", + "auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs", + "client_secret":"jakuaL9YyieakhECKL2SwZcu", + "redirect_uris":[ + "http://example.appspot.com/oauth2callback", + "http://localhost:8080/oauth2callback" + ], + "javascript_origins":[ + "https://www.example.com" + ] + } +} diff --git a/google-oauth-client/src/test/resources/federated_keys.json b/google-oauth-client/src/test/resources/federated_keys.json new file mode 100644 index 000000000..9986a8e0f --- /dev/null +++ b/google-oauth-client/src/test/resources/federated_keys.json @@ -0,0 +1,20 @@ +{ + "keys": [ + { + "kid": "f9d97b4cae90bcd76aeb20026f6b770cac221783", + "e": "AQAB", + "kty": "RSA", + "alg": "RS256", + "n": "ya_7gVJrvqFp5xfYPOco8gBLY38kQDlTlT6ueHtUtbTkRVE1X5tFmPqChnX7wWd2fK7MS4-nclYaGLL7IvJtN9tjrD0h_3_HvnrRZTaVyS-yfWqCQDRq_0VW1LBEygwYRqbO2T0lOocTY-5qUosDvJfe-o-lQYMH7qtDAyiq9XprVzKYTfS545BTECXi0he9ikJl5Q_RAP1BZoaip8F0xX5Y_60G90VyXFWuy16nm5ASW8fwqzdn1lL_ogiO1LirgBFFEXz_t4PwmjWzfQwkoKv4Ab_l9u2FdAoKtFH2CwKaGB8hatIK3bOAJJgRebeU3w6Ah3gxRfi8HWPHbAGjtw", + "use": "sig" + }, + { + "kid": "28b741e8de984a47159f19e6d7783e9d4fa810db", + "e": "AQAB", + "kty": "RSA", + "alg": "RS256", + "n": "zc4ELn-9nLzCZb4PdXGVhtUtzwmQI8HZH8tOIEg9omx6CW-PZ5xtVQ5O5EBG2AA5_K-aOWvVEWyfeHe8WwZltM1cXu6QNdXbpVVYeZ0th9hm7ZflNz7h1PMM9lNXLJjokax5gxGskc8CsjhkwurEot1TD2zbGIQsOYoebQTvJ2AYxIjk77BU20nLplurge8jrK-V1G3zJlp0xIKqxjsfIFYm1Mp-HQhJzdMbjNEScs0dDT4rPxdA-wOVGix0wrPdIE1gM4GxZ7AlSZ7IcjuYMZIe6d6oAeKG0FG0avbtipAQglxTHM3UOge6PmThr_mmiI82oLqGutul-XYgy1S2NQ", + "use": "sig" + } + ] +} \ No newline at end of file diff --git a/google-oauth-client/src/test/resources/iap_keys.json b/google-oauth-client/src/test/resources/iap_keys.json new file mode 100644 index 000000000..2ba2bfa01 --- /dev/null +++ b/google-oauth-client/src/test/resources/iap_keys.json @@ -0,0 +1,49 @@ +{ + "keys" : [ + { + "alg" : "ES256", + "crv" : "P-256", + "kid" : "2nMJtw", + "kty" : "EC", + "use" : "sig", + "x" : "9e1x7YRZg53A5zIJ0p2ZQ9yTrgPLGIf4ntOk-4O2R28", + "y" : "q8iDm7nsnpz1xPdrWBtTZSowzciS3O7bMYtFFJ8saYo" + }, + { + "alg" : "ES256", + "crv" : "P-256", + "kid" : "LYyP2g", + "kty" : "EC", + "use" : "sig", + "x" : "SlXFFkJ3JxMsXyXNrqzE3ozl_0913PmNbccLLWfeQFU", + "y" : "GLSahrZfBErmMUcHP0MGaeVnJdBwquhrhQ8eP05NfCI" + }, + { + "alg" : "ES256", + "crv" : "P-256", + "kid" : "mpf0DA", + "kty" : "EC", + "use" : "sig", + "x" : "fHEdeT3a6KaC1kbwov73ZwB_SiUHEyKQwUUtMCEn0aI", + "y" : "QWOjwPhInNuPlqjxLQyhveXpWqOFcQPhZ3t-koMNbZI" + }, + { + "alg" : "ES256", + "crv" : "P-256", + "kid" : "b9vTLA", + "kty" : "EC", + "use" : "sig", + "x" : "qCByTAvci-jRAD7uQSEhTdOs8iA714IbcY2L--YzynI", + "y" : "WQY0uCoQyPSozWKGQ0anmFeOH5JNXiZa9i6SNqOcm7w" + }, + { + "alg" : "ES256", + "crv" : "P-256", + "kid" : "0oeLcQ", + "kty" : "EC", + "use" : "sig", + "x" : "MdhRXGEoGJLtBjQEIjnYLPkeci9rXnca2TffkI0Kac0", + "y" : "9BoREHfX7g5OK8ELpA_4RcOnFCGSjfR4SGZpBo7juEY" + } + ] +} \ No newline at end of file diff --git a/google-oauth-client/src/test/resources/legacy_federated_keys.json b/google-oauth-client/src/test/resources/legacy_federated_keys.json new file mode 100644 index 000000000..3a5748399 --- /dev/null +++ b/google-oauth-client/src/test/resources/legacy_federated_keys.json @@ -0,0 +1,4 @@ +{ + "f9d97b4cae90bcd76aeb20026f6b770cac221783": "-----BEGIN CERTIFICATE-----\nMIIDJjCCAg6gAwIBAgIILRTfnfU3e2gwDQYJKoZIhvcNAQEFBQAwNjE0MDIGA1UE\nAxMrZmVkZXJhdGVkLXNpZ25vbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTAe\nFw0yMDA0MTQwNDI5MzBaFw0yMDA0MzAxNjQ0MzBaMDYxNDAyBgNVBAMTK2ZlZGVy\nYXRlZC1zaWdub24uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJr/uBUmu+oWnnF9g85yjyAEtjfyRAOVOV\nPq54e1S1tORFUTVfm0WY+oKGdfvBZ3Z8rsxLj6dyVhoYsvsi8m0322OsPSH/f8e+\netFlNpXJL7J9aoJANGr/RVbUsETKDBhGps7ZPSU6hxNj7mpSiwO8l976j6VBgwfu\nq0MDKKr1emtXMphN9LnjkFMQJeLSF72KQmXlD9EA/UFmhqKnwXTFflj/rQb3RXJc\nVa7LXqebkBJbx/CrN2fWUv+iCI7UuKuAEUURfP+3g/CaNbN9DCSgq/gBv+X27YV0\nCgq0UfYLApoYHyFq0grds4AkmBF5t5TfDoCHeDFF+LwdY8dsAaO3AgMBAAGjODA2\nMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsG\nAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4IBAQA1Wrx3XsIAAYOaycAkV2mZW1j+Vqxx\nSAeUyuhLoaJ7jntd7LqTuTr+qRnR/fH/CjTbPzngvCyVE6hjClh159YRpf4TJ4aL\nMJ97qDxc/f/pM/7yklIaHHOwqYU10plIyw+m0dnQutPqy1o/aDUytDznNmM6L3v+\ncot2bxyd2PtjGfa1hPNNnEnrZfS2Gc0qqR64RUWbsdLVVQB8MKcaNUqjk9o/1O4p\nNNk2D2VcofdaLPpwSmtzV8wEd4vfzI17qFSPi6gbTfydvxkejk0kdSyWUPw+1YC4\nv2o2rzwXub9hcP2zXyZvTGKPMAkZ8VKuzWuvfuSsTtgcPJ20GpIkin/j\n-----END CERTIFICATE-----\n", + "28b741e8de984a47159f19e6d7783e9d4fa810db": "-----BEGIN CERTIFICATE-----\nMIIDJjCCAg6gAwIBAgIIcog+uwMaMb8wDQYJKoZIhvcNAQEFBQAwNjE0MDIGA1UE\nAxMrZmVkZXJhdGVkLXNpZ25vbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTAe\nFw0yMDA0MjIwNDI5MzBaFw0yMDA1MDgxNjQ0MzBaMDYxNDAyBgNVBAMTK2ZlZGVy\nYXRlZC1zaWdub24uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNzgQuf72cvMJlvg91cZWG1S3PCZAjwdkf\ny04gSD2ibHoJb49nnG1VDk7kQEbYADn8r5o5a9URbJ94d7xbBmW0zVxe7pA11dul\nVVh5nS2H2Gbtl+U3PuHU8wz2U1csmOiRrHmDEayRzwKyOGTC6sSi3VMPbNsYhCw5\nih5tBO8nYBjEiOTvsFTbScumW6uB7yOsr5XUbfMmWnTEgqrGOx8gVibUyn4dCEnN\n0xuM0RJyzR0NPis/F0D7A5UaLHTCs90gTWAzgbFnsCVJnshyO5gxkh7p3qgB4obQ\nUbRq9u2KkBCCXFMczdQ6B7o+ZOGv+aaIjzaguoa626X5diDLVLY1AgMBAAGjODA2\nMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsG\nAQUFBwMCMA0GCSqGSIb3DQEBBQUAA4IBAQBEfCN7qgI2GJJAC99PDbafqC1EMBlv\nBT/7UiQdTuDV04+cQH9IpzROW7IZc/ILcqpF6KXUmj6j0sWO+hxKFY66TJKPcypK\n/ZMI58epwwVgGZyYU0BbAIZ9uvOgDfuveMildlMDMg1cJNp7WjBrEJ2DcGfC56wJ\nuKvqB1upxnfh+Ceg3ApU50k6Ld6+dbDDR0Vzt/wGZlZZ5Uj6AwDFe+5p9zEpWg61\nHeny/tSBfgZ19vP2h3ye9ZTK1OFRMNufj8iSzmlkbSqWuy82XVSBRKy5QslqXsYe\nU3gM3EVvXHA/Of3sROFpvznCXNr+Kn03wTv0ny6rnSgHQUzj7p9fydXY\n-----END CERTIFICATE-----\n" +} \ No newline at end of file diff --git a/google-oauth-client/src/test/resources/service_account_keys.json b/google-oauth-client/src/test/resources/service_account_keys.json new file mode 100644 index 000000000..361bb2e4d --- /dev/null +++ b/google-oauth-client/src/test/resources/service_account_keys.json @@ -0,0 +1,4 @@ +{ + "a8611b6a9c0a0a8b940d0f915c326fd1605c8ac6": "-----BEGIN CERTIFICATE-----\nMIIDPDCCAiSgAwIBAgIIFJsPvyc/ZSUwDQYJKoZIhvcNAQEFBQAwQTE/MD0GA1UE\nAxM2aW50ZWdyYXRpb24tdGVzdHMuY2hpbmdvci10ZXN0LmlhbS5nc2VydmljZWFj\nY291bnQuY29tMB4XDTIwMDQwMjIyMjIxN1oXDTIyMDUwMTEzNTYxNVowQTE/MD0G\nA1UEAxM2aW50ZWdyYXRpb24tdGVzdHMuY2hpbmdvci10ZXN0LmlhbS5nc2Vydmlj\nZWFjY291bnQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6Yys\nP5LIa1rRxQY93FXIJDzq6Tai4VuetffJbltRtYbdwC5Vyl99O2zoVdRlg+iYXK5B\nb6kidjmWOf0kNimQ5FwYvu+xsm6w8vjL/XShkHEKiURszyCua8wvLeGVCiGBg/XU\nDOgYMjzRIH5fTuj3PTZk4sMj02ZCpCQEMQ6ogpLXjaLp3ZXtFhkuHyCxVYbTRr+k\nGU86JAg4XwD6AdC349v+8FEQD7YtJezUAAKEgXh9e5UeL5CpOo3Vsdv/yEVo00jh\nYuWzLM6Oxt55WAhiD29vKrm7VQPSr1XwwqpdyFL2BlmqyTlb3amwvc9qv2kojGvM\nSUqgS83dc0jFqtMvEQIDAQABozgwNjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQE\nAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEA\nm3XUMKOtXdpw0oRjykLHdYzTHHFjQTMmhWD172rsxSzwpkFoErAC7bnmEvpcRU7D\nr4M+pE5VuDJ64J3lEpAh7W0zMXezPtGyWr39hVxL3vp3nh4CbCzzkUCfFvBOFFhm\nOI9qnjtMtaozoGi5zLs5jEaFmgR3wfij9KQjNGZJxAg0ZkwcSNb76qOCG1/vG5au\n4UuoIaq8WqSxMqBF/g+NrAE2PZhjNGnUwFPTre3SyR0otYDzJfmpL/tp5VDie8hM\nL5UZU/CmZk46+T9VbvnZ5mkPAjGiPumiptO5iliBOHPtPdn8VrP+aSQM1btHA094\n1HwfbFp7pZHBUn9COAP/1Q==\n-----END CERTIFICATE-----\n", + "2ef77b38a1b0370487043916acbf27d74edd08b1": "-----BEGIN CERTIFICATE-----\nMIIC+jCCAeKgAwIBAgIIIwRR4+AftjswDQYJKoZIhvcNAQEFBQAwIDEeMBwGA1UE\nAxMVMTA0MDI5MjkyODUzMDk5OTc4MjkzMB4XDTIwMDMwNDA1NTIyMloXDTMwMDMw\nMjA1NTIyMlowIDEeMBwGA1UEAxMVMTA0MDI5MjkyODUzMDk5OTc4MjkzMIIBIjAN\nBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm4jAbNdDEDkG/36wP07lFKMTAnWy\nhtV/Vp4QFSIE456moU/HEmBwJX2wocPgvoxPat7FxUv7mwdgHq7+sczis4DrDIIY\n8XfZ+D98+X+rOfkS1WLXpO76REZE4JCUfkB3NKVMP0kfoCFPf2pafz1NJRrZczUw\nbSi/q1+KYHmbk8YS+Q7Iq7gW9dvQtWrsRH8dQIrToJfGH+rbSQyKUFN7skFOflw4\n/OSuT0wvD6z57JcRFtAD3zgeUuCPNRIbkPQC3vCLwWGLKSYWLJ3eM9PPW9bk+czf\nSxJOie7zRMToh4BchLO6ZQgshoEaBHbwdOTu8455skqlRJMU9SKwA6eqVQIDAQAB\nozgwNjAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAK\nBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOCAQEAXvt8M2GFK+5UHG0GclIqse8j\n+EgqXvDTkbeTxFP+onbA/RwKM+fzdYpDwrH1dQ6jJervmceewUTTMegfFzhF33GC\nxvjQfhs+yVOXQiBHosd93CgR19dMUYs/r1wuUpwqBGdW2S81ns3yreY72BHrikrl\nHNLD3aSJ6hq5CZ01EFpjTW10ndBdPhJRSWD2g8VI1lpd716HEmrXfPHX73KVkk5/\nWfvrMA1UK/Ag+TWQerKG3iQFUAPIUiyepdaG4uFWTBY9nzLPiC1cx3bVPVZ+5yul\nJN15hmAMd3qPgSbbeQ6JC72zXCfW3buBE2n9cGtRbZF1URJZ3NbvwRS5BD425g==\n-----END CERTIFICATE-----\n" +} \ No newline at end of file From 1c16f8f044532e9699d0274e2fcc403a90fa5f0d Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Thu, 24 Mar 2022 13:50:19 -0700 Subject: [PATCH 02/17] more test cases --- .../auth/openidconnect/IdTokenVerifier.java | 6 +- .../openidconnect/IdTokenVerifierTest.java | 148 ++++++++++-------- 2 files changed, 89 insertions(+), 65 deletions(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index 813f93f59..99c39fc8e 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -194,8 +194,8 @@ public final Collection getAudience() { */ public boolean verify(IdToken idToken) throws VerificationException { boolean simpleChecks = (issuers == null || idToken.verifyIssuer(issuers)) - && (audience == null || idToken.verifyAudience(audience)) - && idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds); + && (audience == null || idToken.verifyAudience(audience)); + //&& idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds); if (!simpleChecks) { return false; @@ -442,7 +442,7 @@ public static class JsonWebKey { @Override public Map load(String certificateUrl) throws Exception { - HttpTransport httpTransport = new NetHttpTransport(); + HttpTransport httpTransport = httpTransportFactory.create(); JsonWebKeySet jwks; try { HttpRequest request = 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 c8dd34240..c1c72c204 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 @@ -19,6 +19,7 @@ import com.google.api.client.http.HttpTransport; import com.google.api.client.http.LowLevelHttpRequest; import com.google.api.client.http.LowLevelHttpResponse; +import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.gson.GsonFactory; import com.google.api.client.json.webtoken.JsonWebSignature.Header; @@ -27,7 +28,11 @@ import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.api.client.util.Clock; import com.google.api.client.util.Lists; +import com.google.common.io.CharStreams; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -66,6 +71,7 @@ public class IdTokenVerifierTest extends TestCase { Arrays.asList(ES256_TOKEN, FEDERATED_SIGNON_RS256_TOKEN, SERVICE_ACCOUNT_RS256_TOKEN); static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); + static final MyClock FIXED_CLOCK = new MyClock(1582704000000L); private static IdToken newIdToken(String issuer, String audience) { Payload payload = new Payload(); @@ -82,7 +88,7 @@ public void testBuilder() throws Exception { assertEquals(Clock.SYSTEM, builder.getClock()); assertEquals(ISSUER, builder.getIssuer()); assertEquals(Collections.singleton(ISSUER), builder.getIssuers()); - assertTrue(TRUSTED_CLIENT_IDS.equals(builder.getAudience())); + assertEquals(TRUSTED_CLIENT_IDS, builder.getAudience()); Clock clock = new MyClock(); builder.setClock(clock); assertEquals(clock, builder.getClock()); @@ -93,15 +99,6 @@ public void testBuilder() throws Exception { assertEquals(TRUSTED_CLIENT_IDS, Lists.newArrayList(verifier.getAudience())); } - static class MyClock implements Clock { - - long timeMillis; - - public long currentTimeMillis() { - return timeMillis; - } - } - public void testVerify() throws Exception { MyClock clock = new MyClock(); IdTokenVerifier verifier = @@ -216,62 +213,89 @@ public LowLevelHttpResponse execute() throws IOException { tokenVerifier.verify(IdToken.parse(JSON_FACTORY, ES256_TOKEN)); } catch (VerificationException ex) { assertTrue(ex.getMessage().contains("Error fetching PublicKey")); + return; } throw new Exception("Should have failed verification"); } - // @Test - // void verifyEs256Token() throws TokenVerifier.VerificationException, IOException { - // HttpTransportFactory httpTransportFactory = - // mockTransport( - // "https://www.gstatic.com/iap/verify/public_key-jwk", - // readResourceAsString("iap_keys.json")); - // TokenVerifier tokenVerifier = - // TokenVerifier.newBuilder() - // .setClock(FIXED_CLOCK) - // .setHttpTransportFactory(httpTransportFactory) - // .build(); - // assertNotNull(tokenVerifier.verify(ES256_TOKEN)); - // } - // - // @Test - // void verifyRs256Token() throws TokenVerifier.VerificationException, IOException { - // HttpTransportFactory httpTransportFactory = - // mockTransport( - // "https://www.googleapis.com/oauth2/v3/certs", - // readResourceAsString("federated_keys.json")); - // TokenVerifier tokenVerifier = - // TokenVerifier.newBuilder() - // .setClock(FIXED_CLOCK) - // .setHttpTransportFactory(httpTransportFactory) - // .build(); - // assertNotNull(tokenVerifier.verify(FEDERATED_SIGNON_RS256_TOKEN)); - // } - // - // @Test - // void verifyRs256TokenWithLegacyCertificateUrlFormat() - // throws TokenVerifier.VerificationException, IOException { - // HttpTransportFactory httpTransportFactory = - // mockTransport( - // LEGACY_FEDERATED_SIGNON_CERT_URL, readResourceAsString("legacy_federated_keys.json")); - // TokenVerifier tokenVerifier = - // TokenVerifier.newBuilder() - // .setCertificatesLocation(LEGACY_FEDERATED_SIGNON_CERT_URL) - // .setClock(FIXED_CLOCK) - // .setHttpTransportFactory(httpTransportFactory) - // .build(); - // assertNotNull(tokenVerifier.verify(FEDERATED_SIGNON_RS256_TOKEN)); - // } - // - // @Test - // void verifyServiceAccountRs256Token() throws TokenVerifier.VerificationException, IOException { - // TokenVerifier tokenVerifier = - // TokenVerifier.newBuilder() - // .setClock(FIXED_CLOCK) - // .setCertificatesLocation(SERVICE_ACCOUNT_CERT_URL) - // .build(); - // assertNotNull(tokenVerifier.verify(SERVICE_ACCOUNT_RS256_TOKEN)); - // } + public void testVerifyEs256Token() throws VerificationException, IOException { + HttpTransportFactory httpTransportFactory = + mockTransport( + "https://www.gstatic.com/iap/verify/public_key-jwk", + readResourceAsString("iap_keys.json")); + MyClock clock = new MyClock(); + clock.timeMillis = 1582704000000L; + IdTokenVerifier tokenVerifier = + new IdTokenVerifier.Builder() + .setClock(clock) + .setHttpTransportFactory(httpTransportFactory) + .build(); + assertTrue(tokenVerifier.verify(IdToken.parse(JSON_FACTORY, ES256_TOKEN))); + } + + public void testVerifyRs256Token() throws VerificationException, IOException { + HttpTransportFactory httpTransportFactory = + mockTransport( + "https://www.googleapis.com/oauth2/v3/certs", + readResourceAsString("federated_keys.json")); + IdTokenVerifier tokenVerifier = + new IdTokenVerifier.Builder() + .setClock(FIXED_CLOCK) + .setHttpTransportFactory(httpTransportFactory) + .build(); + assertTrue(tokenVerifier.verify(IdToken.parse(JSON_FACTORY, FEDERATED_SIGNON_RS256_TOKEN))); + } + + public void testVerifyRs256TokenWithLegacyCertificateUrlFormat() + throws VerificationException, IOException { + HttpTransportFactory httpTransportFactory = + mockTransport( + LEGACY_FEDERATED_SIGNON_CERT_URL, readResourceAsString("legacy_federated_keys.json")); + IdTokenVerifier tokenVerifier = + new IdTokenVerifier.Builder() + .setCertificatesLocation(LEGACY_FEDERATED_SIGNON_CERT_URL) + .setClock(FIXED_CLOCK) + .setHttpTransportFactory(httpTransportFactory) + .build(); + assertTrue(tokenVerifier.verify(IdToken.parse(JSON_FACTORY, FEDERATED_SIGNON_RS256_TOKEN))); + } + + public void testVerifyServiceAccountRs256Token() throws VerificationException, IOException { + IdTokenVerifier tokenVerifier = + new IdTokenVerifier.Builder() + .setClock(FIXED_CLOCK) + .setCertificatesLocation(SERVICE_ACCOUNT_CERT_URL) + .setHttpTransportFactory(new DefaultHttpTransportFactory()) + .build(); + assertTrue(tokenVerifier.verify(IdToken.parse(JSON_FACTORY, SERVICE_ACCOUNT_RS256_TOKEN))); + } + + static String readResourceAsString(String resourceName) throws IOException { + InputStream inputStream = + IdTokenVerifierTest.class.getClassLoader().getResourceAsStream(resourceName); + try (final Reader reader = new InputStreamReader(inputStream)) { + return CharStreams.toString(reader); + } + } + + static class MyClock implements Clock { + public MyClock() {} + + public MyClock(long timeMillis) { + this.timeMillis = timeMillis; + } + + long timeMillis; + public long currentTimeMillis() { + return timeMillis; + } + } + + static class DefaultHttpTransportFactory implements HttpTransportFactory { + public HttpTransport create() { + return new NetHttpTransport(); + } + } static HttpTransportFactory mockTransport(String url, String certificates) { final String certificatesContent = certificates; From 872f5f6275ef364ac4247f4b377e396c09390f0f Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Fri, 25 Mar 2022 22:43:02 +0000 Subject: [PATCH 03/17] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- .../openidconnect/HttpTransportFactory.java | 2 +- .../auth/openidconnect/IdTokenVerifier.java | 23 ++++++++----------- .../openidconnect/IdTokenVerifierTest.java | 1 + 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/HttpTransportFactory.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/HttpTransportFactory.java index c3d7f46a5..4ec046d60 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/HttpTransportFactory.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/HttpTransportFactory.java @@ -47,4 +47,4 @@ public interface HttpTransportFactory { * @return The HttpTransport instance. */ HttpTransport create(); -} \ No newline at end of file +} diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index 99c39fc8e..7c07fa14b 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -18,10 +18,8 @@ import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpTransport; -import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.GenericJson; import com.google.api.client.json.gson.GsonFactory; -import com.google.api.client.json.webtoken.JsonWebSignature; import com.google.api.client.json.webtoken.JsonWebSignature.Header; import com.google.api.client.util.Base64; import com.google.api.client.util.Beta; @@ -98,6 +96,7 @@ public class IdTokenVerifier { /** Clock to use for expiration checks. */ private final Clock clock; + private final String certificatesLocation; private final LoadingCache> publicKeyCache; @@ -193,9 +192,10 @@ public final Collection getAudience() { * @return {@code true} if verified successfully or {@code false} if failed */ public boolean verify(IdToken idToken) throws VerificationException { - boolean simpleChecks = (issuers == null || idToken.verifyIssuer(issuers)) - && (audience == null || idToken.verifyAudience(audience)); - //&& idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds); + boolean simpleChecks = + (issuers == null || idToken.verifyIssuer(issuers)) + && (audience == null || idToken.verifyAudience(audience)); + // && idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds); if (!simpleChecks) { return false; @@ -204,16 +204,14 @@ public boolean verify(IdToken idToken) throws VerificationException { PublicKey publicKeyToUse = null; try { String certificateLocation = getCertificateLocation(idToken.getHeader()); - publicKeyToUse = - publicKeyCache.get(certificateLocation).get(idToken.getHeader().getKeyId()); + publicKeyToUse = publicKeyCache.get(certificateLocation).get(idToken.getHeader().getKeyId()); } catch (ExecutionException | UncheckedExecutionException e) { throw new VerificationException("Error fetching PublicKey from certificate location", e); } if (publicKeyToUse == null) { throw new VerificationException( - "Could not find PublicKey for provided keyId: " - + idToken.getHeader().getKeyId()); + "Could not find PublicKey for provided keyId: " + idToken.getHeader().getKeyId()); } try { @@ -226,8 +224,7 @@ public boolean verify(IdToken idToken) throws VerificationException { } } - private String getCertificateLocation(Header header) - throws VerificationException { + private String getCertificateLocation(Header header) throws VerificationException { if (certificatesLocation != null) return certificatesLocation; switch (header.getAlgorithm()) { @@ -264,6 +261,7 @@ public static class Builder { /** List of trusted audience client IDs or {@code null} to suppress the audience check. */ Collection audience; + HttpTransportFactory httpTransportFactory; /** Builds a new instance of {@link IdTokenVerifier}. */ @@ -410,8 +408,7 @@ static class PublicKeyLoader extends CacheLoader> * Data class used for deserializing a JSON Web Key Set (JWKS) from an external HTTP request. */ public static class JsonWebKeySet extends GenericJson { - @Key - public List keys; + @Key public List keys; } /** Data class used for deserializing a single JSON Web Key. */ 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 c1c72c204..34c2befbd 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 @@ -286,6 +286,7 @@ public MyClock(long timeMillis) { } long timeMillis; + public long currentTimeMillis() { return timeMillis; } From 70ac584783662d2ecf66d52a89041c66017891d3 Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Mon, 28 Mar 2022 13:08:14 -0700 Subject: [PATCH 04/17] fix: more test fixes --- .../auth/openidconnect/Environment.java | 7 ++ .../auth/openidconnect/IdTokenVerifier.java | 34 ++++++- .../openidconnect/IdTokenVerifierTest.java | 99 ++++++++++++------- 3 files changed, 102 insertions(+), 38 deletions(-) create mode 100644 google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/Environment.java diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/Environment.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/Environment.java new file mode 100644 index 000000000..03ba67318 --- /dev/null +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/Environment.java @@ -0,0 +1,7 @@ +package com.google.api.client.auth.openidconnect; + +class Environment { + public String getVariable(String name) { + return System.getenv(name); + } +} diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index 7c07fa14b..11c4fac4c 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -91,6 +91,7 @@ public class IdTokenVerifier { "https://www.googleapis.com/oauth2/v3/certs"; private static final Set SUPPORTED_ALGORITHMS = ImmutableSet.of("RS256", "ES256"); + static final String SKIP_SIGNATURE_ENV_VAR = "OAUTH_CLIENT_SKIP_SIGNATURE"; /** Default value for seconds of time skew to accept when verifying time (5 minutes). */ public static final long DEFAULT_TIME_SKEW_SECONDS = 300; @@ -98,6 +99,7 @@ public class IdTokenVerifier { private final Clock clock; private final String certificatesLocation; + private final Environment environment; private final LoadingCache> publicKeyCache; /** Seconds of time skew to accept when verifying time. */ @@ -131,6 +133,7 @@ protected IdTokenVerifier(Builder builder) { CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.HOURS) .build(new PublicKeyLoader(builder.httpTransportFactory)); + this.environment = builder.environment == null ? new Environment() : builder.environment; } /** Returns the clock. */ @@ -192,15 +195,24 @@ public final Collection getAudience() { * @return {@code true} if verified successfully or {@code false} if failed */ public boolean verify(IdToken idToken) throws VerificationException { - boolean simpleChecks = - (issuers == null || idToken.verifyIssuer(issuers)) - && (audience == null || idToken.verifyAudience(audience)); - // && idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds); + boolean simpleChecks = (issuers == null || idToken.verifyIssuer(issuers)) + && (audience == null || idToken.verifyAudience(audience)) + && idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds); if (!simpleChecks) { return false; } + if (Boolean.parseBoolean(environment.getVariable(SKIP_SIGNATURE_ENV_VAR))) { + return true; + } + + // Short-circuit signature types + if (!SUPPORTED_ALGORITHMS.contains(idToken.getHeader().getAlgorithm())) { + throw new VerificationException( + "Unexpected signing algorithm: expected either RS256 or ES256"); + } + PublicKey publicKeyToUse = null; try { String certificateLocation = getCertificateLocation(idToken.getHeader()); @@ -253,6 +265,9 @@ public static class Builder { String certificatesLocation; + /** wrapper for environment variables */ + Environment environment; + /** Seconds of time skew to accept when verifying time. */ long acceptableTimeSkewSeconds = DEFAULT_TIME_SKEW_SECONDS; @@ -387,6 +402,17 @@ public Builder setAcceptableTimeSkewSeconds(long acceptableTimeSkewSeconds) { return this; } + /** Returns an instance of the {@link Environment} */ + public final Environment getEnvironment() { + return environment; + } + + /** Sets the environment. Used mostly for testing */ + public Builder setEnvironment(Environment environment) { + this.environment = environment; + return this; + } + /** * Set the HttpTransportFactory used for requesting public keys from the certificate URL. Used * mostly for testing. 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 34c2befbd..b95cca564 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 @@ -35,7 +35,9 @@ import java.io.Reader; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import junit.framework.TestCase; /** @@ -71,7 +73,7 @@ public class IdTokenVerifierTest extends TestCase { Arrays.asList(ES256_TOKEN, FEDERATED_SIGNON_RS256_TOKEN, SERVICE_ACCOUNT_RS256_TOKEN); static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); - static final MyClock FIXED_CLOCK = new MyClock(1582704000000L); + static final MockClock FIXED_CLOCK = new MockClock(1584047020000L); private static IdToken newIdToken(String issuer, String audience) { Payload payload = new Payload(); @@ -89,7 +91,7 @@ public void testBuilder() throws Exception { assertEquals(ISSUER, builder.getIssuer()); assertEquals(Collections.singleton(ISSUER), builder.getIssuers()); assertEquals(TRUSTED_CLIENT_IDS, builder.getAudience()); - Clock clock = new MyClock(); + Clock clock = new MockClock(); builder.setClock(clock); assertEquals(clock, builder.getClock()); IdTokenVerifier verifier = builder.build(); @@ -100,15 +102,24 @@ public void testBuilder() throws Exception { } public void testVerify() throws Exception { - MyClock clock = new MyClock(); + MockClock clock = new MockClock(); + MockEnvironment testEnvironment = new MockEnvironment(); + testEnvironment.setVariable(IdTokenVerifier.SKIP_SIGNATURE_ENV_VAR, "true"); IdTokenVerifier verifier = new IdTokenVerifier.Builder() .setIssuers(Arrays.asList(ISSUER, ISSUER3)) .setAudience(Arrays.asList(CLIENT_ID)) .setClock(clock) + .setEnvironment(testEnvironment) .build(); + // verifier flexible doesn't check issuer and audience - IdTokenVerifier verifierFlexible = new IdTokenVerifier.Builder().setClock(clock).build(); + IdTokenVerifier verifierFlexible = + new IdTokenVerifier.Builder() + .setClock(clock) + .setEnvironment(testEnvironment) + .build(); + // issuer clock.timeMillis = 1500000L; IdToken idToken = newIdToken(ISSUER, CLIENT_ID); @@ -166,7 +177,7 @@ public void testBuilderSetNullIssuers() throws Exception { public void testMissingAudience() throws VerificationException { IdToken idToken = newIdToken(ISSUER, null); - MyClock clock = new MyClock(); + MockClock clock = new MockClock(); clock.timeMillis = 1500000L; IdTokenVerifier verifier = new IdTokenVerifier.Builder() @@ -201,11 +212,9 @@ public LowLevelHttpResponse execute() throws IOException { }; } }; - MyClock clock = new MyClock(); - clock.timeMillis = 1582704000000L; IdTokenVerifier tokenVerifier = new IdTokenVerifier.Builder() - .setClock(clock) + .setClock(FIXED_CLOCK) .setHttpTransportFactory(httpTransportFactory) .build(); @@ -223,11 +232,9 @@ public void testVerifyEs256Token() throws VerificationException, IOException { mockTransport( "https://www.gstatic.com/iap/verify/public_key-jwk", readResourceAsString("iap_keys.json")); - MyClock clock = new MyClock(); - clock.timeMillis = 1582704000000L; IdTokenVerifier tokenVerifier = new IdTokenVerifier.Builder() - .setClock(clock) + .setClock(FIXED_CLOCK) .setHttpTransportFactory(httpTransportFactory) .build(); assertTrue(tokenVerifier.verify(IdToken.parse(JSON_FACTORY, ES256_TOKEN))); @@ -238,9 +245,10 @@ public void testVerifyRs256Token() throws VerificationException, IOException { mockTransport( "https://www.googleapis.com/oauth2/v3/certs", readResourceAsString("federated_keys.json")); + MockClock clock = new MockClock(1587625988000L); IdTokenVerifier tokenVerifier = new IdTokenVerifier.Builder() - .setClock(FIXED_CLOCK) + .setClock(clock) .setHttpTransportFactory(httpTransportFactory) .build(); assertTrue(tokenVerifier.verify(IdToken.parse(JSON_FACTORY, FEDERATED_SIGNON_RS256_TOKEN))); @@ -251,19 +259,21 @@ public void testVerifyRs256TokenWithLegacyCertificateUrlFormat() HttpTransportFactory httpTransportFactory = mockTransport( LEGACY_FEDERATED_SIGNON_CERT_URL, readResourceAsString("legacy_federated_keys.json")); + MockClock clock = new MockClock(1587626288000L); IdTokenVerifier tokenVerifier = new IdTokenVerifier.Builder() .setCertificatesLocation(LEGACY_FEDERATED_SIGNON_CERT_URL) - .setClock(FIXED_CLOCK) + .setClock(clock) .setHttpTransportFactory(httpTransportFactory) .build(); assertTrue(tokenVerifier.verify(IdToken.parse(JSON_FACTORY, FEDERATED_SIGNON_RS256_TOKEN))); } public void testVerifyServiceAccountRs256Token() throws VerificationException, IOException { + MockClock clock = new MockClock(1587626643000L); IdTokenVerifier tokenVerifier = new IdTokenVerifier.Builder() - .setClock(FIXED_CLOCK) + .setClock(clock) .setCertificatesLocation(SERVICE_ACCOUNT_CERT_URL) .setHttpTransportFactory(new DefaultHttpTransportFactory()) .build(); @@ -278,26 +288,6 @@ static String readResourceAsString(String resourceName) throws IOException { } } - static class MyClock implements Clock { - public MyClock() {} - - public MyClock(long timeMillis) { - this.timeMillis = timeMillis; - } - - long timeMillis; - - public long currentTimeMillis() { - return timeMillis; - } - } - - static class DefaultHttpTransportFactory implements HttpTransportFactory { - public HttpTransport create() { - return new NetHttpTransport(); - } - } - static HttpTransportFactory mockTransport(String url, String certificates) { final String certificatesContent = certificates; final String certificatesUrl = url; @@ -323,4 +313,45 @@ public LowLevelHttpResponse execute() throws IOException { } }; } + + /** + * A mock implementation of {@link Clock} to set clock for testing + */ + static class MockClock implements Clock { + public MockClock() {} + + public MockClock(long timeMillis) { + this.timeMillis = timeMillis; + } + + long timeMillis; + public long currentTimeMillis() { + return timeMillis; + } + } + + /** + * A default http transport factory for testing + */ + static class DefaultHttpTransportFactory implements HttpTransportFactory { + public HttpTransport create() { + return new NetHttpTransport(); + } + } + + /** + * A mock implementation of {@link Environment} to set environment variables for testing + */ + class MockEnvironment extends Environment { + private final Map variables = new HashMap<>(); + + @Override + public String getVariable(String name) { + return variables.get(name); + } + + public void setVariable(String name, String value) { + variables.put(name, value); + } + } } From 5dde91388ebeba76673d1fb9ee6faed25d662ef6 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Mon, 28 Mar 2022 20:13:33 +0000 Subject: [PATCH 05/17] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- .../auth/openidconnect/IdTokenVerifier.java | 9 +++++---- .../openidconnect/IdTokenVerifierTest.java | 18 +++++------------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index 11c4fac4c..0fff323fa 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -195,9 +195,10 @@ public final Collection getAudience() { * @return {@code true} if verified successfully or {@code false} if failed */ public boolean verify(IdToken idToken) throws VerificationException { - boolean simpleChecks = (issuers == null || idToken.verifyIssuer(issuers)) - && (audience == null || idToken.verifyAudience(audience)) - && idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds); + boolean simpleChecks = + (issuers == null || idToken.verifyIssuer(issuers)) + && (audience == null || idToken.verifyAudience(audience)) + && idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds); if (!simpleChecks) { return false; @@ -402,7 +403,7 @@ public Builder setAcceptableTimeSkewSeconds(long acceptableTimeSkewSeconds) { return this; } - /** Returns an instance of the {@link Environment} */ + /** Returns an instance of the {@link Environment} */ public final Environment getEnvironment() { return environment; } 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 b95cca564..f40895a22 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 @@ -115,10 +115,7 @@ public void testVerify() throws Exception { // verifier flexible doesn't check issuer and audience IdTokenVerifier verifierFlexible = - new IdTokenVerifier.Builder() - .setClock(clock) - .setEnvironment(testEnvironment) - .build(); + new IdTokenVerifier.Builder().setClock(clock).setEnvironment(testEnvironment).build(); // issuer clock.timeMillis = 1500000L; @@ -314,9 +311,7 @@ public LowLevelHttpResponse execute() throws IOException { }; } - /** - * A mock implementation of {@link Clock} to set clock for testing - */ + /** A mock implementation of {@link Clock} to set clock for testing */ static class MockClock implements Clock { public MockClock() {} @@ -325,23 +320,20 @@ public MockClock(long timeMillis) { } long timeMillis; + public long currentTimeMillis() { return timeMillis; } } - /** - * A default http transport factory for testing - */ + /** A default http transport factory for testing */ static class DefaultHttpTransportFactory implements HttpTransportFactory { public HttpTransport create() { return new NetHttpTransport(); } } - /** - * A mock implementation of {@link Environment} to set environment variables for testing - */ + /** A mock implementation of {@link Environment} to set environment variables for testing */ class MockEnvironment extends Environment { private final Map variables = new HashMap<>(); From 5c83d4745125ff3edb35ffe85802032ee8c76526 Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Wed, 30 Mar 2022 11:33:39 -0700 Subject: [PATCH 06/17] Update google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java Co-authored-by: Tomo Suzuki --- .../google/api/client/auth/openidconnect/IdTokenVerifier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index 0fff323fa..a11647f73 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -211,7 +211,7 @@ public boolean verify(IdToken idToken) throws VerificationException { // Short-circuit signature types if (!SUPPORTED_ALGORITHMS.contains(idToken.getHeader().getAlgorithm())) { throw new VerificationException( - "Unexpected signing algorithm: expected either RS256 or ES256"); + "Unexpected signing algorithm: expected either RS256 or ES256 but got " + idToken.getHeader().getAlgorithm()); } PublicKey publicKeyToUse = null; From 17affb3363c280e4fd71fbbacf0944250f638e64 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Wed, 30 Mar 2022 18:35:40 +0000 Subject: [PATCH 07/17] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- .../google/api/client/auth/openidconnect/IdTokenVerifier.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index a11647f73..caf3c9f18 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -211,7 +211,8 @@ public boolean verify(IdToken idToken) throws VerificationException { // Short-circuit signature types if (!SUPPORTED_ALGORITHMS.contains(idToken.getHeader().getAlgorithm())) { throw new VerificationException( - "Unexpected signing algorithm: expected either RS256 or ES256 but got " + idToken.getHeader().getAlgorithm()); + "Unexpected signing algorithm: expected either RS256 or ES256 but got " + + idToken.getHeader().getAlgorithm()); } PublicKey publicKeyToUse = null; From 4d342018c23972b86696df9a1126efc52ef4ef37 Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Thu, 31 Mar 2022 03:57:49 -0700 Subject: [PATCH 08/17] fix: restored original interface for verifier, added default http factory --- google-oauth-client/pom.xml | 5 -- .../auth/openidconnect/Environment.java | 31 +++++++++ .../auth/openidconnect/IdTokenVerifier.java | 63 ++++++++++++++++--- .../openidconnect/IdTokenVerifierTest.java | 5 +- 4 files changed, 87 insertions(+), 17 deletions(-) diff --git a/google-oauth-client/pom.xml b/google-oauth-client/pom.xml index 38aac68e5..9abd81a52 100644 --- a/google-oauth-client/pom.xml +++ b/google-oauth-client/pom.xml @@ -82,7 +82,6 @@ com.google.http-client google-http-client-gson - test junit @@ -93,9 +92,5 @@ com.google.guava guava - - com.google.http-client - google-http-client-gson - diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/Environment.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/Environment.java index 03ba67318..9b9e17816 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/Environment.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/Environment.java @@ -1,3 +1,34 @@ +/* + * Copyright 2022, Google Inc. 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 Google Inc. 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. + */ + package com.google.api.client.auth.openidconnect; class Environment { diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index caf3c9f18..e99c4da0e 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -18,6 +18,7 @@ import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.GenericJson; import com.google.api.client.json.gson.GsonFactory; import com.google.api.client.json.webtoken.JsonWebSignature.Header; @@ -26,6 +27,7 @@ import com.google.api.client.util.Clock; import com.google.api.client.util.Key; import com.google.api.client.util.Preconditions; +import com.google.common.annotations.VisibleForTesting; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @@ -90,7 +92,10 @@ public class IdTokenVerifier { private static final String FEDERATED_SIGNON_CERT_URL = "https://www.googleapis.com/oauth2/v3/certs"; private static final Set SUPPORTED_ALGORITHMS = ImmutableSet.of("RS256", "ES256"); + private static final String NOT_SUPPORTED_ALGORITHM = + "Unexpected signing algorithm %s: expected either RS256 or ES256"; + static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport(); static final String SKIP_SIGNATURE_ENV_VAR = "OAUTH_CLIENT_SKIP_SIGNATURE"; /** Default value for seconds of time skew to accept when verifying time (5 minutes). */ public static final long DEFAULT_TIME_SKEW_SECONDS = 300; @@ -121,7 +126,9 @@ public IdTokenVerifier() { this(new Builder()); } - /** @param builder builder */ + /** + * @param builder builder + */ protected IdTokenVerifier(Builder builder) { this.certificatesLocation = builder.certificatesLocation; clock = builder.clock; @@ -129,10 +136,14 @@ protected IdTokenVerifier(Builder builder) { issuers = builder.issuers == null ? null : Collections.unmodifiableCollection(builder.issuers); audience = builder.audience == null ? null : Collections.unmodifiableCollection(builder.audience); + HttpTransportFactory transport = + builder.httpTransportFactory == null + ? new DefaultHttpTransportFactory() + : builder.httpTransportFactory; this.publicKeyCache = CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.HOURS) - .build(new PublicKeyLoader(builder.httpTransportFactory)); + .build(new PublicKeyLoader(transport)); this.environment = builder.environment == null ? new Environment() : builder.environment; } @@ -194,7 +205,7 @@ public final Collection getAudience() { * @param idToken ID token * @return {@code true} if verified successfully or {@code false} if failed */ - public boolean verify(IdToken idToken) throws VerificationException { + public boolean verify(IdToken idToken) { boolean simpleChecks = (issuers == null || idToken.verifyIssuer(issuers)) && (audience == null || idToken.verifyAudience(audience)) @@ -204,6 +215,33 @@ public boolean verify(IdToken idToken) throws VerificationException { return false; } + // This method validates token signature per current OpenID Connect Spec: + // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation + // By default, method gets a certificate from well-known location + // A request to certificate location is performed using + // {@link com.google.api.client.http.javanet.NetHttpTransport} + // Both certificate location and transport implementation can be overridden via {@link Builder} + // not recommended: this check can be disabled with OAUTH_CLIENT_SKIP_SIGNATURE + // environment variable set to true. + try { + return verifySignature(idToken); + } catch (VerificationException ex) { + return false; + } + } + + /** + * Verifies the signature part of the id token By default, method gets a certificate from + * well-known location A request to certificate location is performed using {@link + * com.google.api.client.http.javanet.NetHttpTransport} Both default can be overridden via {@link + * Builder} + * + * @param idToken an id token + * @return true if signature validated successfully, false otherwise + */ + @VisibleForTesting + boolean verifySignature(IdToken idToken) throws VerificationException { + if (Boolean.parseBoolean(environment.getVariable(SKIP_SIGNATURE_ENV_VAR))) { return true; } @@ -211,8 +249,7 @@ public boolean verify(IdToken idToken) throws VerificationException { // Short-circuit signature types if (!SUPPORTED_ALGORITHMS.contains(idToken.getHeader().getAlgorithm())) { throw new VerificationException( - "Unexpected signing algorithm: expected either RS256 or ES256 but got " - + idToken.getHeader().getAlgorithm()); + String.format(NOT_SUPPORTED_ALGORITHM, idToken.getHeader().getAlgorithm())); } PublicKey publicKeyToUse = null; @@ -220,7 +257,8 @@ public boolean verify(IdToken idToken) throws VerificationException { String certificateLocation = getCertificateLocation(idToken.getHeader()); publicKeyToUse = publicKeyCache.get(certificateLocation).get(idToken.getHeader().getKeyId()); } catch (ExecutionException | UncheckedExecutionException e) { - throw new VerificationException("Error fetching PublicKey from certificate location", e); + throw new VerificationException( + "Error fetching PublicKey from certificate location " + certificatesLocation, e); } if (publicKeyToUse == null) { @@ -248,7 +286,7 @@ private String getCertificateLocation(Header header) throws VerificationExceptio return IAP_CERT_URL; } - throw new VerificationException("Unknown algorithm"); + throw new VerificationException(String.format(NOT_SUPPORTED_ALGORITHM, header.getAlgorithm())); } /** @@ -405,12 +443,12 @@ public Builder setAcceptableTimeSkewSeconds(long acceptableTimeSkewSeconds) { } /** Returns an instance of the {@link Environment} */ - public final Environment getEnvironment() { + final Environment getEnvironment() { return environment; } /** Sets the environment. Used mostly for testing */ - public Builder setEnvironment(Environment environment) { + Builder setEnvironment(Environment environment) { this.environment = environment; return this; } @@ -562,4 +600,11 @@ public VerificationException(String message, Throwable cause) { super(message, cause); } } + + static class DefaultHttpTransportFactory implements HttpTransportFactory { + + public HttpTransport create() { + return HTTP_TRANSPORT; + } + } } 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 f40895a22..cf32e8ee8 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 @@ -216,12 +216,11 @@ public LowLevelHttpResponse execute() throws IOException { .build(); try { - tokenVerifier.verify(IdToken.parse(JSON_FACTORY, ES256_TOKEN)); + tokenVerifier.verifySignature(IdToken.parse(JSON_FACTORY, ES256_TOKEN)); + fail("Should have failed verification"); } catch (VerificationException ex) { assertTrue(ex.getMessage().contains("Error fetching PublicKey")); - return; } - throw new Exception("Should have failed verification"); } public void testVerifyEs256Token() throws VerificationException, IOException { From 9611f534f94ceedeacad89df9be74921493ac716 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Thu, 31 Mar 2022 11:03:08 +0000 Subject: [PATCH 09/17] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- .../google/api/client/auth/openidconnect/IdTokenVerifier.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index e99c4da0e..8e86bd2a8 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -126,9 +126,7 @@ public IdTokenVerifier() { this(new Builder()); } - /** - * @param builder builder - */ + /** @param builder builder */ protected IdTokenVerifier(Builder builder) { this.certificatesLocation = builder.certificatesLocation; clock = builder.clock; From 8b55b29af07bc9ac413256501b7c5a6502b47a06 Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Thu, 31 Mar 2022 10:40:24 -0700 Subject: [PATCH 10/17] Update google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java Co-authored-by: Tomo Suzuki --- .../google/api/client/auth/openidconnect/IdTokenVerifier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index 8e86bd2a8..dabacca21 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -230,7 +230,7 @@ public boolean verify(IdToken idToken) { /** * Verifies the signature part of the id token By default, method gets a certificate from - * well-known location A request to certificate location is performed using {@link + * well-known location. A request to certificate location is performed using {@link * com.google.api.client.http.javanet.NetHttpTransport} Both default can be overridden via {@link * Builder} * From 813bb20da820a80d1014e8e0aaf810fb171e3ad2 Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Thu, 31 Mar 2022 10:40:34 -0700 Subject: [PATCH 11/17] Update google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java Co-authored-by: Tomo Suzuki --- .../google/api/client/auth/openidconnect/IdTokenVerifier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index dabacca21..1ed7ff445 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -231,7 +231,7 @@ public boolean verify(IdToken idToken) { /** * Verifies the signature part of the id token By default, method gets a certificate from * well-known location. A request to certificate location is performed using {@link - * com.google.api.client.http.javanet.NetHttpTransport} Both default can be overridden via {@link + * com.google.api.client.http.javanet.NetHttpTransport}. Both default can be overridden via {@link * Builder} * * @param idToken an id token From e065d1a75a62e2718db0587759b05ff9f8379c97 Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Thu, 31 Mar 2022 11:13:58 -0700 Subject: [PATCH 12/17] doc fixes --- .../auth/openidconnect/IdTokenVerifier.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index 1ed7ff445..2551622cb 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -79,6 +79,28 @@ * if (!verifier.verify(idToken)) {...} * * + * The verifier validates token signature per current OpenID Connect Spec: + * https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation + * By default, method gets a certificate from well-known location + * A request to certificate location is performed using + * {@link com.google.api.client.http.javanet.NetHttpTransport} + * Either or both certificate location and transport implementation + * can be overridden via {@link Builder} + * + *

+ * IdTokenVerifier verifier = new IdTokenVerifier.Builder()
+ * .setIssuer("issuer.example.com")
+ * .setAudience(Arrays.asList("myClientId"))
+ * .setHttpTransportFactory(customHttpTransportFactory)
+ * .build();
+ * ...
+ * if (!verifier.verify(idToken)) {...}
+ * 
+ * + * not recommended: this check can be disabled with OAUTH_CLIENT_SKIP_SIGNATURE + * environment variable set to true. + * + * *

Note that {@link #verify(IdToken)} only implements a subset of the verification steps, mostly * just the MUST steps. Please read getAudience() { *

  • 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)}. + *
  • This method verifies token signature per current OpenID Connect Spec: + * https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation. + * By default, method gets a certificate from well-known location. + * A request to certificate location is performed using + * {@link com.google.api.client.http.javanet.NetHttpTransport} + * Both certificate location and transport implementation can be overridden via {@link Builder} + * not recommended: this check can be disabled with OAUTH_CLIENT_SKIP_SIGNATURE + * environment variable set to true. * * *

    Overriding is allowed, but it must call the super implementation. @@ -226,42 +237,26 @@ public final Collection getAudience() { * @return {@code true} if verified successfully or {@code false} if failed */ public boolean verify(IdToken idToken) { - boolean simpleChecks = - (issuers == null || idToken.verifyIssuer(issuers)) - && (audience == null || idToken.verifyAudience(audience)) - && idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds); + boolean tokenFieldsValid = + (issuers == null || idToken.verifyIssuer(issuers)) + && (audience == null || idToken.verifyAudience(audience)) + && idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds); - if (!simpleChecks) { + if (!tokenFieldsValid) { return false; } - // This method validates token signature per current OpenID Connect Spec: - // https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation - // By default, method gets a certificate from well-known location - // A request to certificate location is performed using - // {@link com.google.api.client.http.javanet.NetHttpTransport} - // Both certificate location and transport implementation can be overridden via {@link Builder} - // not recommended: this check can be disabled with OAUTH_CLIENT_SKIP_SIGNATURE - // environment variable set to true. try { return verifySignature(idToken); } catch (VerificationException ex) { + LOGGER.log(Level.SEVERE, "id token signature verification failed. " + + "Please see docs for IdTokenVerifier for default settings and configuration options", ex); return false; } } - /** - * Verifies the signature part of the id token By default, method gets a certificate from - * well-known location. A request to certificate location is performed using {@link - * com.google.api.client.http.javanet.NetHttpTransport}. Both default can be overridden via {@link - * Builder} - * - * @param idToken an id token - * @return true if signature validated successfully, false otherwise - */ @VisibleForTesting boolean verifySignature(IdToken idToken) throws VerificationException { - if (Boolean.parseBoolean(environment.getVariable(SKIP_SIGNATURE_ENV_VAR))) { return true; } @@ -536,6 +531,8 @@ public Map load(String certificateUrl) throws Exception { HttpResponse response = request.execute(); jwks = response.parseAs(JsonWebKeySet.class); } catch (IOException io) { + LOGGER.log(Level.WARNING, "Failed to get a certificate from certificate location " + + certificateUrl, io); return ImmutableMap.of(); } @@ -553,6 +550,7 @@ public Map load(String certificateUrl) throws Exception { } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidParameterSpecException ignored) { + LOGGER.log(Level.WARNING, "Failed to put a key into the cache", ignored); ignored.printStackTrace(); } } @@ -611,7 +609,7 @@ private PublicKey buildEs256PublicKey(JsonWebKey key) } /** Custom exception for wrapping all verification errors. */ - public static class VerificationException extends Exception { + static class VerificationException extends Exception { public VerificationException(String message) { super(message); } @@ -622,7 +620,6 @@ public VerificationException(String message, Throwable cause) { } static class DefaultHttpTransportFactory implements HttpTransportFactory { - public HttpTransport create() { return HTTP_TRANSPORT; } From 22178caeed854617a5e892101cf4942804dbeb71 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Sun, 3 Apr 2022 08:50:18 +0000 Subject: [PATCH 14/17] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- .../auth/openidconnect/IdTokenVerifier.java | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index 3194b81dd..c390d3a3a 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -82,12 +82,10 @@ * * * The verifier validates token signature per current OpenID Connect Spec: - * https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation - * By default, method gets a certificate from well-known location - * A request to certificate location is performed using - * {@link com.google.api.client.http.javanet.NetHttpTransport} - * Either or both certificate location and transport implementation - * can be overridden via {@link Builder} + * https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation By default, method gets a + * certificate from well-known location A request to certificate location is performed using {@link + * com.google.api.client.http.javanet.NetHttpTransport} Either or both certificate location and + * transport implementation can be overridden via {@link Builder} * *

      * IdTokenVerifier verifier = new IdTokenVerifier.Builder()
    @@ -99,9 +97,8 @@
      * if (!verifier.verify(idToken)) {...}
      * 
    * - * not recommended: this check can be disabled with OAUTH_CLIENT_SKIP_SIGNATURE - * environment variable set to true. - * + * not recommended: this check can be disabled with OAUTH_CLIENT_SKIP_SIGNATURE environment variable + * set to true. * *

    Note that {@link #verify(IdToken)} only implements a subset of the verification steps, mostly * just the MUST steps. Please read getAudience() { * and allowing for a time skew specified in {@link #getAcceptableTimeSkewSeconds()} , by * calling {@link IdToken#verifyTime(long, long)}. *

  • This method verifies token signature per current OpenID Connect Spec: - * https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation. - * By default, method gets a certificate from well-known location. - * A request to certificate location is performed using - * {@link com.google.api.client.http.javanet.NetHttpTransport} - * Both certificate location and transport implementation can be overridden via {@link Builder} - * not recommended: this check can be disabled with OAUTH_CLIENT_SKIP_SIGNATURE - * environment variable set to true. + * https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation. By default, + * method gets a certificate from well-known location. A request to certificate location is + * performed using {@link com.google.api.client.http.javanet.NetHttpTransport} Both + * certificate location and transport implementation can be overridden via {@link Builder} + * not recommended: this check can be disabled with OAUTH_CLIENT_SKIP_SIGNATURE environment + * variable set to true. * * *

    Overriding is allowed, but it must call the super implementation. @@ -238,9 +234,9 @@ public final Collection getAudience() { */ public boolean verify(IdToken idToken) { boolean tokenFieldsValid = - (issuers == null || idToken.verifyIssuer(issuers)) - && (audience == null || idToken.verifyAudience(audience)) - && idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds); + (issuers == null || idToken.verifyIssuer(issuers)) + && (audience == null || idToken.verifyAudience(audience)) + && idToken.verifyTime(clock.currentTimeMillis(), acceptableTimeSkewSeconds); if (!tokenFieldsValid) { return false; @@ -249,8 +245,11 @@ public boolean verify(IdToken idToken) { try { return verifySignature(idToken); } catch (VerificationException ex) { - LOGGER.log(Level.SEVERE, "id token signature verification failed. " - + "Please see docs for IdTokenVerifier for default settings and configuration options", ex); + LOGGER.log( + Level.SEVERE, + "id token signature verification failed. " + + "Please see docs for IdTokenVerifier for default settings and configuration options", + ex); return false; } } @@ -531,8 +530,10 @@ public Map load(String certificateUrl) throws Exception { HttpResponse response = request.execute(); jwks = response.parseAs(JsonWebKeySet.class); } catch (IOException io) { - LOGGER.log(Level.WARNING, "Failed to get a certificate from certificate location " - + certificateUrl, io); + LOGGER.log( + Level.WARNING, + "Failed to get a certificate from certificate location " + certificateUrl, + io); return ImmutableMap.of(); } From c8d01d37fbef608e6389a0f47450123171320562 Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Mon, 11 Apr 2022 18:05:25 -0700 Subject: [PATCH 15/17] Update google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java Co-authored-by: Tomo Suzuki --- .../google/api/client/auth/openidconnect/IdTokenVerifier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index c390d3a3a..cd4639daf 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -468,7 +468,7 @@ Builder setEnvironment(Environment environment) { } /** - * Set the HttpTransportFactory used for requesting public keys from the certificate URL. Used + * Sets the HttpTransportFactory used for requesting public keys from the certificate URL. Used * mostly for testing. * * @param httpTransportFactory the HttpTransportFactory used to build certificate URL requests From aca11aad2fbce5d0d532d6be68ef1101a01256a5 Mon Sep 17 00:00:00 2001 From: Timur Sadykov Date: Mon, 11 Apr 2022 18:30:42 -0700 Subject: [PATCH 16/17] nit and linter fixes --- .../servlet/auth/AbstractCallbackServlet.java | 12 +++++++++--- .../api/client/auth/oauth/AbstractOAuthGetToken.java | 4 +++- .../auth/oauth/OAuthAuthorizeTemporaryTokenUrl.java | 4 +++- .../api/client/auth/oauth/OAuthGetAccessToken.java | 4 +++- .../client/auth/oauth/OAuthGetTemporaryToken.java | 4 +++- .../auth/oauth2/AuthorizationCodeResponseUrl.java | 4 +++- .../api/client/auth/oauth2/StoredCredential.java | 4 +++- .../client/auth/openidconnect/IdTokenVerifier.java | 5 +++-- .../samples/dailymotion/cmdline/DailyMotionUrl.java | 8 ++++++-- .../services/samples/dailymotion/cmdline/Video.java | 4 +++- .../samples/dailymotion/cmdline/VideoFeed.java | 4 +++- 11 files changed, 42 insertions(+), 15 deletions(-) diff --git a/google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/AbstractCallbackServlet.java b/google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/AbstractCallbackServlet.java index f69d8e669..8fcf92339 100644 --- a/google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/AbstractCallbackServlet.java +++ b/google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/AbstractCallbackServlet.java @@ -99,13 +99,19 @@ protected final HttpTransport getHttpTransport() { */ protected abstract PersistenceManagerFactory getPersistenceManagerFactory(); - /** @return Specific ThreeLeggedFlow type that this callback should retreieve and complete. */ + /** + * @return Specific ThreeLeggedFlow type that this callback should retreieve and complete. + */ protected abstract Class getConcreteFlowType(); - /** @return Url to redirect the user to upon a successful credential exchange. */ + /** + * @return Url to redirect the user to upon a successful credential exchange. + */ protected abstract String getSuccessRedirectUrl(); - /** @return Url to redirect the user to upon failure. */ + /** + * @return Url to redirect the user to upon failure. + */ protected abstract String getDeniedRedirectUrl(); /** diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/AbstractOAuthGetToken.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/AbstractOAuthGetToken.java index 1a6cf4da5..751bd0d8a 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/AbstractOAuthGetToken.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/AbstractOAuthGetToken.java @@ -50,7 +50,9 @@ public abstract class AbstractOAuthGetToken extends GenericUrl { /** {@code true} for POST request or the default {@code false} for GET request. */ protected boolean usePost; - /** @param authorizationServerUrl encoded authorization server URL */ + /** + * @param authorizationServerUrl encoded authorization server URL + */ protected AbstractOAuthGetToken(String authorizationServerUrl) { super(authorizationServerUrl); } diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/OAuthAuthorizeTemporaryTokenUrl.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/OAuthAuthorizeTemporaryTokenUrl.java index acc9e10f3..28a5ceb02 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/OAuthAuthorizeTemporaryTokenUrl.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/OAuthAuthorizeTemporaryTokenUrl.java @@ -43,7 +43,9 @@ public class OAuthAuthorizeTemporaryTokenUrl extends GenericUrl { @Key("oauth_token") public String temporaryToken; - /** @param encodedUserAuthorizationUrl encoded user authorization URL */ + /** + * @param encodedUserAuthorizationUrl encoded user authorization URL + */ public OAuthAuthorizeTemporaryTokenUrl(String encodedUserAuthorizationUrl) { super(encodedUserAuthorizationUrl); } diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/OAuthGetAccessToken.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/OAuthGetAccessToken.java index cdbb3eced..d11973f49 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/OAuthGetAccessToken.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/OAuthGetAccessToken.java @@ -43,7 +43,9 @@ public class OAuthGetAccessToken extends AbstractOAuthGetToken { */ public String verifier; - /** @param authorizationServerUrl encoded authorization server URL */ + /** + * @param authorizationServerUrl encoded authorization server URL + */ public OAuthGetAccessToken(String authorizationServerUrl) { super(authorizationServerUrl); } diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/OAuthGetTemporaryToken.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/OAuthGetTemporaryToken.java index 00ebc0f75..9e08a8d74 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/OAuthGetTemporaryToken.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth/OAuthGetTemporaryToken.java @@ -38,7 +38,9 @@ public class OAuthGetTemporaryToken extends AbstractOAuthGetToken { */ public String callback; - /** @param authorizationServerUrl encoded authorization server URL */ + /** + * @param authorizationServerUrl encoded authorization server URL + */ public OAuthGetTemporaryToken(String authorizationServerUrl) { super(authorizationServerUrl); } diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/AuthorizationCodeResponseUrl.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/AuthorizationCodeResponseUrl.java index 6f85e8ba3..0d08a82aa 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/AuthorizationCodeResponseUrl.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/AuthorizationCodeResponseUrl.java @@ -83,7 +83,9 @@ public class AuthorizationCodeResponseUrl extends GenericUrl { @Key("error_uri") private String errorUri; - /** @param encodedResponseUrl encoded authorization code response URL */ + /** + * @param encodedResponseUrl encoded authorization code response URL + */ public AuthorizationCodeResponseUrl(String encodedResponseUrl) { super(encodedResponseUrl); // either error or code but not both diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/StoredCredential.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/StoredCredential.java index 2dace28dd..70252fcc1 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/StoredCredential.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/StoredCredential.java @@ -55,7 +55,9 @@ public final class StoredCredential implements Serializable { public StoredCredential() {} - /** @param credential existing credential to copy from */ + /** + * @param credential existing credential to copy from + */ public StoredCredential(Credential credential) { setAccessToken(credential.getAccessToken()); setRefreshToken(credential.getRefreshToken()); diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java index cd4639daf..899402aa3 100644 --- a/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java +++ b/google-oauth-client/src/main/java/com/google/api/client/auth/openidconnect/IdTokenVerifier.java @@ -148,7 +148,9 @@ public IdTokenVerifier() { this(new Builder()); } - /** @param builder builder */ + /** + * @param builder builder + */ protected IdTokenVerifier(Builder builder) { this.certificatesLocation = builder.certificatesLocation; clock = builder.clock; @@ -552,7 +554,6 @@ public Map load(String certificateUrl) throws Exception { | InvalidKeySpecException | InvalidParameterSpecException ignored) { LOGGER.log(Level.WARNING, "Failed to put a key into the cache", ignored); - ignored.printStackTrace(); } } } diff --git a/samples/dailymotion-cmdline-sample/src/main/java/com/google/api/services/samples/dailymotion/cmdline/DailyMotionUrl.java b/samples/dailymotion-cmdline-sample/src/main/java/com/google/api/services/samples/dailymotion/cmdline/DailyMotionUrl.java index 55376cb00..a4a5b9795 100644 --- a/samples/dailymotion-cmdline-sample/src/main/java/com/google/api/services/samples/dailymotion/cmdline/DailyMotionUrl.java +++ b/samples/dailymotion-cmdline-sample/src/main/java/com/google/api/services/samples/dailymotion/cmdline/DailyMotionUrl.java @@ -30,12 +30,16 @@ public DailyMotionUrl(String encodedUrl) { super(encodedUrl); } - /** @return the fields */ + /** + * @return the fields + */ public String getFields() { return fields; } - /** @param fields the fields to set */ + /** + * @param fields the fields to set + */ public void setFields(String fields) { this.fields = fields; } diff --git a/samples/dailymotion-cmdline-sample/src/main/java/com/google/api/services/samples/dailymotion/cmdline/Video.java b/samples/dailymotion-cmdline-sample/src/main/java/com/google/api/services/samples/dailymotion/cmdline/Video.java index 7bbf169ed..6481f983d 100644 --- a/samples/dailymotion-cmdline-sample/src/main/java/com/google/api/services/samples/dailymotion/cmdline/Video.java +++ b/samples/dailymotion-cmdline-sample/src/main/java/com/google/api/services/samples/dailymotion/cmdline/Video.java @@ -17,7 +17,9 @@ import com.google.api.client.util.Key; import java.util.List; -/** @author Yaniv Inbar */ +/** + * @author Yaniv Inbar + */ public class Video { @Key public String id; diff --git a/samples/dailymotion-cmdline-sample/src/main/java/com/google/api/services/samples/dailymotion/cmdline/VideoFeed.java b/samples/dailymotion-cmdline-sample/src/main/java/com/google/api/services/samples/dailymotion/cmdline/VideoFeed.java index 91a4c606e..a68173ef8 100644 --- a/samples/dailymotion-cmdline-sample/src/main/java/com/google/api/services/samples/dailymotion/cmdline/VideoFeed.java +++ b/samples/dailymotion-cmdline-sample/src/main/java/com/google/api/services/samples/dailymotion/cmdline/VideoFeed.java @@ -17,7 +17,9 @@ import com.google.api.client.util.Key; import java.util.List; -/** @author Yaniv Inbar */ +/** + * @author Yaniv Inbar + */ public class VideoFeed { @Key public List