diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/IdentifierGenerator.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/IdentifierGenerator.java index 799b31b13..82538c6a9 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/IdentifierGenerator.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/IdentifierGenerator.java @@ -21,6 +21,7 @@ package org.apache.guacamole.auth.sso; import com.google.common.io.BaseEncoding; import com.google.inject.Singleton; +import java.math.BigInteger; import java.security.SecureRandom; /** @@ -40,7 +41,8 @@ public class IdentifierGenerator { /** * Generates a unique and unpredictable identifier. Each identifier is at * least 256-bit and produced using a cryptographically-secure random - * number generator. + * number generator. The identifier may contain characters that differ only + * in case. * * @return * A unique and unpredictable identifier with at least 256 bits of @@ -53,7 +55,8 @@ public class IdentifierGenerator { /** * Generates a unique and unpredictable identifier having at least the * given number of bits of entropy. The resulting identifier may have more - * than the number of bits required. + * than the number of bits required. The identifier may contain characters + * that differ only in case. * * @param minBits * The number of bits of entropy that the identifier should contain. @@ -63,9 +66,41 @@ public class IdentifierGenerator { * of bits of entropy. */ public String generateIdentifier(int minBits) { - byte[] bytes = new byte[(minBits + 23) / 24 * 3]; // Round up to nearest multiple of 3 bytes, as base64 encodes blocks of 3 bytes at a time - secureRandom.nextBytes(bytes); - return BaseEncoding.base64().encode(bytes); + return generateIdentifier(minBits, true); + } + + /** + * Generates a unique and unpredictable identifier having at least the + * given number of bits of entropy. The resulting identifier may have more + * than the number of bits required. The identifier may contain characters + * that differ only in case. + * + * @param minBits + * The number of bits of entropy that the identifier should contain. + * + * @param caseSensitive + * Whether identifiers are permitted to contain characters that vary + * by case. If false, all characters that may vary by case will be + * lowercase, and the generated identifier will be longer. + * + * @return + * A unique and unpredictable identifier with at least the given number + * of bits of entropy. + */ + public String generateIdentifier(int minBits, boolean caseSensitive) { + + // Generate a base64 identifier if we're allowed to vary by case + if (caseSensitive) { + int minBytes = (minBits + 23) / 24 * 3; // Round up to nearest multiple of 3 bytes, as base64 encodes blocks of 3 bytes at a time + byte[] bytes = new byte[minBytes]; + secureRandom.nextBytes(bytes); + return BaseEncoding.base64().encode(bytes); + } + + // Generate base32 identifiers if we cannot vary by case + minBits = (minBits + 4) / 5 * 5; // Round up to nearest multiple of 5 bits, as base32 encodes 5 bits at a time + return new BigInteger(minBits, secureRandom).toString(32); + } } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/NonceService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/NonceService.java index 88fff881b..a06340653 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/NonceService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/NonceService.java @@ -22,11 +22,13 @@ package org.apache.guacamole.auth.sso; import com.google.inject.Inject; import com.google.inject.Singleton; import java.util.Iterator; +import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Service for generating and validating single-use random tokens (nonces). + * Each generated nonce is at least 128 bits and case-insensitive. */ @Singleton public class NonceService { @@ -98,7 +100,8 @@ public class NonceService { * valid, in milliseconds. * * @return - * A cryptographically-secure nonce value. + * A cryptographically-secure nonce value. Generated nonces are at + * least 128-bit and are case-insensitive. */ public String generate(long maxAge) { @@ -106,7 +109,7 @@ public class NonceService { sweepExpiredNonces(); // Generate and store nonce, along with expiration timestamp - String nonce = idGenerator.generateIdentifier(NONCE_BITS); + String nonce = idGenerator.generateIdentifier(NONCE_BITS, false); nonces.put(nonce, System.currentTimeMillis() + maxAge); return nonce; @@ -119,7 +122,7 @@ public class NonceService { * invalidates that nonce. * * @param nonce - * The nonce value to test. + * The nonce value to test. Comparisons are case-insensitive. * * @return * true if the provided nonce is valid, false otherwise. @@ -127,7 +130,7 @@ public class NonceService { public boolean isValid(String nonce) { // Remove nonce, verifying whether it was present at all - Long expires = nonces.remove(nonce); + Long expires = nonces.remove(nonce.toLowerCase(Locale.US)); if (expires == null) return false;