CVE-2020-7692: Incorrect Authorization (CWE-863)
value: PKCE support is not implemented in accordance with the RFC for OAuth 2.0 for Native Apps. Without the use of PKCE, the authorization code returned by an authorization server is not enough to guarantee that the client that issued the initial authorization request is the one that will be authorized. An attacker is able to obtain the authorization code using a malicious app on the client-side and use it to gain authorization to the protected resource. This affects the package com.google.oauth-client:google-oauth-client before 1.31.0.
CWE description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
CVSS v3 base score: 9.1
Published: 2020-07-09T14:15Z
Repository: https://github.com/googleapis/google-oauth-java-client
Language: Java
Files changed: google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/AuthorizationCodeFlow.java, google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/AuthorizationCodeRequestUrl.java, google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/AuthorizationCodeFlowTest.java, pom.xml, samples/dailymotion-cmdline-sample/README.md, samples/keycloak-pkce-cmdline-sample/README.md, samples/keycloak-pkce-cmdline-sample/pom.xml, samples/keycloak-pkce-cmdline-sample/scripts/initialize-keycloak.sh, samples/keycloak-pkce-cmdline-sample/src/main/java/com/google/api/services/samples/keycloak/cmdline/PKCESample.java
Commit date: 2020-06-29 12:57:20 -0700
Commit message: feat: add PKCE support to AuthorizationCodeFlow (#470)

* Initial test code for a PKCE enabled Authorization Code Flow

* WIP: work on README.md

* Script to initialize keycloak by adding client via REST API.

* Improve keycloak init script and some code cleanup. Still WIP.

* WIP: work on README.md

* Working PKCE AuthorizationCodeFlow. Some cleanup of test classes.

* Add scopes back to the AuthorizationCodeRequestUrl creation.

* Simplify code by moving PKCE entirely into the AuthorizationCodeFlow class. Add documentation.

* Remove wildcard imports as that seems to be the way to do things here.

* Add @since annotation in JavaDoc to the PKCE parameters of the autorization url class.

* Add PKCE unit test, documentation and minor cleanup of dependencies for code sample.

* Add PKCE unit test, documentation and minor cleanup of dependencies for code sample.

* Annotate PKCE with Beta annotation.

* Responding to code review comments

* Responding to more PR comments

* Improve Keycloak PKCE sample documentation

* Add license header with copyright to new files. Improve documentation.
Diff: diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/AuthorizationCodeFlow.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/AuthorizationCodeFlow.java
index 71ca8d2..52208f9 100644
--- a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/AuthorizationCodeFlow.java
+++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/AuthorizationCodeFlow.java
@@ -15,24 +15,32 @@
 package com.google.api.client.auth.oauth2;
 
 import com.google.api.client.auth.oauth2.Credential.AccessMethod;
 import com.google.api.client.http.GenericUrl;
 import com.google.api.client.http.HttpExecuteInterceptor;
+import com.google.api.client.http.HttpRequest;
 import com.google.api.client.http.HttpRequestInitializer;
 import com.google.api.client.http.HttpTransport;
+import com.google.api.client.http.UrlEncodedContent;
 import com.google.api.client.json.JsonFactory;
+import com.google.api.client.util.Base64;
 import com.google.api.client.util.Beta;
+import com.google.api.client.util.Data;
 import com.google.api.client.util.Clock;
 import com.google.api.client.util.Joiner;
 import com.google.api.client.util.Lists;
 import com.google.api.client.util.Preconditions;
 import com.google.api.client.util.store.DataStore;
 import com.google.api.client.util.store.DataStoreFactory;
 
 import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Map;
 
 import static com.google.api.client.util.Strings.isNullOrEmpty;
 
 /**
  * Thread-safe OAuth 2.0 authorization code flow that manages and persists end-user credentials.
@@ -83,10 +91,13 @@ public class AuthorizationCodeFlow {
   private final String clientId;
 
   /** Authorization server encoded URL. */
   private final String authorizationServerEncodedUrl;
 
+  /** The Proof Key for Code Exchange (PKCE) or {@code null} if this flow should not use PKCE. */
+  private final PKCE pkce;
+
   /** Credential persistence store or {@code null} for none. */
   @Beta
   @Deprecated
   private final CredentialStore credentialStore;
 
@@ -157,10 +168,11 @@ public class AuthorizationCodeFlow {
     credentialDataStore = builder.credentialDataStore;
     scopes = Collections.unmodifiableCollection(builder.scopes);
     clock = Preconditions.checkNotNull(builder.clock);
     credentialCreatedListener = builder.credentialCreatedListener;
     refreshListeners = Collections.unmodifiableCollection(builder.refreshListeners);
+    pkce = builder.pkce;
   }
 
   /**
    * Returns a new instance of an authorization code request URL.
    *
@@ -180,12 +192,17 @@ public class AuthorizationCodeFlow {
     response.sendRedirect(url);
   }
    * </pre>
    */
   public AuthorizationCodeRequestUrl newAuthorizationUrl() {
-    return new AuthorizationCodeRequestUrl(authorizationServerEncodedUrl, clientId).setScopes(
-        scopes);
+    AuthorizationCodeRequestUrl url = new  AuthorizationCodeRequestUrl(authorizationServerEncodedUrl, clientId);
+    url.setScopes(scopes);
+    if (pkce != null) {
+      url.setCodeChallenge(pkce.getChallenge());
+      url.setCodeChallengeMethod(pkce.getChallengeMethod());
+    }
+    return url;
   }
 
   /**
    * Returns a new instance of an authorization code token request based on the given authorization
    * code.
@@ -204,13 +221,24 @@ public class AuthorizationCodeFlow {
    * </pre>
    *
    * @param authorizationCode authorization code.
    */
   public AuthorizationCodeTokenRequest newTokenRequest(String authorizationCode) {
+    HttpExecuteInterceptor pkceClientAuthenticationWrapper = new HttpExecuteInterceptor() {
+      @Override
+      public void intercept(HttpRequest request) throws IOException {
+        clientAuthentication.intercept(request);
+        if (pkce != null) {
+          Map<String, Object> data = Data.mapOf(UrlEncodedContent.getContent(request).getData());
+          data.put("code_verifier", pkce.getVerifier());
+        }
+      }
+    };
+
     return new AuthorizationCodeTokenRequest(transport, jsonFactory,
         new GenericUrl(tokenServerEncodedUrl), authorizationCode).setClientAuthentication(
-        clientAuthentication).setRequestInitializer(requestInitializer).setScopes(scopes);
+        pkceClientAuthenticationWrapper).setRequestInitializer(requestInitializer).setScopes(scopes);
   }
 
   /**
    * Creates a new credential for the given user ID based on the given token response
    * and stores it in the credential store.
@@ -410,10 +438,65 @@ public class AuthorizationCodeFlow {
      * @param tokenResponse successful token response
      */
     void onCredentialCreated(Credential credential, TokenResponse tokenResponse) throws IOException;
   }
 
+  /**
+   * An implementation of <a href="https://tools.ietf.org/html/rfc7636">Proof Key for Code Exchange</a>
+   * which, according to the <a href="https://tools.ietf.org/html/rfc8252#section-6">OAuth 2.0 for Native Apps RFC</a>,
+   * is mandatory for public native apps.
+   */
+  private static class PKCE {
+    private final String verifier;
+    private String challenge;
+    private String challengeMethod;
+
+    public PKCE() {
+      verifier = generateVerifier();
+      generateChallenge(verifier);
+    }
+
+    private static String generateVerifier() {
+      SecureRandom sr = new SecureRandom();
+      byte[] code = new byte[32];
+      sr.nextBytes(code);
+      return Base64.encodeBase64URLSafeString(code);
+    }
+
+    /**
+     * Create the PKCE code verifier. It uses the S256 method but
+     * falls back to using the 'plain' method in the unlikely case
+     * that the SHA-256 MessageDigest algorithm implementation can't be
+     * loaded.
+     */
+    private void generateChallenge(String verifier) {
+      try {
+        byte[] bytes = verifier.getBytes();
+        MessageDigest md = MessageDigest.getInstance("SHA-256");
+        md.update(bytes, 0, bytes.length);
+        byte[] digest = md.digest();
+        challenge = Base64.encodeBase64URLSafeString(digest);
+        challengeMethod = "S256";
+      } catch (NoSuchAlgorithmException e) {
+        challenge = verifier;
+        challengeMethod = "plain";
+      }
+    }
+
+    public String getVerifier() {
+      return verifier;
+    }
+
+    public String getChallenge() {
+      return challenge;
+    }
+
+    public String getChallengeMethod() {
+      return challengeMethod;
+    }
+  }
+
   /**
    * Authorization code flow builder.
    *
    * <p>
    * Implementation is not thread-safe.
@@ -446,10 +529,12 @@ public class AuthorizationCodeFlow {
     String clientId;
 
     /** Authorization server encoded URL. */
     String authorizationServerEncodedUrl;
 
+    PKCE pkce;
+
     /** Credential persistence store or {@code null} for none. */
     @Deprecated
     @Beta
     CredentialStore credentialStore;
 
@@ -782,10 +867,20 @@ public class AuthorizationCodeFlow {
     public Builder setRequestInitializer(HttpRequestInitializer requestInitializer) {
       this.requestInitializer = requestInitializer;
       return this;
     }
 
+    /**
+     * Enables Proof Key for Code Exchange (PKCE) for this Athorization Code Flow.
+     * @since 1.31
+     */
+    @Beta
+    public Builder enablePKCE() {
+      this.pkce = new PKCE();
+      return this;
+    }
+
     /**
      * Sets the collection of scopes.
      *
      * <p>
      * Overriding is only supported for the purpose of calling the super implementation and changing
diff --git a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/AuthorizationCodeRequestUrl.java b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/AuthorizationCodeRequestUrl.java
index 3dc6bb4..025aaa2 100644
--- a/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/AuthorizationCodeRequestUrl.java
+++ b/google-oauth-client/src/main/java/com/google/api/client/auth/oauth2/AuthorizationCodeRequestUrl.java
@@ -12,10 +12,12 @@
  * the License.
  */
 
 package com.google.api.client.auth.oauth2;
 
+import com.google.api.client.util.Key;
+
 import java.util.Collection;
 import java.util.Collections;
 
 /**
  * OAuth 2.0 URL builder for an authorization web page to allow the end user to authorize the
@@ -50,18 +52,70 @@ import java.util.Collections;
  * @since 1.7
  * @author Yaniv Inbar
  */
 public class AuthorizationCodeRequestUrl extends AuthorizationRequestUrl {
 
+  /**
+   * The PKCE <a href="https://tools.ietf.org/html/rfc7636#section-4.3">Code Challenge</a>.
+   * @since 1.31
+   */
+  @Key("code_challenge")
+  String codeChallenge;
+
+  /**
+   * The PKCE <a href="https://tools.ietf.org/html/rfc7636#section-4.3">Code Challenge Method</a>.
+   * @since 1.31
+   */
+  @Key("code_challenge_method")
+  String codeChallengeMethod;
+
   /**
    * @param authorizationServerEncodedUrl authorization server encoded URL
    * @param clientId client identifier
    */
   public AuthorizationCodeRequestUrl(String authorizationServerEncodedUrl, String clientId) {
     super(authorizationServerEncodedUrl, clientId, Collections.singleton("code"));
   }
 
+  /**
+   * Get the code challenge (<a href="https://tools.ietf.org/html/rfc7636#section-4.3">details</a>).
+   *
+   * @since 1.31
+   */
+  public String getCodeChallenge() {
+    return codeChallenge;
+  }
+
+  /**
+   * Get the code challenge method (<a href="https://tools.ietf.org/html/rfc7636#section-4.3">details</a>).
+   *
+   * @since 1.31
+   */
+  public String getCodeChallengeMethod() {
+    return codeChallengeMethod;
+  }
+
+  /**
+   * Set the code challenge (<a href="https://tools.ietf.org/html/rfc7636#section-4.3">details</a>).
+   * @param codeChallenge the code challenge.
+   *
+   * @since 1.31
+   */
+  public void setCodeChallenge(String codeChallenge) {
+    this.codeChallenge = codeChallenge;
+  }
+
+  /**
+   * Set the code challenge method (<a href="https://tools.ietf.org/html/rfc7636#section-4.3">details</a>).
+   * @param codeChallengeMethod the code challenge method.
+   *
+   * @since 1.31
+   */
+  public void setCodeChallengeMethod(String codeChallengeMethod) {
+    this.codeChallengeMethod = codeChallengeMethod;
+  }
+
   @Override
   public AuthorizationCodeRequestUrl setResponseTypes(Collection<String> responseTypes) {
     return (AuthorizationCodeRequestUrl) super.setResponseTypes(responseTypes);
   }
 
diff --git a/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/AuthorizationCodeFlowTest.java b/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/AuthorizationCodeFlowTest.java
index 75f65f5..e5bed2e 100644
--- a/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/AuthorizationCodeFlowTest.java
+++ b/google-oauth-client/src/test/java/com/google/api/client/auth/oauth2/AuthorizationCodeFlowTest.java
@@ -21,10 +21,12 @@ import com.google.api.client.util.Joiner;
 
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Tests {@link AuthorizationCodeFlow}.
  *
  * @author Yaniv Inbar
@@ -121,6 +123,26 @@ public class AuthorizationCodeFlowTest extends AuthenticationTestBase {
       assertNull(url.getScopes());
     } else {
       assertEquals(Joiner.on(' ').join(scopes), url.getScopes());
     }
   }
+
+  public void testPKCE() {
+    AuthorizationCodeFlow flow =
+        new AuthorizationCodeFlow.Builder(BearerToken.queryParameterAccessMethod(),
+            new AccessTokenTransport(),
+            new JacksonFactory(),
+            TOKEN_SERVER_URL,
+            new BasicAuthentication(CLIENT_ID, CLIENT_SECRET),
+            CLIENT_ID,
+            "https://example.com")
+        .enablePKCE()
+        .build();
+
+    AuthorizationCodeRequestUrl url = flow.newAuthorizationUrl();
+    assertNotNull(url.getCodeChallenge());
+    assertNotNull(url.getCodeChallengeMethod());
+    Set<String> methods = new HashSet<>(Arrays.asList("plain", "s256"));
+    assertTrue(methods.contains(url.getCodeChallengeMethod().toLowerCase()));
+    assertTrue(url.getCodeChallenge().length() > 0);
+  }
 }
diff --git a/pom.xml b/pom.xml
index 83b10f4..40d7267 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,10 +63,11 @@
     <module>google-oauth-client-bom</module>
     <module>google-oauth-client-servlet</module>
     <module>google-oauth-client-java6</module>
     <module>google-oauth-client-jetty</module>
     <module>samples/dailymotion-cmdline-sample</module>
+    <module>samples/keycloak-pkce-cmdline-sample</module>
 
     <!-- For deployment reasons, a deployable artifact must be the last one. -->
     <module>google-oauth-client-assembly</module>
   </modules>
 
diff --git a/samples/dailymotion-cmdline-sample/README.md b/samples/dailymotion-cmdline-sample/README.md
index 82b4063..c4a1fbb 100644
--- a/samples/dailymotion-cmdline-sample/README.md
+++ b/samples/dailymotion-cmdline-sample/README.md
@@ -4,11 +4,11 @@
 
 [Browse Source][browse-source], or main file [DailyMotionSample.java][main-source].
 
 ## Command-Line Instructions
 
-**Prerequisites:** install [Java 6 or higher][install-java], [git][install-git], and
+**Prerequisites:** install [Java 7 or higher][install-java], [git][install-git], and
 [Maven][install-maven]. You may need to set your `JAVA_HOME`.
 
 1. Check out the sample code:
 
     ```bash
diff --git a/samples/keycloak-pkce-cmdline-sample/README.md b/samples/keycloak-pkce-cmdline-sample/README.md
new file mode 100644
index 0000000..c5b5416
--- /dev/null
+++ b/samples/keycloak-pkce-cmdline-sample/README.md
@@ -0,0 +1,42 @@
+# Instructions for the Keycloak OAuth2 with PKCE Command-Line Sample
+
+## Browse Online
+
+[Browse Source][browse-source], or main file [PKCESample.java][main-source].
+
+## Command-Line Instructions
+
+**Prerequisites:** install [Java 7 or higher][install-java], [git][install-git], and
+[Maven][install-maven]. You may need to set your `JAVA_HOME`. 
+You'll also need [Docker][install-docker].
+
+1. Check out the sample code:
+
+    ```bash
+    git clone https://github.com/google/google-oauth-java-client.git
+    cd google-oauth-java-client
+    ```
+
+2. Run keycloak in a docker container:
+
+   ```
+   docker run -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:10.0.1 
+   ```
+
+3. Run the sample:
+
+    ```bash
+    mvn install
+    mvn exec:java -pl samples/keycloak-pkce-cmdline-sample
+    ```
+   
+   This will open up the Keycloak login page where you can log in with the username/password specified
+   when running the Keycloak docker container above (`admin / admin`). Once you log in, the application
+   will print out a message that it successfully obtained an access token.
+
+[browse-source]: https://github.com/google/google-oauth-java-client/tree/dev/samples/keycloak-pkce-cmdline-sample
+[main-source]: https://github.com/google/google-oauth-java-client/blob/dev/samples/keycloak-pkce-cmdline-sample/src/main/java/com/google/api/services/samples/keycloak/cmdline/PKCESample.java
+[install-java]: https://java.com/
+[install-git]: https://git-scm.com
+[install-maven]: https://maven.apache.org
+[install-docker]: https://docs.docker.com/get-docker/
\ No newline at end of file
diff --git a/samples/keycloak-pkce-cmdline-sample/pom.xml b/samples/keycloak-pkce-cmdline-sample/pom.xml
new file mode 100644
index 0000000..b19a662
--- /dev/null
+++ b/samples/keycloak-pkce-cmdline-sample/pom.xml
@@ -0,0 +1,104 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>com.google.oauth-client</groupId>
+    <artifactId>google-oauth-client-parent</artifactId>
+    <version>1.30.7-SNAPSHOT</version><!-- {x-version-update:google-oauth-client:current} -->
+    <relativePath>../../pom.xml</relativePath>
+  </parent>
+  <artifactId>keycloak-pkce-cmdline-sample</artifactId>
+  <name>Example for obtaining OAuth2 tokens with PKCE verification using the Authorization Code Flow against Keycloak.</name>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <version>1.6.0</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>java</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <mainClass>com.google.api.services.samples.keycloak.cmdline.PKCESample</mainClass>
+          <systemProperties>
+            <systemProperty>
+              <key>java.util.logging.config.file</key>
+              <value>logging.properties</value>
+            </systemProperty>
+          </systemProperties>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <version>2.6</version>
+        <configuration>
+          <configLocation>../checkstyle.xml</configLocation>
+          <consoleOutput>true</consoleOutput>
+          <failOnViolation>false</failOnViolation>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>check</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <version>3.0.5</version>
+        <configuration>
+          <excludeFilterFile>../../findbugs-exclude.xml</excludeFilterFile>
+          <failOnError>false</failOnError>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>check</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <version>2.8.2</version>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.sonatype.plugins</groupId>
+        <artifactId>nexus-staging-maven-plugin</artifactId>
+        <version>1.6.8</version>
+        <configuration>
+          <skipNexusStagingDeployMojo>true</skipNexusStagingDeployMojo>
+        </configuration>
+      </plugin>
+    </plugins>
+    <finalName>${project.artifactId}-${project.version}</finalName>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>com.google.oauth-client</groupId>
+      <artifactId>google-oauth-client</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.oauth-client</groupId>
+      <artifactId>google-oauth-client-jetty</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.http-client</groupId>
+      <artifactId>google-http-client-jackson2</artifactId>
+    </dependency>
+  </dependencies>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+</project>
diff --git a/samples/keycloak-pkce-cmdline-sample/scripts/initialize-keycloak.sh b/samples/keycloak-pkce-cmdline-sample/scripts/initialize-keycloak.sh
new file mode 100755
index 0000000..257da7d
--- /dev/null
+++ b/samples/keycloak-pkce-cmdline-sample/scripts/initialize-keycloak.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+#Start keycloak server before running this script:
+# docker run -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:10.0.1
+
+# The following script will create a new public client in the running keycloak server
+# in which PKCE is required for obtaining an authorization token via the authorization
+# code flow. Once this script has been run, the PKCESample.java sample application can
+# be run.
+
+KEYCLOAK_BASE_URL="http://localhost:8080/auth"
+KEYCLOAK_REALM="master"
+KEYCLOAK_URL="${KEYCLOAK_BASE_URL}/realms/${KEYCLOAK_REALM}"
+
+KEYCLOAK_CLIENT_ID="admin"
+KEYCLOAK_CLIENT_SECRET="admin"
+
+export TKN=$(curl -s -X POST "${KEYCLOAK_URL}/protocol/openid-connect/token" \
+ -H "Content-Type: application/x-www-form-urlencoded" \
+ -d "username=${KEYCLOAK_CLIENT_ID}" \
+ -d "password=${KEYCLOAK_CLIENT_SECRET}" \
+ -d 'grant_type=password' \
+ -d 'client_id=admin-cli' | jq -r '.access_token')
+
+curl -s -X POST "${KEYCLOAK_URL}/clients-registrations/default" \
+ -d '{ "clientId": "pkce-test-client", "publicClient": true, "redirectUris": ["http://127.0.0.1*"], "attributes": {"pkce.code.challenge.method": "S256"} }' \
+ -H "Content-Type:application/json" \
+ -H "Authorization: bearer ${TKN}" 
+
+ 
\ No newline at end of file
diff --git a/samples/keycloak-pkce-cmdline-sample/src/main/java/com/google/api/services/samples/keycloak/cmdline/PKCESample.java b/samples/keycloak-pkce-cmdline-sample/src/main/java/com/google/api/services/samples/keycloak/cmdline/PKCESample.java
new file mode 100644
index 0000000..8fa63ca
--- /dev/null
+++ b/samples/keycloak-pkce-cmdline-sample/src/main/java/com/google/api/services/samples/keycloak/cmdline/PKCESample.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2020 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.api.services.samples.keycloak.cmdline;
+
+import com.google.api.client.auth.oauth2.AuthorizationCodeFlow;
+import com.google.api.client.auth.oauth2.BearerToken;
+import com.google.api.client.auth.oauth2.ClientParametersAuthentication;
+import com.google.api.client.auth.oauth2.Credential;
+import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
+import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
+import com.google.api.client.http.GenericUrl;
+import com.google.api.client.http.HttpTransport;
+import com.google.api.client.http.javanet.NetHttpTransport;
+import com.google.api.client.json.JsonFactory;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.google.api.client.util.store.DataStoreFactory;
+import com.google.api.client.util.store.MemoryDataStoreFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * A sample application that demonstrates how the Google OAuth2 library can be used to authenticate
+ * against a locally running Keycloak server with a registered public client where using
+ * <a href="https://tools.ietf.org/html/rfc7636">PKCE</a> is required.
+ *
+ * Please note that before running this sample application, a local Keycloak server must be running
+ * and a PKCE enabled client must have been defined. Please see
+ * <code>samples/keycloak-pkce-cmdline-sample/scripts/initialize-keycloak.sh</code> for further
+ * information.
+ *
+ * @author Stefan Freyr Stefansson
+ */
+public class PKCESample {
+    /**
+     * Global instance of the {@link DataStoreFactory}. The best practice is to make it a single
+     * globally shared instance across your application.
+     */
+    private static DataStoreFactory DATA_STORE_FACTORY;
+
+    /** OAuth 2 scope. */
+    private static final String SCOPE = "email";
+
+    /** Global instance of the HTTP transport. */
+    private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
+
+    /** Global instance of the JSON factory. */
+    static final JsonFactory JSON_FACTORY = new JacksonFactory();
+
+    private static final String TOKEN_SERVER_URL = "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token";
+    private static final String AUTHORIZATION_SERVER_URL = "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth";
+
+    /** Authorizes the installed application to access user's protected data. */
+    private static Credential authorize() throws Exception {
+        // set up authorization code flow
+        String clientId = "pkce-test-client";
+        AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(
+                BearerToken.authorizationHeaderAccessMethod(),
+                HTTP_TRANSPORT,
+                JSON_FACTORY,
+                new GenericUrl(TOKEN_SERVER_URL),
+                new ClientParametersAuthentication(clientId, null),
+                clientId,
+                AUTHORIZATION_SERVER_URL)
+                .setScopes(Arrays.asList(SCOPE))
+                .enablePKCE()
+                .setDataStoreFactory(DATA_STORE_FACTORY).build();
+        // authorize
+        LocalServerReceiver receiver = new LocalServerReceiver.Builder().setHost("127.0.0.1").build();
+        return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
+    }
+
+    public static void main(String[] args) {
+        try {
+            DATA_STORE_FACTORY = new MemoryDataStoreFactory();
+            final Credential credential = authorize();
+            System.out.println("Successfully obtained credential from Keycloak running on localhost.");
+            final String accessToken = credential.getAccessToken();
+            System.out.println("Retrieved an access token of length " + accessToken.length());
+            return;
+        } catch (IOException e) {
+            System.err.println(e.getMessage());
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+        System.exit(1);
+    }
+}

Vulnerable code:     return new AuthorizationCodeRequestUrl(authorizationServerEncodedUrl, clientId).setScopes(
        scopes);
        clientAuthentication).setRequestInitializer(requestInitializer).setScopes(scopes);
**Prerequisites:** install [Java 6 or higher][install-java], [git][install-git], and
Fixed code: import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.UrlEncodedContent;
import com.google.api.client.util.Base64;
import com.google.api.client.util.Data;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Map;
  /** The Proof Key for Code Exchange (PKCE) or {@code null} if this flow should not use PKCE. */
  private final PKCE pkce;

    pkce = builder.pkce;
    AuthorizationCodeRequestUrl url = new  AuthorizationCodeRequestUrl(authorizationServerEncodedUrl, clientId);
    url.setScopes(scopes);
    if (pkce != null) {
      url.setCodeChallenge(pkce.getChallenge());
      url.setCodeChallengeMethod(pkce.getChallengeMethod());
    }
    return url;
    HttpExecuteInterceptor pkceClientAuthenticationWrapper = new HttpExecuteInterceptor() {
      @Override
      public void intercept(HttpRequest request) throws IOException {
        clientAuthentication.intercept(request);
        if (pkce != null) {
          Map<String, Object> data = Data.mapOf(UrlEncodedContent.getContent(request).getData());
          data.put("code_verifier", pkce.getVerifier());
        }
      }
    };

        pkceClientAuthenticationWrapper).setRequestInitializer(requestInitializer).setScopes(scopes);
  /**
   * An implementation of <a href="https://tools.ietf.org/html/rfc7636">Proof Key for Code Exchange</a>
   * which, according to the <a href="https://tools.ietf.org/html/rfc8252#section-6">OAuth 2.0 for Native Apps RFC</a>,
   * is mandatory for public native apps.
   */
  private static class PKCE {
    private final String verifier;
    private String challenge;
    private String challengeMethod;

    public PKCE() {
      verifier = generateVerifier();
      generateChallenge(verifier);
    }

    private static String generateVerifier() {
      SecureRandom sr = new SecureRandom();
      byte[] code = new byte[32];
      sr.nextBytes(code);
      return Base64.encodeBase64URLSafeString(code);
    }

    /**
     * Create the PKCE code verifier. It uses the S256 method but
     * falls back to using the 'plain' method in the unlikely case
     * that the SHA-256 MessageDigest algorithm implementation can't be
     * loaded.
     */
    private void generateChallenge(String verifier) {
      try {
        byte[] bytes = verifier.getBytes();
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(bytes, 0, bytes.length);
        byte[] digest = md.digest();
        challenge = Base64.encodeBase64URLSafeString(digest);
        challengeMethod = "S256";
      } catch (NoSuchAlgorithmException e) {
        challenge = verifier;
        challengeMethod = "plain";
      }
    }

    public String getVerifier() {
      return verifier;
    }

    public String getChallenge() {
      return challenge;
    }

    public String getChallengeMethod() {
      return challengeMethod;
    }
  }

    PKCE pkce;

    /**
     * Enables Proof Key for Code Exchange (PKCE) for this Athorization Code Flow.
     * @since 1.31
     */
    @Beta
    public Builder enablePKCE() {
      this.pkce = new PKCE();
      return this;
    }

import com.google.api.client.util.Key;

  /**
   * The PKCE <a href="https://tools.ietf.org/html/rfc7636#section-4.3">Code Challenge</a>.
   * @since 1.31
   */
  @Key("code_challenge")
  String codeChallenge;

  /**
   * The PKCE <a href="https://tools.ietf.org/html/rfc7636#section-4.3">Code Challenge Method</a>.
   * @since 1.31
   */
  @Key("code_challenge_method")
  String codeChallengeMethod;

  /**
   * Get the code challenge (<a href="https://tools.ietf.org/html/rfc7636#section-4.3">details</a>).
   *
   * @since 1.31
   */
  public String getCodeChallenge() {
    return codeChallenge;
  }

  /**
   * Get the code challenge method (<a href="https://tools.ietf.org/html/rfc7636#section-4.3">details</a>).
   *
   * @since 1.31
   */
  public String getCodeChallengeMethod() {
    return codeChallengeMethod;
  }

  /**
   * Set the code challenge (<a href="https://tools.ietf.org/html/rfc7636#section-4.3">details</a>).
   * @param codeChallenge the code challenge.
   *
   * @since 1.31
   */
  public void setCodeChallenge(String codeChallenge) {
    this.codeChallenge = codeChallenge;
  }

  /**
   * Set the code challenge method (<a href="https://tools.ietf.org/html/rfc7636#section-4.3">details</a>).
   * @param codeChallengeMethod the code challenge method.
   *
   * @since 1.31
   */
  public void setCodeChallengeMethod(String codeChallengeMethod) {
    this.codeChallengeMethod = codeChallengeMethod;
  }

import java.util.HashSet;
import java.util.Set;

  public void testPKCE() {
    AuthorizationCodeFlow flow =
        new AuthorizationCodeFlow.Builder(BearerToken.queryParameterAccessMethod(),
            new AccessTokenTransport(),
            new JacksonFactory(),
            TOKEN_SERVER_URL,
            new BasicAuthentication(CLIENT_ID, CLIENT_SECRET),
            CLIENT_ID,
            "https://example.com")
        .enablePKCE()
        .build();

    AuthorizationCodeRequestUrl url = flow.newAuthorizationUrl();
    assertNotNull(url.getCodeChallenge());
    assertNotNull(url.getCodeChallengeMethod());
    Set<String> methods = new HashSet<>(Arrays.asList("plain", "s256"));
    assertTrue(methods.contains(url.getCodeChallengeMethod().toLowerCase()));
    assertTrue(url.getCodeChallenge().length() > 0);
  }
    <module>samples/keycloak-pkce-cmdline-sample</module>
**Prerequisites:** install [Java 7 or higher][install-java], [git][install-git], and
# Instructions for the Keycloak OAuth2 with PKCE Command-Line Sample

## Browse Online

[Browse Source][browse-source], or main file [PKCESample.java][main-source].

## Command-Line Instructions

**Prerequisites:** install [Java 7 or higher][install-java], [git][install-git], and
[Maven][install-maven]. You may need to set your `JAVA_HOME`. 
You'll also need [Docker][install-docker].

1. Check out the sample code:

    ```bash
    git clone https://github.com/google/google-oauth-java-client.git
    cd google-oauth-java-client
    ```

2. Run keycloak in a docker container:

   ```
   docker run -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:10.0.1 
   ```

3. Run the sample:

    ```bash
    mvn install
    mvn exec:java -pl samples/keycloak-pkce-cmdline-sample
    ```
   
   This will open up the Keycloak login page where you can log in with the username/password specified
   when running the Keycloak docker container above (`admin / admin`). Once you log in, the application
   will print out a message that it successfully obtained an access token.

[browse-source]: https://github.com/google/google-oauth-java-client/tree/dev/samples/keycloak-pkce-cmdline-sample
[main-source]: https://github.com/google/google-oauth-java-client/blob/dev/samples/keycloak-pkce-cmdline-sample/src/main/java/com/google/api/services/samples/keycloak/cmdline/PKCESample.java
[install-java]: https://java.com/
[install-git]: https://git-scm.com
[install-maven]: https://maven.apache.org
[install-docker]: https://docs.docker.com/get-docker/
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.google.oauth-client</groupId>
    <artifactId>google-oauth-client-parent</artifactId>
    <version>1.30.7-SNAPSHOT</version><!-- {x-version-update:google-oauth-client:current} -->
    <relativePath>../../pom.xml</relativePath>
  </parent>
  <artifactId>keycloak-pkce-cmdline-sample</artifactId>
  <name>Example for obtaining OAuth2 tokens with PKCE verification using the Authorization Code Flow against Keycloak.</name>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.6.0</version>
        <executions>
          <execution>
            <goals>
              <goal>java</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <mainClass>com.google.api.services.samples.keycloak.cmdline.PKCESample</mainClass>
          <systemProperties>
            <systemProperty>
              <key>java.util.logging.config.file</key>
              <value>logging.properties</value>
            </systemProperty>
          </systemProperties>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-checkstyle-plugin</artifactId>
        <version>2.6</version>
        <configuration>
          <configLocation>../checkstyle.xml</configLocation>
          <consoleOutput>true</consoleOutput>
          <failOnViolation>false</failOnViolation>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>check</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>findbugs-maven-plugin</artifactId>
        <version>3.0.5</version>
        <configuration>
          <excludeFilterFile>../../findbugs-exclude.xml</excludeFilterFile>
          <failOnError>false</failOnError>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>check</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-deploy-plugin</artifactId>
        <version>2.8.2</version>
        <configuration>
          <skip>true</skip>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.sonatype.plugins</groupId>
        <artifactId>nexus-staging-maven-plugin</artifactId>
        <version>1.6.8</version>
        <configuration>
          <skipNexusStagingDeployMojo>true</skipNexusStagingDeployMojo>
        </configuration>
      </plugin>
    </plugins>
    <finalName>${project.artifactId}-${project.version}</finalName>
  </build>
  <dependencies>
    <dependency>
      <groupId>com.google.oauth-client</groupId>
      <artifactId>google-oauth-client</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.oauth-client</groupId>
      <artifactId>google-oauth-client-jetty</artifactId>
    </dependency>
    <dependency>
      <groupId>com.google.http-client</groupId>
      <artifactId>google-http-client-jackson2</artifactId>
    </dependency>
  </dependencies>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
</project>
#!/bin/sh
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


#Start keycloak server before running this script:
# docker run -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:10.0.1

# The following script will create a new public client in the running keycloak server
# in which PKCE is required for obtaining an authorization token via the authorization
# code flow. Once this script has been run, the PKCESample.java sample application can
# be run.

KEYCLOAK_BASE_URL="http://localhost:8080/auth"
KEYCLOAK_REALM="master"
KEYCLOAK_URL="${KEYCLOAK_BASE_URL}/realms/${KEYCLOAK_REALM}"

KEYCLOAK_CLIENT_ID="admin"
KEYCLOAK_CLIENT_SECRET="admin"

export TKN=$(curl -s -X POST "${KEYCLOAK_URL}/protocol/openid-connect/token" \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -d "username=${KEYCLOAK_CLIENT_ID}" \
 -d "password=${KEYCLOAK_CLIENT_SECRET}" \
 -d 'grant_type=password' \
 -d 'client_id=admin-cli' | jq -r '.access_token')

curl -s -X POST "${KEYCLOAK_URL}/clients-registrations/default" \
 -d '{ "clientId": "pkce-test-client", "publicClient": true, "redirectUris": ["http://127.0.0.1*"], "attributes": {"pkce.code.challenge.method": "S256"} }' \
 -H "Content-Type:application/json" \
 -H "Authorization: bearer ${TKN}" 

 
/*
 * Copyright (c) 2020 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.api.services.samples.keycloak.cmdline;

import com.google.api.client.auth.oauth2.AuthorizationCodeFlow;
import com.google.api.client.auth.oauth2.BearerToken;
import com.google.api.client.auth.oauth2.ClientParametersAuthentication;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.store.DataStoreFactory;
import com.google.api.client.util.store.MemoryDataStoreFactory;

import java.io.IOException;
import java.util.Arrays;

/**
 * A sample application that demonstrates how the Google OAuth2 library can be used to authenticate
 * against a locally running Keycloak server with a registered public client where using
 * <a href="https://tools.ietf.org/html/rfc7636">PKCE</a> is required.
 *
 * Please note that before running this sample application, a local Keycloak server must be running
 * and a PKCE enabled client must have been defined. Please see
 * <code>samples/keycloak-pkce-cmdline-sample/scripts/initialize-keycloak.sh</code> for further
 * information.
 *
 * @author Stefan Freyr Stefansson
 */
public class PKCESample {
    /**
     * Global instance of the {@link DataStoreFactory}. The best practice is to make it a single
     * globally shared instance across your application.
     */
    private static DataStoreFactory DATA_STORE_FACTORY;

    /** OAuth 2 scope. */
    private static final String SCOPE = "email";

    /** Global instance of the HTTP transport. */
    private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();

    /** Global instance of the JSON factory. */
    static final JsonFactory JSON_FACTORY = new JacksonFactory();

    private static final String TOKEN_SERVER_URL = "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token";
    private static final String AUTHORIZATION_SERVER_URL = "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth";

    /** Authorizes the installed application to access user's protected data. */
    private static Credential authorize() throws Exception {
        // set up authorization code flow
        String clientId = "pkce-test-client";
        AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(
                BearerToken.authorizationHeaderAccessMethod(),
                HTTP_TRANSPORT,
                JSON_FACTORY,
                new GenericUrl(TOKEN_SERVER_URL),
                new ClientParametersAuthentication(clientId, null),
                clientId,
                AUTHORIZATION_SERVER_URL)
                .setScopes(Arrays.asList(SCOPE))
                .enablePKCE()
                .setDataStoreFactory(DATA_STORE_FACTORY).build();
        // authorize
        LocalServerReceiver receiver = new LocalServerReceiver.Builder().setHost("127.0.0.1").build();
        return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
    }

    public static void main(String[] args) {
        try {
            DATA_STORE_FACTORY = new MemoryDataStoreFactory();
            final Credential credential = authorize();
            System.out.println("Successfully obtained credential from Keycloak running on localhost.");
            final String accessToken = credential.getAccessToken();
            System.out.println("Retrieved an access token of length " + accessToken.length());
            return;
        } catch (IOException e) {
            System.err.println(e.getMessage());
        } catch (Throwable t) {
            t.printStackTrace();
        }
        System.exit(1);
    }
}