mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-31 00:53:21 +00:00 
			
		
		
		
	GUACAMOLE-210: Properly generate and validate nonces.
This commit is contained in:
		| @@ -25,6 +25,7 @@ import java.util.Arrays; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import org.apache.guacamole.auth.openid.conf.ConfigurationService; | ||||
| import org.apache.guacamole.auth.openid.form.TokenField; | ||||
| import org.apache.guacamole.auth.openid.token.NonceService; | ||||
| import org.apache.guacamole.auth.openid.token.TokenValidationService; | ||||
| import org.apache.guacamole.auth.openid.user.AuthenticatedUser; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| @@ -52,6 +53,12 @@ public class AuthenticationProviderService { | ||||
|     @Inject | ||||
|     private ConfigurationService confService; | ||||
|  | ||||
|     /** | ||||
|      * Service for validating and generating unique nonce values. | ||||
|      */ | ||||
|     @Inject | ||||
|     private NonceService nonceService; | ||||
|  | ||||
|     /** | ||||
|      * Service for validating received ID tokens. | ||||
|      */ | ||||
| @@ -112,7 +119,8 @@ public class AuthenticationProviderService { | ||||
|                 new TokenField( | ||||
|                     confService.getAuthorizationEndpoint(), | ||||
|                     confService.getClientID(), | ||||
|                     confService.getRedirectURI() | ||||
|                     confService.getRedirectURI(), | ||||
|                     nonceService.generate(30000 /* FIXME: Calculate appropriate value based on configuration */) | ||||
|                 ) | ||||
|  | ||||
|             })) | ||||
|   | ||||
| @@ -21,6 +21,7 @@ package org.apache.guacamole.auth.openid; | ||||
|  | ||||
| import com.google.inject.AbstractModule; | ||||
| import org.apache.guacamole.auth.openid.conf.ConfigurationService; | ||||
| import org.apache.guacamole.auth.openid.token.NonceService; | ||||
| import org.apache.guacamole.auth.openid.token.TokenValidationService; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.environment.Environment; | ||||
| @@ -74,6 +75,7 @@ public class OpenIDAuthenticationProviderModule extends AbstractModule { | ||||
|  | ||||
|         // Bind openid-specific services | ||||
|         bind(ConfigurationService.class); | ||||
|         bind(NonceService.class); | ||||
|         bind(TokenValidationService.class); | ||||
|  | ||||
|     } | ||||
|   | ||||
| @@ -20,15 +20,12 @@ | ||||
| package org.apache.guacamole.auth.openid.form; | ||||
|  | ||||
| import java.io.UnsupportedEncodingException; | ||||
| import java.math.BigInteger; | ||||
| import java.net.URLEncoder; | ||||
| import java.security.SecureRandom; | ||||
| import org.apache.guacamole.form.Field; | ||||
|  | ||||
| /** | ||||
|  * Field definition which represents the token returned by an OpenID service. | ||||
|  * Within the user interface, this will be rendered as an appropriate "Log in | ||||
|  * with ..." button which links to the OpenID service. | ||||
|  * Field definition which represents the token returned by an OpenID Connect | ||||
|  * service. | ||||
|  */ | ||||
| public class TokenField extends Field { | ||||
|  | ||||
| @@ -44,29 +41,12 @@ public class TokenField extends Field { | ||||
|     private final String authorizationURI; | ||||
|  | ||||
|     /** | ||||
|      * Cryptographically-secure random number generator for generating the | ||||
|      * required nonce. | ||||
|      */ | ||||
|     private static final SecureRandom random = new SecureRandom(); | ||||
|  | ||||
|     /** | ||||
|      * Generates a cryptographically-secure nonce value. The nonce is intended | ||||
|      * to be used to prevent replay attacks. | ||||
|      * | ||||
|      * @return | ||||
|      *     A cryptographically-secure nonce value. | ||||
|      */ | ||||
|     private static String generateNonce() { | ||||
|         return new BigInteger(130, random).toString(32); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a new OpenID "id_token" field which links to the given OpenID | ||||
|      * service using the provided client ID. Successful authentication at the | ||||
|      * OpenID service will result in the client being redirected to the specified | ||||
|      * redirect URI. The OpenID token will be embedded in the fragment (the part | ||||
|      * following the hash symbol) of that URI, which the JavaScript side of | ||||
|      * this extension will move to the query parameters. | ||||
|      * Creates a new field which requests authentication via OpenID connect. | ||||
|      * Successful authentication at the OpenID Connect service will result in | ||||
|      * the client being redirected to the specified redirect URI. The OpenID | ||||
|      * token will be embedded in the fragment (the part following the hash | ||||
|      * symbol) of that URI, which the JavaScript side of this extension will | ||||
|      * move to the query parameters. | ||||
|      * | ||||
|      * @param authorizationEndpoint | ||||
|      *     The full URL of the endpoint accepting OpenID authentication | ||||
| @@ -80,9 +60,13 @@ public class TokenField extends Field { | ||||
|      * @param redirectURI | ||||
|      *     The URI that the OpenID service should redirect to upon successful | ||||
|      *     authentication. | ||||
|      * | ||||
|      * @param nonce | ||||
|      *     A random string unique to this request. To defend against replay | ||||
|      *     attacks, this value must cease being valid after its first use. | ||||
|      */ | ||||
|     public TokenField(String authorizationEndpoint, String clientID, | ||||
|             String redirectURI) { | ||||
|             String redirectURI, String nonce) { | ||||
|  | ||||
|         // Init base field properties | ||||
|         super(PARAMETER_NAME, "GUAC_OPENID_TOKEN"); | ||||
| @@ -94,7 +78,7 @@ public class TokenField extends Field { | ||||
|                     + "&response_type=id_token" | ||||
|                     + "&client_id=" + URLEncoder.encode(clientID, "UTF-8") | ||||
|                     + "&redirect_uri=" + URLEncoder.encode(redirectURI, "UTF-8") | ||||
|                     + "&nonce=" + generateNonce(); | ||||
|                     + "&nonce=" + nonce; | ||||
|         } | ||||
|  | ||||
|         // Java is required to provide UTF-8 support | ||||
|   | ||||
| @@ -0,0 +1,135 @@ | ||||
| /* | ||||
|  * Licensed to the Apache Software Foundation (ASF) under one | ||||
|  * or more contributor license agreements.  See the NOTICE file | ||||
|  * distributed with this work for additional information | ||||
|  * regarding copyright ownership.  The ASF licenses this file | ||||
|  * to you 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 org.apache.guacamole.auth.openid.token; | ||||
|  | ||||
| import com.google.inject.Singleton; | ||||
| import java.math.BigInteger; | ||||
| import java.security.SecureRandom; | ||||
| import java.util.Iterator; | ||||
| import java.util.Map; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
|  | ||||
| /** | ||||
|  * Service for generating and validating single-use random tokens (nonces). | ||||
|  */ | ||||
| @Singleton | ||||
| public class NonceService { | ||||
|  | ||||
|     /** | ||||
|      * Cryptographically-secure random number generator for generating the | ||||
|      * required nonce. | ||||
|      */ | ||||
|     private final SecureRandom random = new SecureRandom(); | ||||
|  | ||||
|     /** | ||||
|      * Map of all generated nonces to their corresponding expiration timestamps. | ||||
|      * This Map must be periodically swept of expired nonces to avoid growing | ||||
|      * without bound. | ||||
|      */ | ||||
|     private final Map<String, Long> nonces = new ConcurrentHashMap<String, Long>(); | ||||
|  | ||||
|     /** | ||||
|      * The timestamp of the last expired nonce sweep. | ||||
|      */ | ||||
|     private long lastSweep = System.currentTimeMillis(); | ||||
|  | ||||
|     /** | ||||
|      * The minimum amount of time to wait between sweeping expired nonces from | ||||
|      * the Map. | ||||
|      */ | ||||
|     private static final long SWEEP_INTERVAL = 60000; | ||||
|  | ||||
|     /** | ||||
|      * Iterates through the entire Map of generated nonces, removing any nonce | ||||
|      * that has exceeded its expiration timestamp. If insufficient time has | ||||
|      * elapsed since the last sweep, as dictated by SWEEP_INTERVAL, this | ||||
|      * function has no effect. | ||||
|      */ | ||||
|     private void sweepExpiredNonces() { | ||||
|  | ||||
|         // Do not sweep until enough time has elapsed since the last sweep | ||||
|         long currentTime = System.currentTimeMillis(); | ||||
|         if (currentTime - lastSweep < SWEEP_INTERVAL) | ||||
|             return; | ||||
|  | ||||
|         // Record time of sweep | ||||
|         lastSweep = currentTime; | ||||
|  | ||||
|         // For each stored nonce | ||||
|         Iterator<Map.Entry<String, Long>> entries = nonces.entrySet().iterator(); | ||||
|         while (entries.hasNext()) { | ||||
|  | ||||
|             // Remove all entries which have expired | ||||
|             Map.Entry<String, Long> current = entries.next(); | ||||
|             if (current.getValue() <= System.currentTimeMillis()) | ||||
|                 entries.remove(); | ||||
|  | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Generates a cryptographically-secure nonce value. The nonce is intended | ||||
|      * to be used to prevent replay attacks. | ||||
|      * | ||||
|      * @param maxAge | ||||
|      *     The maximum amount of time that the generated nonce should remain | ||||
|      *     valid, in milliseconds. | ||||
|      * | ||||
|      * @return | ||||
|      *     A cryptographically-secure nonce value. | ||||
|      */ | ||||
|     public String generate(long maxAge) { | ||||
|  | ||||
|         // Sweep expired nonces if enough time has passed | ||||
|         sweepExpiredNonces(); | ||||
|  | ||||
|         // Generate and store nonce, along with expiration timestamp | ||||
|         String nonce = new BigInteger(130, random).toString(32); | ||||
|         nonces.put(nonce, System.currentTimeMillis() + maxAge); | ||||
|         return nonce; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns whether the give nonce value is valid. A nonce is valid if and | ||||
|      * only if it was generated by this instance of the NonceService. Testing | ||||
|      * nonce validity through this function immediately and permanently | ||||
|      * invalidates that nonce. | ||||
|      * | ||||
|      * @param nonce | ||||
|      *     The nonce value to test. | ||||
|      * | ||||
|      * @return | ||||
|      *     true if the provided nonce is valid, false otherwise. | ||||
|      */ | ||||
|     public boolean isValid(String nonce) { | ||||
|  | ||||
|         // Remove nonce, verifying whether it was present at all | ||||
|         Long expires = nonces.remove(nonce); | ||||
|         if (expires == null) | ||||
|             return false; | ||||
|  | ||||
|         // Nonce is only valid if it hasn't expired | ||||
|         return expires > System.currentTimeMillis(); | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -51,6 +51,12 @@ public class TokenValidationService { | ||||
|     @Inject | ||||
|     private ConfigurationService confService; | ||||
|  | ||||
|     /** | ||||
|      * Service for validating and generating unique nonce values. | ||||
|      */ | ||||
|     @Inject | ||||
|     private NonceService nonceService; | ||||
|  | ||||
|     /** | ||||
|      * Validates and parses the given ID token, returning the username contained | ||||
|      * therein, as defined by the username claim type given in | ||||
| @@ -91,6 +97,20 @@ public class TokenValidationService { | ||||
|             // Validate JWT | ||||
|             JwtClaims claims = jwtConsumer.processToClaims(token); | ||||
|  | ||||
|             // Verify a nonce is present | ||||
|             String nonce = claims.getStringClaimValue("nonce"); | ||||
|             if (nonce == null) { | ||||
|                 logger.info("Rejected OpenID token without nonce."); | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             // Verify that we actually generated the nonce, and that it has not | ||||
|             // already been used | ||||
|             if (!nonceService.isValid(nonce)) { | ||||
|                 logger.debug("Rejected OpenID token with invalid/old nonce."); | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             // Pull username from claims | ||||
|             String username = claims.getStringClaimValue(usernameClaim); | ||||
|             if (username != null) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user