GUACAMOLE-839: Generate case-insensitive nonce values that can safely be used in domains.

This commit is contained in:
Michael Jumper
2023-01-26 10:24:08 -08:00
parent f2c7d746ea
commit 841190df5a
2 changed files with 47 additions and 9 deletions

View File

@@ -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);
}
}

View File

@@ -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;