mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +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 javax.servlet.http.HttpServletRequest;
|
||||||
import org.apache.guacamole.auth.openid.conf.ConfigurationService;
|
import org.apache.guacamole.auth.openid.conf.ConfigurationService;
|
||||||
import org.apache.guacamole.auth.openid.form.TokenField;
|
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.token.TokenValidationService;
|
||||||
import org.apache.guacamole.auth.openid.user.AuthenticatedUser;
|
import org.apache.guacamole.auth.openid.user.AuthenticatedUser;
|
||||||
import org.apache.guacamole.GuacamoleException;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
@@ -52,6 +53,12 @@ public class AuthenticationProviderService {
|
|||||||
@Inject
|
@Inject
|
||||||
private ConfigurationService confService;
|
private ConfigurationService confService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for validating and generating unique nonce values.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
private NonceService nonceService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service for validating received ID tokens.
|
* Service for validating received ID tokens.
|
||||||
*/
|
*/
|
||||||
@@ -112,7 +119,8 @@ public class AuthenticationProviderService {
|
|||||||
new TokenField(
|
new TokenField(
|
||||||
confService.getAuthorizationEndpoint(),
|
confService.getAuthorizationEndpoint(),
|
||||||
confService.getClientID(),
|
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 com.google.inject.AbstractModule;
|
||||||
import org.apache.guacamole.auth.openid.conf.ConfigurationService;
|
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.auth.openid.token.TokenValidationService;
|
||||||
import org.apache.guacamole.GuacamoleException;
|
import org.apache.guacamole.GuacamoleException;
|
||||||
import org.apache.guacamole.environment.Environment;
|
import org.apache.guacamole.environment.Environment;
|
||||||
@@ -74,6 +75,7 @@ public class OpenIDAuthenticationProviderModule extends AbstractModule {
|
|||||||
|
|
||||||
// Bind openid-specific services
|
// Bind openid-specific services
|
||||||
bind(ConfigurationService.class);
|
bind(ConfigurationService.class);
|
||||||
|
bind(NonceService.class);
|
||||||
bind(TokenValidationService.class);
|
bind(TokenValidationService.class);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -20,15 +20,12 @@
|
|||||||
package org.apache.guacamole.auth.openid.form;
|
package org.apache.guacamole.auth.openid.form;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import org.apache.guacamole.form.Field;
|
import org.apache.guacamole.form.Field;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Field definition which represents the token returned by an OpenID service.
|
* Field definition which represents the token returned by an OpenID Connect
|
||||||
* Within the user interface, this will be rendered as an appropriate "Log in
|
* service.
|
||||||
* with ..." button which links to the OpenID service.
|
|
||||||
*/
|
*/
|
||||||
public class TokenField extends Field {
|
public class TokenField extends Field {
|
||||||
|
|
||||||
@@ -44,29 +41,12 @@ public class TokenField extends Field {
|
|||||||
private final String authorizationURI;
|
private final String authorizationURI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cryptographically-secure random number generator for generating the
|
* Creates a new field which requests authentication via OpenID connect.
|
||||||
* required nonce.
|
* Successful authentication at the OpenID Connect service will result in
|
||||||
*/
|
* the client being redirected to the specified redirect URI. The OpenID
|
||||||
private static final SecureRandom random = new SecureRandom();
|
* 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.
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* @param authorizationEndpoint
|
* @param authorizationEndpoint
|
||||||
* The full URL of the endpoint accepting OpenID authentication
|
* The full URL of the endpoint accepting OpenID authentication
|
||||||
@@ -80,9 +60,13 @@ public class TokenField extends Field {
|
|||||||
* @param redirectURI
|
* @param redirectURI
|
||||||
* The URI that the OpenID service should redirect to upon successful
|
* The URI that the OpenID service should redirect to upon successful
|
||||||
* authentication.
|
* 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,
|
public TokenField(String authorizationEndpoint, String clientID,
|
||||||
String redirectURI) {
|
String redirectURI, String nonce) {
|
||||||
|
|
||||||
// Init base field properties
|
// Init base field properties
|
||||||
super(PARAMETER_NAME, "GUAC_OPENID_TOKEN");
|
super(PARAMETER_NAME, "GUAC_OPENID_TOKEN");
|
||||||
@@ -94,7 +78,7 @@ public class TokenField extends Field {
|
|||||||
+ "&response_type=id_token"
|
+ "&response_type=id_token"
|
||||||
+ "&client_id=" + URLEncoder.encode(clientID, "UTF-8")
|
+ "&client_id=" + URLEncoder.encode(clientID, "UTF-8")
|
||||||
+ "&redirect_uri=" + URLEncoder.encode(redirectURI, "UTF-8")
|
+ "&redirect_uri=" + URLEncoder.encode(redirectURI, "UTF-8")
|
||||||
+ "&nonce=" + generateNonce();
|
+ "&nonce=" + nonce;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Java is required to provide UTF-8 support
|
// 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
|
@Inject
|
||||||
private ConfigurationService confService;
|
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
|
* Validates and parses the given ID token, returning the username contained
|
||||||
* therein, as defined by the username claim type given in
|
* therein, as defined by the username claim type given in
|
||||||
@@ -91,6 +97,20 @@ public class TokenValidationService {
|
|||||||
// Validate JWT
|
// Validate JWT
|
||||||
JwtClaims claims = jwtConsumer.processToClaims(token);
|
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
|
// Pull username from claims
|
||||||
String username = claims.getStringClaimValue(usernameClaim);
|
String username = claims.getStringClaimValue(usernameClaim);
|
||||||
if (username != null)
|
if (username != null)
|
||||||
|
Reference in New Issue
Block a user