71 lines
2.5 KiB
Java
71 lines
2.5 KiB
Java
package com.spotifymc.auth;
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.security.MessageDigest;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.SecureRandom;
|
|
import java.util.Base64;
|
|
|
|
/**
|
|
* Utility class for generating OAuth 2.0 PKCE (Proof Key for Code Exchange)
|
|
* verifiers and SHA-256 code challenges per RFC 7636.
|
|
*/
|
|
public final class PkceUtils {
|
|
|
|
private static final SecureRandom RANDOM = new SecureRandom();
|
|
private static final String UNRESERVED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
|
|
private PkceUtils() {
|
|
}
|
|
|
|
/**
|
|
* Generates a cryptographically secure random code verifier string.
|
|
*
|
|
* @param length The length of the verifier (between 43 and 128 characters).
|
|
* @return The randomly generated code verifier.
|
|
*/
|
|
public static String generateCodeVerifier(int length) {
|
|
if (length < 43 || length > 128) {
|
|
throw new IllegalArgumentException("Verifier length must be between 43 and 128 characters");
|
|
}
|
|
StringBuilder sb = new StringBuilder(length);
|
|
for (int i = 0; i < length; i++) {
|
|
int index = RANDOM.nextInt(UNRESERVED_CHARS.length());
|
|
sb.append(UNRESERVED_CHARS.charAt(index));
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
/**
|
|
* Generates a 64-character code verifier (recommended default length).
|
|
*/
|
|
public static String generateCodeVerifier() {
|
|
return generateCodeVerifier(64);
|
|
}
|
|
|
|
/**
|
|
* Generates a code challenge from the given verifier using SHA-256 and Base64URL encoding (without padding).
|
|
*
|
|
* @param codeVerifier The PKCE code verifier.
|
|
* @return Base64URL-encoded SHA-256 code challenge.
|
|
*/
|
|
public static String generateCodeChallenge(String codeVerifier) {
|
|
try {
|
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
|
byte[] hash = digest.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
|
|
return Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new RuntimeException("SHA-256 message digest algorithm not available", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates a random state string to prevent Cross-Site Request Forgery (CSRF).
|
|
*/
|
|
public static String generateState() {
|
|
byte[] bytes = new byte[16];
|
|
RANDOM.nextBytes(bytes);
|
|
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
|
|
}
|
|
}
|