From 3815d3baf1c68bd69a8b54b9dd9a1e86d8eb9e34 Mon Sep 17 00:00:00 2001 From: petrberan Date: Tue, 17 Jan 2023 19:40:51 +0100 Subject: [PATCH] [ELY-489] Add JavaDoc for the 'org.wildfly.security.mechanism' package and sub packages --- .../AuthenticationMechanismException.java | 8 ++ .../security/mechanism/MechanismUtil.java | 2 + .../mechanism/ScramServerErrorCode.java | 18 ++++ .../mechanism/ScramServerException.java | 5 ++ .../mechanism/_private/MechanismUtil.java | 2 + .../mechanism/digest/DigestQuote.java | 18 +++- .../security/mechanism/digest/DigestUtil.java | 51 +++++++++-- .../digest/PasswordDigestObtainer.java | 50 ++++++++++- .../gssapi/GSSCredentialSecurityFactory.java | 71 ++++++++++++++- ...ernamePasswordAuthenticationMechanism.java | 32 ++++++- .../mechanism/oauth2/OAuth2Client.java | 24 ++++- .../oauth2/OAuth2InitialClientMessage.java | 29 ++++++ .../mechanism/oauth2/OAuth2Server.java | 37 +++++++- .../security/mechanism/scram/ScramClient.java | 81 +++++++++++++++++ .../scram/ScramFinalClientMessage.java | 57 ++++++++++++ .../scram/ScramFinalServerMessage.java | 28 ++++++ .../scram/ScramInitialClientMessage.java | 90 +++++++++++++++++++ .../scram/ScramInitialServerMessage.java | 56 ++++++++++++ .../scram/ScramInitialServerResult.java | 18 ++++ .../mechanism/scram/ScramMechanism.java | 57 +++++++++++- .../security/mechanism/scram/ScramServer.java | 70 +++++++++++++++ .../security/mechanism/scram/ScramUtil.java | 23 +++++ 22 files changed, 807 insertions(+), 20 deletions(-) diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/AuthenticationMechanismException.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/AuthenticationMechanismException.java index 525fd9fdba3..e5a82cb2d03 100644 --- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/AuthenticationMechanismException.java +++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/AuthenticationMechanismException.java @@ -102,6 +102,14 @@ public static AuthenticationMechanismException fromException(final Exception sou return copyContents(source, new AuthenticationMechanismException(source.getMessage(), source.getCause())); } + /** + * Copies the stack trace and suppressed exceptions from a source exception to a specified throwable. + * + * @param source the source exception from which the stack trace and suppressed exceptions should be copied. + * @param throwable the throwable to which the contents should be copied. + * @param the type of throwable to which the contents should be copied. + * @return the throwable that was passed in as a parameter, with the contents copied from the source exception. + */ private static T copyContents(final Exception source, final T throwable) { throwable.setStackTrace(source.getStackTrace()); final Throwable[] suppressed = source.getSuppressed(); diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/MechanismUtil.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/MechanismUtil.java index 622a53c1f1b..c845093aeef 100644 --- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/MechanismUtil.java +++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/MechanismUtil.java @@ -57,6 +57,7 @@ private MechanismUtil() {} * @param providers the security providers to use with the {@link PasswordFactory} * @param the password type * @return the password + * @throws AuthenticationMechanismException if there is an error retrieving the password */ @Deprecated public static S getPasswordCredential(String userName, CallbackHandler callbackHandler, Class passwordType, String passwordAlgorithm, AlgorithmParameterSpec matchParameters, AlgorithmParameterSpec generateParameters, Supplier providers) throws AuthenticationMechanismException { @@ -78,6 +79,7 @@ public static S getPasswordCredential(String userName, Call * @param the password type * @param log mechanism specific logger * @return the password + * @throws AuthenticationMechanismException if there is an error retrieving the password */ @Deprecated public static S getPasswordCredential(String userName, CallbackHandler callbackHandler, Class passwordType, String passwordAlgorithm, AlgorithmParameterSpec matchParameters, AlgorithmParameterSpec generateParameters, Supplier providers, ElytronMessages log) throws AuthenticationMechanismException { diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerErrorCode.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerErrorCode.java index f1659f2dcef..111a4c2a340 100644 --- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerErrorCode.java +++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerErrorCode.java @@ -45,6 +45,9 @@ public enum ScramServerErrorCode { private final String text; private final byte[] messageBytes; + /** + * Creates an error code instance with a String representation and a byte array for error message. + */ ScramServerErrorCode() { text = name().replace('_', '-').toLowerCase(Locale.US); final int length = text.length(); @@ -54,14 +57,29 @@ public enum ScramServerErrorCode { messageBytes = msg; } + /** + * Returns the String representation of the error code. + * + * @return String representation of the error code. + */ public String getText() { return text; } + /** + * Returns the copy of the byte array representing the error message. + * + * @return copy of the byte array representing the error message. + */ public byte[] getMessageBytes() { return messageBytes.clone(); } + /** + * Returns the byte array representing the error message. + * + * @return the byte array representing the error message. + */ byte[] getRawMessageBytes() { return messageBytes; } diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerException.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerException.java index a0594f69c42..7e7057ad1d2 100644 --- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerException.java +++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/ScramServerException.java @@ -83,6 +83,11 @@ public String getMessage() { return super.getMessage() + ": " + error.getText(); } + /** + * Returns the error code of the exception. + * + * @return the error code of the exception. + */ public ScramServerErrorCode getError() { return error; } diff --git a/mechanism/base/src/main/java/org/wildfly/security/mechanism/_private/MechanismUtil.java b/mechanism/base/src/main/java/org/wildfly/security/mechanism/_private/MechanismUtil.java index 74e7b99e85c..32192d27f85 100644 --- a/mechanism/base/src/main/java/org/wildfly/security/mechanism/_private/MechanismUtil.java +++ b/mechanism/base/src/main/java/org/wildfly/security/mechanism/_private/MechanismUtil.java @@ -67,6 +67,7 @@ private MechanismUtil() {} * @param the password type * @param log mechanism specific logger * @return the password + * @throws AuthenticationMechanismException if there is an error retrieving the password */ public static S getPasswordCredential(String userName, CallbackHandler callbackHandler, Class passwordType, String passwordAlgorithm, AlgorithmParameterSpec matchParameters, AlgorithmParameterSpec generateParameters, Supplier providers, ElytronMessages log) throws AuthenticationMechanismException { Assert.checkNotNullParam("userName", userName); @@ -168,6 +169,7 @@ public static void handleCallbacks(ElytronMessages log, CallbackHandler callback * @param scope the HTTP scope to store computed value (must not be {@code null}) * @param key the key to retrieve (must not be {@code null}) * @param mappingFunction the function to apply to acquire the value (must not be {@code null}) + * @param the type of returned value * @return the stored or new value (not {@code null}) */ public static R computeIfAbsent(HttpScope scope, String key, Function mappingFunction) { diff --git a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestQuote.java b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestQuote.java index 0f6cc400572..da81adc1fb7 100644 --- a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestQuote.java +++ b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestQuote.java @@ -21,7 +21,7 @@ import org.wildfly.common.bytes.ByteStringBuilder; /** - * Utility class used to convert string to quoted strings + * Utility class used to convert string to quoted strings. * * @author Peter Skopek * @@ -33,6 +33,12 @@ public class DigestQuote { private DigestQuote() { } + /** + * Checks if a given character needs to be quoted. + * + * @param ch the character to check. + * @return {@code true} if the character needs to be quoted, {@code false} otherwise. + */ private static boolean quoteNeeded(char ch) { return ch == '"' || // escape char @@ -46,8 +52,8 @@ private static boolean quoteNeeded(char ch) { /** * Creates new String quoted by SASL rules. * - * @param inputStr String to be quoted - * @return + * @param inputStr String to be quoted. + * @return new String with quoted characters. */ public static String quote(String inputStr) { int len = inputStr.length(); @@ -64,6 +70,12 @@ public static String quote(String inputStr) { return sb.toString(); } + /** + * Creates new Array quoted by SASL rules. + * + * @param input Byte array to be quoted. + * @return new byte array with quoted bytes. + */ public static byte[] quote(byte[] input) { ByteStringBuilder bsb = new ByteStringBuilder(); for (int i = 0; i < input.length; i++) { diff --git a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestUtil.java b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestUtil.java index fc4e4399638..c277abd51d1 100644 --- a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestUtil.java +++ b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/DigestUtil.java @@ -30,8 +30,6 @@ import java.util.HashMap; import java.util.function.Supplier; -import javax.security.sasl.SaslException; - import org.wildfly.common.bytes.ByteStringBuilder; import org.wildfly.security.mechanism._private.ElytronMessages; import org.wildfly.security.mechanism.AuthenticationMechanismException; @@ -52,9 +50,12 @@ public class DigestUtil { /** * Client side method to parse challenge sent by server. * - * @param challenge - * @return - * @throws AuthenticationMechanismException + * @param challenge the byte array representing the authentication challenge to be parsed. + * @param charset the charset to decide which whitespace separator is used. + * @param multiRealm {@code true} if there are multiple realms in the challenge, {@code false} otherwise + * @param log the logger to use. + * @return A new HashMap representing response for the parsed challenge + * @throws AuthenticationMechanismException if there is an error parsing the challenge */ public static HashMap parseResponse(byte [] challenge, Charset charset, boolean multiRealm, ElytronMessages log) throws AuthenticationMechanismException { @@ -170,6 +171,15 @@ else if (expectSeparator) { return response; } + /** + * Adds a key-value pair to a HashMap representing a parsed challenge. + * + * @param response the HashMap to add the key-value pair to. + * @param keyBuilder the StringBuilder containing the key. + * @param valueBuilder the ByteStringBuilder containing the value. + * @param realmNumber the current number of realms in the parsed challenge. + * @return the updated number of realms in the parsed challenge. + */ private static int addToParsedChallenge(HashMap response, StringBuilder keyBuilder, ByteStringBuilder valueBuilder, int realmNumber) { String k = keyBuilder.toString(); byte[] v = valueBuilder.toArray(); @@ -183,6 +193,13 @@ private static int addToParsedChallenge(HashMap response, String return realmNumber; } + /** + * Finds the next non-whitespace character in a byte array. + * + * @param buffer the byte array to search in. + * @param startPoint the starting point in the buffer to begin the search. + * @return the index of the next non-whitespace character. + */ private static int skipWhiteSpace(byte[] buffer, int startPoint) { int i = startPoint; while (i < buffer.length && isWhiteSpace(buffer[i])) { @@ -191,6 +208,12 @@ private static int skipWhiteSpace(byte[] buffer, int startPoint) { return i; } + /** + * Checks if a given byte is a whitespace character. + * + * @param b the byte to check. + * @return {@code true} if the byte is a whitespace character, {@code false} otherwise. + */ private static boolean isWhiteSpace(byte b) { if (b == 13) // CR return true; @@ -204,6 +227,15 @@ else if (b == 32) // SPACE return false; } + /** + * Digests the concatenated username, realm and password. + * + * @param messageDigest the message digest algorithm to use when computing the digest. + * @param username the username to use when concatenating. + * @param realm the realm to use when concatenating. + * @param password the password in the form of a char array to use when concatenating. + * @return byte array of the digested password. + */ public static byte[] userRealmPasswordDigest(MessageDigest messageDigest, String username, String realm, char[] password) { CharsetEncoder latin1Encoder = StandardCharsets.ISO_8859_1.newEncoder(); latin1Encoder.reset(); @@ -232,10 +264,13 @@ public static byte[] userRealmPasswordDigest(MessageDigest messageDigest, String } /** - * Get array of password chars from TwoWayPassword + * Get array of password chars from TwoWayPassword. * - * @return - * @throws SaslException + * @param password the TwoWayPassword that needs to be processed. + * @param providers the supplier for the providers to be used for processing. + * @param log the logger to use. + * @throws AuthenticationMechanismException if there is an error retrieving the encoded password. + * @return encoded password in the form of a char array. */ public static char[] getTwoWayPasswordChars(TwoWayPassword password, Supplier providers, ElytronMessages log) throws AuthenticationMechanismException { if (password == null) { diff --git a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/PasswordDigestObtainer.java b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/PasswordDigestObtainer.java index b876af7ad2c..7af037bd6ba 100644 --- a/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/PasswordDigestObtainer.java +++ b/mechanism/digest/src/main/java/org/wildfly/security/mechanism/digest/PasswordDigestObtainer.java @@ -45,7 +45,7 @@ import static org.wildfly.security.mechanism.digest.DigestUtil.userRealmPasswordDigest; /** - * Utility class used to obtain username+realm+password using SASL/HTTP mechanism callbacks + * Utility class used to obtain username+realm+password using SASL/HTTP mechanism callbacks. * * @author Jan Kalina */ @@ -67,6 +67,20 @@ public class PasswordDigestObtainer { private RealmCallback realmCallback; private NameCallback nameCallback; + /** + * Constructs a new {@code PasswordDigestObtainer} instance. + * + * @param callbackHandler the callbackHandler to handle the callbacks required to obtain the username and password. + * @param defaultUsername the default username to use if a callback is not provided. + * @param defaultRealm the default realm to use if a callback is not provided. + * @param log the logger to use. + * @param credentialAlgorithm the name of the algorithm for obtaining the credential. + * @param messageDigest the {@link MessageDigest} used for digesting the password. + * @param passwordFactoryProviders the supplier of the providers to use when creating a {@code PasswordFactory} instance. + * @param realms the realms to check for a user and password. + * @param readOnlyRealmUsername {@code true} if the username passed in the callback can be modified, {@code false} otherwise. + * @param skipRealmCallbacks {@code true} if realm callbacks should be skipped, {@code false} otherwise. + */ public PasswordDigestObtainer(CallbackHandler callbackHandler, String defaultUsername, String defaultRealm, ElytronMessages log, String credentialAlgorithm, MessageDigest messageDigest, Supplier passwordFactoryProviders, String[] realms, @@ -83,14 +97,30 @@ public PasswordDigestObtainer(CallbackHandler callbackHandler, String defaultUse this.skipRealmCallbacks = skipRealmCallbacks; } + /** + * Returns the username obtained from callback or the default one. + * + * @return the username obtained from callback or the default one. + */ public String getUsername() { return username; } + /** + * Returns the realm obtained from callback or the default one. + * + * @return the realm obtained from callback or the default one. + */ public String getRealm() { return realm; } + /** + * Handles callbacks for user and password information. + * + * @return the salted password. + * @throws AuthenticationMechanismException if the callback handler does not support credential acquisition. + */ public byte[] handleUserRealmPasswordCallbacks() throws AuthenticationMechanismException { realmChoiceCallBack = skipRealmCallbacks || realms == null || realms.length <= 1 ? null : @@ -115,6 +145,12 @@ public byte[] handleUserRealmPasswordCallbacks() throws AuthenticationMechanismE throw log.mechCallbackHandlerDoesNotSupportCredentialAcquisition(null); } + /** + * Obtains the pre-digested salted password for the {@code username} in the {@code realm}. + * + * @return the pre-digested salted password if obtained, {@code null} otherwise. + * @throws AuthenticationMechanismException if an exception occurs while handling the callbacks. + */ private byte[] getPredigestedSaltedPassword() throws AuthenticationMechanismException { if (realmChoiceCallBack != null) { try { @@ -180,6 +216,12 @@ private byte[] getPredigestedSaltedPassword() throws AuthenticationMechanismExce return null; } + /** + * Obtains the salted password from a two-way callback. + * + * @return the byte array of the salted password if obtained, {@code null} otherwise. + * @throws AuthenticationMechanismException if an error occurs during the process of handling callbacks or obtaining the password. + */ private byte[] getSaltedPasswordFromTwoWay() throws AuthenticationMechanismException { if (realmChoiceCallBack != null) { try { @@ -253,6 +295,12 @@ private byte[] getSaltedPasswordFromTwoWay() throws AuthenticationMechanismExcep return null; } + /** + * Obtains the salted password from a password callback. + * + * @return the byte array of the salted password. + * @throws AuthenticationMechanismException if an error occurs during the process of handling callbacks or obtaining the password. + */ private byte[] getSaltedPasswordFromPasswordCallback() throws AuthenticationMechanismException { PasswordCallback passwordCallback = new PasswordCallback("User password: ", false); diff --git a/mechanism/gssapi/src/main/java/org/wildfly/security/mechanism/gssapi/GSSCredentialSecurityFactory.java b/mechanism/gssapi/src/main/java/org/wildfly/security/mechanism/gssapi/GSSCredentialSecurityFactory.java index 03566163be3..2322b9ede25 100644 --- a/mechanism/gssapi/src/main/java/org/wildfly/security/mechanism/gssapi/GSSCredentialSecurityFactory.java +++ b/mechanism/gssapi/src/main/java/org/wildfly/security/mechanism/gssapi/GSSCredentialSecurityFactory.java @@ -87,12 +87,25 @@ public final class GSSCredentialSecurityFactory implements SecurityFactory credentialOperator; + /** + * Constructs a new {@code GSSCredentialSecurityFactory} instance. + * + * @param minimumRemainingLifetime the minimum remaining lifetime for a {@link GSSCredential} in seconds. + * @param rawSupplier the supplier of raw credentials. + */ GSSCredentialSecurityFactory(final int minimumRemainingLifetime, final ExceptionSupplier rawSupplier) { this.minimumRemainingLifetime = minimumRemainingLifetime; this.rawSupplier = rawSupplier; credentialOperator = this::update; } + /** + * Updates the {@link GSSKerberosCredential}. If the original is not valid, it gets a new {@code GSSKerberosCredential} + * from the {@code rawSupplier}, otherwise returns the original. + * + * @param original the original {@code GSSKerberosCredential} to be updated. + * @return the original if still valid, new {@code GSSKerberosCredential} otherwise. + */ private GSSKerberosCredential update(GSSKerberosCredential original) { GSSKerberosCredential result = null; try { @@ -116,6 +129,13 @@ private GSSKerberosCredential update(GSSKerberosCredential original) { return result; } + /** + * Checks if the GSSCredential is still valid. + * + * @param gssCredential the GSSCredential to check. + * @return {@code true} if the GSSCredential is valid, {@code false} otherwise. + * @throws GeneralSecurityException if an error occurs during the validation. + */ private boolean testIsValid(GSSCredential gssCredential) throws GeneralSecurityException { checkNotNullParam("gssCredential", gssCredential); boolean stillValid; @@ -131,6 +151,12 @@ private boolean testIsValid(GSSCredential gssCredential) throws GeneralSecurityE return stillValid; } + /** + * Checks if the Kerberos ticket is still valid. If not, attempts to refresh it. + * + * @param ticket the Kerberos ticket to be checked. + * @return {@code true} if the ticket is valid, {@code false} otherwise. + */ private boolean testIsValid(KerberosTicket ticket) { if (ticket == null) { log.trace("No cached KerberosTicket"); @@ -231,9 +257,9 @@ public Builder setIsServer(final boolean isServer) { } /** - * Set if the KerberosTicket should also be obtained and associated with the Credential/ + * Set if the KerberosTicket should also be obtained and associated with the Credential. * - * @param obtainKerberosTicket if the KerberosTicket should also be obtained and associated with the Credential/ + * @param obtainKerberosTicket if the KerberosTicket should also be obtained and associated with the Credential. * @return {@code this} to allow chaining. */ public Builder setObtainKerberosTicket(final boolean obtainKerberosTicket) { @@ -297,7 +323,7 @@ public Builder setPrincipal(final String principal) { } /** - * Set if debug logging should be enabled for the JAAS authentication portion of obtaining the {@link GSSCredential} + * Set if debug logging should be enabled for the JAAS authentication portion of obtaining the {@link GSSCredential}. * * @param debug if debug logging should be enabled for the JAAS authentication portion of obtaining the {@link GSSCredential} * @return {@code this} to allow chaining. @@ -336,7 +362,7 @@ public Builder setCheckKeyTab(final boolean value) { } /** - * Set other configuration options for {@code Krb5LoginModule} + * Set other configuration options for {@code Krb5LoginModule}. * * @param options the configuration options which will be appended to options passed into {@code Krb5LoginModule} * @return {@code this} to allow chaining. @@ -380,6 +406,14 @@ public SecurityFactory build() throws IOException { return new GSSCredentialSecurityFactory(minimumRemainingLifetime > 0 ? minimumRemainingLifetime : 0, () -> createGSSCredential(configuration)); } + /** + * Creates an instance of the {@link GSSKerberosCredential} class, which represents a Kerberos credential + * that can be used for authentication using the GSS-API. + * + * @param configuration the configuration used for creating the {@link LoginContext}. + * @return the {@code GSSKerberosCredential} - the GSSCredential object and Kerberos Ticket (if {@code obtainKerberosTicket} is {@code true}. + * @throws GeneralSecurityException if an error occurs during the creation of {@code GSSKerberosCredential}. + */ private GSSKerberosCredential createGSSCredential(Configuration configuration) throws GeneralSecurityException { if (failCache != 0 && System.currentTimeMillis() - lastFailTime < failCache * 1000) { throw log.initialLoginSkipped(failCache); @@ -445,10 +479,24 @@ private GSSKerberosCredential createGSSCredential(Configuration configuration) t } } + /** + * Performs a privileged action. If a security manager is set, the action will be executed via + * {@link AccessController#doPrivileged(PrivilegedAction)}. If no security manager is set, + * the action will be executed directly. + * + * @param action the action do be executed. + * @param the type of the action. + * @return the result of the executed action. + */ private static T doPrivileged(final PrivilegedAction action) { return System.getSecurityManager() != null ? AccessController.doPrivileged(action) : action.run(); } + /** + * Checks if the keytab exists and if it contains any keys for the specified principal. + * + * @throws IOException if the keytab does not exist or if it does not contain any keys for the specified principal. + */ private void checkKeyTab() throws IOException { KeyTab kt = KeyTab.getInstance(keyTab); if (!kt.exists()) { @@ -459,6 +507,12 @@ private void checkKeyTab() throws IOException { } } + /** + * Creates a {@link Configuration} that is used to initiate a {@link LoginContext}. + * + * @return a {@code Configuration} for initiating a {@code LoginContext}. + * @throws IOException if the keyTab does not exist or there are no keys for the principal in the keyTab. + */ private Configuration createConfiguration() throws IOException { Map options = new HashMap<>(); if (debug) { @@ -491,6 +545,9 @@ public AppConfigurationEntry[] getAppConfigurationEntry(String name) { }; } + /** + * Asserts that the builder has not yet been built. + */ private void assertNotBuilt() { if (built) { throw log.builderAlreadyBuilt(); @@ -499,6 +556,12 @@ private void assertNotBuilt() { } + /** + * Wraps the given {@link GSSCredential} and prevents it from being disposed. + * + * @param credential the {@code GSSCredential} to be wrapped. + * @return the wrapped {@code GSSCredential}. + */ private static GSSCredential wrapCredential(final GSSCredential credential) { return new GSSCredential() { diff --git a/mechanism/http/src/main/java/org/wildfly/security/mechanism/http/UsernamePasswordAuthenticationMechanism.java b/mechanism/http/src/main/java/org/wildfly/security/mechanism/http/UsernamePasswordAuthenticationMechanism.java index 328ef1cd200..413848192c5 100644 --- a/mechanism/http/src/main/java/org/wildfly/security/mechanism/http/UsernamePasswordAuthenticationMechanism.java +++ b/mechanism/http/src/main/java/org/wildfly/security/mechanism/http/UsernamePasswordAuthenticationMechanism.java @@ -49,13 +49,24 @@ public abstract class UsernamePasswordAuthenticationMechanism implements HttpSer protected final CallbackHandler callbackHandler; /** - * @param callbackHandler + * Constructs a new {@code UsernamePasswordAuthenticationMechanism} instance. + * + * @param callbackHandler the CallbackHandler used for authentication. */ protected UsernamePasswordAuthenticationMechanism(CallbackHandler callbackHandler) { super(); this.callbackHandler = callbackHandler; } + /** + * Authenticates the user for provided realm using their username and password. + * + * @param realmName the realm for which the user is authenticating. + * @param username the username of the authenticating user. + * @param password the password of the authenticating user. + * @return {@code true} if the user is authenticated for the realm, {@code false} otherwise. + * @throws HttpAuthenticationException if there was an IOException caused by the CallbackHandler. + */ protected boolean authenticate(String realmName, String username, char[] password) throws HttpAuthenticationException { RealmCallback realmCallback = realmName != null ? new RealmCallback("User realm", realmName) : null; NameCallback nameCallback = new NameCallback("Remote Authentication Name", username); @@ -94,6 +105,13 @@ protected boolean authenticate(String realmName, String username, char[] passwor } } + /** + * Checks if the user is authorized. + * + * @param username the username to authorize. + * @return {@code true} if the user is authorized, {@code false} otherwise. + * @throws HttpAuthenticationException if there was an IOException caused by the CallbackHandler. + */ protected boolean authorize(String username) throws HttpAuthenticationException { httpUserPass.debugf("Username authorization. Username: [%s].", username); @@ -111,10 +129,22 @@ protected boolean authorize(String username) throws HttpAuthenticationException } } + /** + * Sends the information to the callbackHandler that the authorization succeeded. + * + * @throws IOException if an input or output error occurs. + * @throws UnsupportedCallbackException if the implementation of callbackHandler does not support the specified Callback type. + */ protected void succeed() throws IOException, UnsupportedCallbackException { callbackHandler.handle(new Callback[] { AuthenticationCompleteCallback.SUCCEEDED }); } + /** + * Sends the information to the callbackHandler that the authorization failed. + * + * @throws IOException if an input or output error occurs. + * @throws UnsupportedCallbackException if the implementation of callbackHandler does not support the specified Callback type. + */ protected void fail() throws IOException, UnsupportedCallbackException { callbackHandler.handle(new Callback[] { AuthenticationCompleteCallback.FAILED }); } diff --git a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Client.java b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Client.java index 315b9c1ee8e..ec78a73eeaa 100644 --- a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Client.java +++ b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Client.java @@ -33,6 +33,8 @@ import static org.wildfly.common.Assert.assertTrue; /** + * Implementation of the client side of the OAuth2 SASL mechanism. + * * @author Pedro Igor */ public class OAuth2Client { @@ -43,12 +45,26 @@ public class OAuth2Client { private final String authorizationId; private ElytronMessages log; + /** + * Constructs a new {@code OAuth2Client} instance. + * + * @param authorizationId the ID of the user to be authorized. + * @param callbackHandler the callback handler for verifying the Bearer token. + * @param log the logger to use. + */ public OAuth2Client(String authorizationId, CallbackHandler callbackHandler, ElytronMessages log) { this.authorizationId = authorizationId; this.callbackHandler = callbackHandler; this.log = log; } + /** + * Gets the initial response message from the client that will be sent to the server. + * It retrieves the Bearer token from a callback and constructs an encoded message that includes the token. + * + * @return encoded message that includes the Bearer token. + * @throws AuthenticationMechanismException if an error occurs during the callback or the token is {@code null}. + */ public OAuth2InitialClientMessage getInitialResponse() throws AuthenticationMechanismException { final CredentialCallback credentialCallback = new CredentialCallback(BearerTokenCredential.class); @@ -80,13 +96,19 @@ public OAuth2InitialClientMessage getInitialResponse() throws AuthenticationMech return new OAuth2InitialClientMessage(null, null, encoded.toArray()); } + /** + * Handles the server's response to the initial client message. + * + * @param serverMessage the byte array containing the server's response. + * @return {@code null} if the response was successful, aborting the authentication otherwise. + */ public byte[] handleServerResponse(byte[] serverMessage) { // got a successful response if (serverMessage.length == 0) { return null; } - // otherwise, server responded with a error message + // otherwise, server responded with an error message try { String errorMessage = ByteIterator.ofBytes(serverMessage).asUtf8String().base64Decode().asUtf8String().drainToString(); log.debugf("Got error message from server [%s].", errorMessage); diff --git a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2InitialClientMessage.java b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2InitialClientMessage.java index f5b6d6014a4..c244b043213 100644 --- a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2InitialClientMessage.java +++ b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2InitialClientMessage.java @@ -19,6 +19,8 @@ package org.wildfly.security.mechanism.oauth2; /** + * Represents the initial client message for OAuth2 protocol. + * * @author Pedro Igor */ public class OAuth2InitialClientMessage { @@ -27,24 +29,51 @@ public class OAuth2InitialClientMessage { private final byte[] messageBytes; private final String authorizationId; + /** + * Constructs a new {@code OAuth2InitialClientMessage} instance. + * + * @param authorizationId the ID of the user to be authorized. + * @param auth the authorization information in form of a String. + * @param messageBytes the byte array containing the message. + */ public OAuth2InitialClientMessage(String authorizationId, String auth, byte[] messageBytes) { this.authorizationId = authorizationId; this.auth = auth; this.messageBytes = messageBytes; } + /** + * Returns the ID of the user to be authorized. + * + * @return the ID of the user to be authorized. + */ public String getAuthorizationId() { return this.authorizationId; } + /** + * Returns the byte array containing the message. + * + * @return the byte array containing the message. + */ public byte[] getMessage() { return this.messageBytes; } + /** + * Returns the authorization information in form of a String. + * + * @return the authorization information in form of a String. + */ public String getAuth() { return auth; } + /** + * Returns whether the client provides a Bearer token. + * + * @return {@code True} if the authorization information contains "Bearer", {@code false} otherwise. + */ public boolean isBearerToken() { return this.auth.startsWith("Bearer"); } diff --git a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Server.java b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Server.java index c1c3e52324f..306771e6fc8 100644 --- a/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Server.java +++ b/mechanism/oauth2/src/main/java/org/wildfly/security/mechanism/oauth2/OAuth2Server.java @@ -40,7 +40,7 @@ import org.wildfly.security.mechanism.AuthenticationMechanismException; /** - * An OAuth2 Sasl Server based on RFC-7628. + * An OAuth2 Server based on RFC-7628. * * @author Pedro Igor */ @@ -53,12 +53,26 @@ public class OAuth2Server { private final Map serverConfig; private ElytronMessages log; + /** + * Constructs a new {@code OAuth2Server} instance. + * + * @param callbackHandler the callback handler for verifying the Bearer token. + * @param serverConfig the server configuration. + * @param log the logger to use. + */ public OAuth2Server(CallbackHandler callbackHandler, Map serverConfig, ElytronMessages log) { this.callbackHandler = callbackHandler; this.serverConfig = serverConfig; this.log = log; } + /** + * Parses the initial client's message in OAuth2 protocol. + * + * @param fromBytes the initial client's message. + * @return parsed client's message. + * @throws AuthenticationMechanismException if an error occurs during the parsing or the message is invalid. + */ public OAuth2InitialClientMessage parseInitialClientMessage(byte[] fromBytes) throws AuthenticationMechanismException { byte[] messageBytes = fromBytes.clone(); ByteIterator byteIterator = ByteIterator.ofBytes(fromBytes.clone()); @@ -98,6 +112,13 @@ public OAuth2InitialClientMessage parseInitialClientMessage(byte[] fromBytes) th } } + /** + * Returns the value associated with a key from an OAuth2 message. + * + * @param key the key for which the value is extracted. + * @param keyValuesPart the String containing key-value pairs in form of OAuth2 message. + * @return the value of the key-value pair, {@code null} if the key is not found. + */ private String getValue(String key, String keyValuesPart) { for (String current : keyValuesPart.split(KV_DELIMITER)) { String[] keyValue = current.split("="); @@ -110,6 +131,14 @@ private String getValue(String key, String keyValuesPart) { return null; } + /** + * Evaluates the initial response sent by the client and verifies if the Bearer token is valid. + * If so, authorizes the user. + * + * @param initialClientMessage the initial client's message containing the Bearer token. + * @return an empty byte array if the token was authorized, error message otherwise. + * @throws AuthenticationMechanismException if an error occurs during the evaluation or the message doesn't contain the Bearer token. + */ public byte[] evaluateInitialResponse(OAuth2InitialClientMessage initialClientMessage) throws AuthenticationMechanismException { if (initialClientMessage.isBearerToken()) { String auth = initialClientMessage.getAuth(); @@ -153,6 +182,12 @@ public byte[] evaluateInitialResponse(OAuth2InitialClientMessage initialClientMe throw log.mechInvalidClientMessage(); } + /** + * Creates an error message in the format of a json object. + * + * @return The error message containing a "status" field with the value "invalid_token" + * and an optional field "openid-configuration" with {@code CONFIG_OPENID_CONFIGURATION_URL} value. + */ private byte[] createErrorMessage() { JsonObjectBuilder objectBuilder = Json.createObjectBuilder(); diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramClient.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramClient.java index 0531242c362..c6cb67e86b9 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramClient.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramClient.java @@ -48,6 +48,8 @@ import org.wildfly.security.sasl.util.StringPrep; /** + * A client-side implementation for the SCRAM authentication. + * * @author David M. Lloyd */ public final class ScramClient { @@ -61,6 +63,19 @@ public final class ScramClient { private final int minimumIterationCount; private final int maximumIterationCount; + /** + * Constructs a new {@code ScramClient} instance. + * + * @param mechanism the SCRAM mechanism used for the authentication. + * @param authorizationId the ID of the user to be authorized. + * @param callbackHandler the callbackHandler used for the authentication. + * @param secureRandom an optional secure RNG to use. + * @param bindingData the binding data for the "PLUS" channel binding option. + * @param bindingType the binding type for the "PLUS" channel binding option. + * @param minimumIterationCount the minimum number of iterations for password hashing. + * @param maximumIterationCount the maximum number of iterations for password hashing. + * @param providers the security providers. + */ ScramClient(final ScramMechanism mechanism, final String authorizationId, final CallbackHandler callbackHandler, final SecureRandom secureRandom, final byte[] bindingData, final String bindingType, final int minimumIterationCount, final int maximumIterationCount, final Supplier providers) { this.mechanism = mechanism; this.authorizationId = authorizationId; @@ -73,26 +88,56 @@ public final class ScramClient { this.providers = providers; } + /** + * Returns the secure RNG used for the authentication. + * + * @return the secure RNG used for the authentication. + */ Random getRandom() { return secureRandom != null ? secureRandom : ThreadLocalRandom.current(); } + /** + * Returns the SCRAM mechanism used for the authentication. + * + * @return the SCRAM mechanism used for the authentication. + */ public ScramMechanism getMechanism() { return mechanism; } + /** + * Returns the ID of the user to be authorized. + * + * @return the ID of the user to be authorized. + */ public String getAuthorizationId() { return authorizationId; } + /** + * Returns the binding type for the "PLUS" channel binding option. + * + * @return the binding type for the "PLUS" channel binding option. + */ public String getBindingType() { return bindingType; } + /** + * Returns the binding data for the "PLUS" channel binding option. + * + * @return the binding data for the "PLUS" channel binding option. + */ byte[] getRawBindingData() { return bindingData; } + /** + * Returns a copy of the binding data for the "PLUS" channel binding option. + * + * @return a copy of the binding data for the "PLUS" channel binding option. + */ public byte[] getBindingData() { final byte[] bindingData = this.bindingData; return bindingData == null ? null : bindingData.clone(); @@ -146,6 +191,15 @@ public ScramInitialClientMessage getInitialResponse() throws AuthenticationMecha return new ScramInitialClientMessage(this, name, binding, nonce, initialPartIndex, encoded.toArray()); } + /** + * Parses the initial server message and creates {@link ScramInitialServerMessage} from parsed information. + * Also checks if the message have all necessary properties. + * + * @param initialResponse the initial client response for the server. + * @param bytes the byte array containing the initial server message to parse. + * @return the initial server message. + * @throws AuthenticationMechanismException if an error occurs during the parsing. + */ public ScramInitialServerMessage parseInitialServerMessage(final ScramInitialClientMessage initialResponse, final byte[] bytes) throws AuthenticationMechanismException { final byte[] challenge = bytes.clone(); final ByteIterator bi = ByteIterator.ofBytes(challenge); @@ -190,6 +244,18 @@ public ScramInitialServerMessage parseInitialServerMessage(final ScramInitialCli return new ScramInitialServerMessage(initialResponse, serverNonce, salt, iterationCount, challenge); } + /** + * Handles the initial challenge from the server and create a response from the client. + * The method uses a password credential obtained from the callback handler to derive a salted password, + * which is then used to generate a client key, stored key, and client proof. + * + * @param initialResponse the initial client message. + * @param initialChallenge the initial server message. + * @return the final client message. + * @throws AuthenticationMechanismException if an error occurs while obtaining the password, + * creating the {@link ScramFinalClientMessage} or the mechanism in the initial response or challenge message + * does not match the mechanism expected by the server + */ public ScramFinalClientMessage handleInitialChallenge(ScramInitialClientMessage initialResponse, ScramInitialServerMessage initialChallenge) throws AuthenticationMechanismException { boolean trace = saslScram.isTraceEnabled(); @@ -288,6 +354,14 @@ public ScramFinalClientMessage handleInitialChallenge(ScramInitialClientMessage } } + /** + * Parses the final server message and creates {@link ScramFinalServerMessage} from parsed information. + * Also checks if the message have all necessary properties. + * + * @param messageBytes the byte array of the final server message. + * @return the final server message. + * @throws AuthenticationMechanismException if an error occurs during the parsing or the server rejected the authentication request. + */ public ScramFinalServerMessage parseFinalServerMessage(final byte[] messageBytes) throws AuthenticationMechanismException { final ByteIterator bi = ByteIterator.ofBytes(messageBytes); final byte[] sig; @@ -312,6 +386,13 @@ public ScramFinalServerMessage parseFinalServerMessage(final byte[] messageBytes return new ScramFinalServerMessage(sig, messageBytes); } + /** + * Verifies the final challenge received from the server. + * + * @param finalResponse the final client message. + * @param finalChallenge the final server message. + * @throws AuthenticationMechanismException if an error occurs during the verification or the server signature is invalid. + */ public void verifyFinalChallenge(final ScramFinalClientMessage finalResponse, final ScramFinalServerMessage finalChallenge) throws AuthenticationMechanismException { boolean trace = saslScram.isTraceEnabled(); diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalClientMessage.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalClientMessage.java index ed77a508121..066f1ff8bde 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalClientMessage.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalClientMessage.java @@ -21,6 +21,8 @@ import org.wildfly.security.password.interfaces.ScramDigestPassword; /** + * Final client message for the SCRAM authentication. + * * @author David M. Lloyd */ public final class ScramFinalClientMessage { @@ -32,6 +34,16 @@ public final class ScramFinalClientMessage { private final byte[] messageBytes; private final int proofOffset; + /** + * Constructs a new {@code ScramFinalClientMessage} instance. + * + * @param initialResponse the initial client message. + * @param initialChallenge the initial server message. + * @param password the password used for authentication. + * @param clientProof the client proof sent to the server. + * @param messageBytes the byte array of the message. + * @param proofOffset the proof location in the {@code messageBytes}. + */ ScramFinalClientMessage(final ScramInitialClientMessage initialResponse, final ScramInitialServerMessage initialChallenge, final ScramDigestPassword password, final byte[] clientProof, final byte[] messageBytes, final int proofOffset) { this.initialResponse = initialResponse; this.initialChallenge = initialChallenge; @@ -41,38 +53,83 @@ public final class ScramFinalClientMessage { this.proofOffset = proofOffset; } + /** + * Returns the initial client message. + * + * @return the initial client message. + */ public ScramInitialClientMessage getInitialResponse() { return initialResponse; } + /** + * Returns the initial server message. + * + * @return the initial server message. + */ public ScramInitialServerMessage getInitialChallenge() { return initialChallenge; } + /** + * Returns the password used for authentication. + * + * @return the password used for authentication. + */ public ScramDigestPassword getPassword() { return password; } + /** + * Returns the client proof sent to the server. + * + * @return the client proof sent to the server. + */ byte[] getRawClientProof() { return clientProof; } + /** + * Returns the byte array of the message. + * + * @return the byte array of the message. + */ byte[] getRawMessageBytes() { return messageBytes; } + /** + * Returns a copy of the client proof sent to the server. + * + * @return a copy of the client proof sent to the server. + */ public byte[] getClientProof() { return clientProof.clone(); } + /** + * Returns a copy of the byte array of the message. + * + * @return a copy of the byte array of the message. + */ public byte[] getMessageBytes() { return messageBytes.clone(); } + /** + * Returns the SCRAM mechanism in the initial client message. + * + * @return the SCRAM mechanism in the initial client message. + */ public ScramMechanism getMechanism() { return initialResponse.getMechanism(); } + /** + * Returns the proof location in the message. + * + * @return the proof location in the message. + */ int getProofOffset() { return proofOffset; } diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalServerMessage.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalServerMessage.java index c8b77f2c922..1a4470ac7a7 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalServerMessage.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramFinalServerMessage.java @@ -19,29 +19,57 @@ package org.wildfly.security.mechanism.scram; /** + * Final server message for the SCRAM authentication. + * * @author David M. Lloyd */ public final class ScramFinalServerMessage { private final byte[] serverSignature; private final byte[] messageBytes; + /** + * Constructs a new {@code ScramFinalServerMessage} instance. + * + * @param serverSignature the server signature sent to the client in form of the byte array. + * @param messageBytes the final server message in form of byte array. + */ ScramFinalServerMessage(final byte[] serverSignature, final byte[] messageBytes) { this.serverSignature = serverSignature; this.messageBytes = messageBytes; } + /** + * Returns the server signature sent to the client in form of the byte array. + * + * @return the server signature sent to the client in form of the byte array. + */ byte[] getRawServerSignature() { return serverSignature; } + /** + * Returns the final server message in form of byte array. + * + * @return the final server message in form of byte array. + */ byte[] getRawMessageBytes() { return messageBytes; } + /** + * Returns a copy of the server signature sent to the client in form of the byte array. + * + * @return a copy of the server signature sent to the client in form of the byte array. + */ public byte[] getServerSignature() { return serverSignature.clone(); } + /** + * Returns a copy of the final server message in form of byte array. + * + * @return a copy of the final server message in form of byte array. + */ public byte[] getMessageBytes() { return messageBytes.clone(); } diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialClientMessage.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialClientMessage.java index 62c6eb212b0..e5e9fa0de15 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialClientMessage.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialClientMessage.java @@ -21,6 +21,8 @@ import java.util.Arrays; /** + * Initial client message for the SCRAM authentication. + * * @author David M. Lloyd */ public final class ScramInitialClientMessage { @@ -34,6 +36,16 @@ public final class ScramInitialClientMessage { private final int initialPartIndex; private final byte[] messageBytes; + /** + * Constructs a new {@code ScramInitialClientMessage} instance using data from the {@code scramClient}. + * + * @param scramClient the SCRAM client providing binding type and data, SCRAM mechanism and authorization ID. + * @param authenticationName the name of the user that is authenticated. + * @param binding whether the client supports channel binding. + * @param nonce a unique value generated by the client to the server. + * @param initialPartIndex index of the initial part of the message. + * @param messageBytes the byte array of the message. + */ ScramInitialClientMessage(final ScramClient scramClient, final String authenticationName, final boolean binding, final byte[] nonce, final int initialPartIndex, final byte[] messageBytes) { this.binding = binding; this.initialPartIndex = initialPartIndex; @@ -46,6 +58,19 @@ public final class ScramInitialClientMessage { this.messageBytes = messageBytes; } + /** + * Constructs a new {@code ScramInitialClientMessage} instance. + * + * @param mechanism the SCRAM mechanism used for the authentication. + * @param authorizationId the ID of the user to be authorized. + * @param authenticationName the name of the user that is authenticated. + * @param binding whether the client supports channel binding. + * @param bindingType the binding type for the "PLUS" channel binding option. + * @param bindingData the binding data for the "PLUS" channel binding option. + * @param nonce a unique value generated by the client to the server. + * @param initialPartIndex index of the initial part of the message. + * @param messageBytes the byte array of the message. + */ ScramInitialClientMessage(final ScramMechanism mechanism, final String authorizationId, final String authenticationName, final boolean binding, final String bindingType, final byte[] bindingData, final byte[] nonce, final int initialPartIndex, final byte[] messageBytes) { this.mechanism = mechanism; this.authorizationId = authorizationId; @@ -58,54 +83,119 @@ public final class ScramInitialClientMessage { this.messageBytes = messageBytes; } + /** + * Returns the SCRAM mechanism used for the authentication. + * + * @return the SCRAM mechanism used for the authentication. + */ public ScramMechanism getMechanism() { return mechanism; } + /** + * Returns the name of the user that is authenticated. + * + * @return the name of the user that is authenticated. + */ public String getAuthenticationName() { return authenticationName; } + /** + * Returns a copy of a unique value generated by the client to the server. + * + * @return a copy of a unique value generated by the client to the server. + */ public byte[] getNonce() { return nonce.clone(); } + /** + * Returns a unique value generated by the client to the server. + * + * @return a unique value generated by the client to the server. + */ byte[] getRawNonce() { return nonce; } + /** + * Returns the initial part of the message. + * + * @return the initial part of the message up to the length of {@code initialPartIndex}. + */ public byte[] getInitialPart() { return Arrays.copyOfRange(messageBytes, 0, initialPartIndex); } + /** + * Returns a copy of the byte array of the message. + * + * @return a copy of the byte array of the message. + */ public byte[] getMessageBytes() { return messageBytes.clone(); } + /** + * Returns the ID of the user to be authorized. + * + * @return the ID of the user to be authorized. + */ public String getAuthorizationId() { return authorizationId; } + /** + * Returns whether the client supports channel binding. + * + * @return {@code true} if the client supports channel binding, {@code false} otherwise. + */ public boolean isBinding() { return binding; } + /** + * Returns the binding type for the "PLUS" channel binding option. + * + * @return the binding type for the "PLUS" channel binding option. + */ public String getBindingType() { return bindingType; } + /** + * Returns a copy of the binding data for the "PLUS" channel binding option. + * + * @return a copy of the binding data for the "PLUS" channel binding option. + */ public byte[] getBindingData() { return bindingData == null ? null : bindingData.clone(); } + /** + * Returns the binding data for the "PLUS" channel binding option. + * + * @return the binding data for the "PLUS" channel binding option. + */ byte[] getRawBindingData() { return bindingData; } + /** + * Returns index of the initial part of the message. + * + * @return index of the initial part of the message. + */ int getInitialPartIndex() { return initialPartIndex; } + /** + * Returns the byte array of the message. + * + * @return the byte array of the message. + */ byte[] getRawMessageBytes() { return messageBytes; } diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerMessage.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerMessage.java index fd0b0d9306b..8e5f24a78bf 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerMessage.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerMessage.java @@ -19,6 +19,8 @@ package org.wildfly.security.mechanism.scram; /** + * Initial server message for the SCRAM authentication. + * * @author David M. Lloyd */ public final class ScramInitialServerMessage { @@ -28,6 +30,15 @@ public final class ScramInitialServerMessage { private final int iterationCount; private final byte[] messageBytes; + /** + * Constructs a new {@code ScramInitialServerMessage} instance. + * + * @param initialResponse the initial client message that this initial server message is responding to. + * @param serverNonce the server generated nonce. + * @param salt the salt used for generating salted password. + * @param iterationCount the iteration count used for generating salted password. + * @param messageBytes the message in form of byte array. + */ ScramInitialServerMessage(final ScramInitialClientMessage initialResponse, final byte[] serverNonce, final byte[] salt, final int iterationCount, final byte[] messageBytes) { this.initialResponse = initialResponse; this.serverNonce = serverNonce; @@ -36,38 +47,83 @@ public final class ScramInitialServerMessage { this.messageBytes = messageBytes; } + /** + * Returns the SCRAM mechanism in the initial client message. + * + * @return the SCRAM mechanism in the initial client message. + */ public ScramMechanism getMechanism() { return initialResponse.getMechanism(); } + /** + * Returns the initial client message. + * + * @return the initial client message. + */ public ScramInitialClientMessage getInitialResponse() { return initialResponse; } + /** + * Returns a copy of the server nonce. + * + * @return a copy of the server nonce. + */ public byte[] getServerNonce() { return serverNonce.clone(); } + /** + * Returns the server nonce. + * + * @return the server nonce. + */ byte[] getRawServerNonce() { return serverNonce; } + /** + * Returns the iteration count used for generating salted password. + * + * @return the iteration count used for generating salted password. + */ public int getIterationCount() { return iterationCount; } + /** + * Returns the salt used for generating salted password. + * + * @return the salt used for generating salted password. + */ byte[] getRawSalt() { return salt; } + /** + * Returns the initial server message in form of byte array. + * + * @return the initial server message in form of byte array. + */ byte[] getRawMessageBytes() { return messageBytes; } + /** + * Returns a copy of the salt used for generating salted password. + * + * @return a copy of the salt used for generating salted password. + */ public byte[] getSalt() { return salt.clone(); } + /** + * Returns a copy of the message in form of byte array. + * + * @return a copy of the message in form of byte array. + */ public byte[] getMessageBytes() { return messageBytes.clone(); } diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerResult.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerResult.java index 0c0df7332c2..07ff6d4f93c 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerResult.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramInitialServerResult.java @@ -21,21 +21,39 @@ import org.wildfly.security.password.interfaces.ScramDigestPassword; /** + * A class for encapsulation of the initial SCRAM challenge and the digest password. + * * @author David M. Lloyd */ public final class ScramInitialServerResult { private final ScramInitialServerMessage scramInitialChallenge; private final ScramDigestPassword scramDigestPassword; + /** + * Constructs a new {@code ScramInitialServerResult}. + * + * @param scramInitialChallenge the SCRAM challenge message. + * @param scramDigestPassword the digest password for the SCRAM authentication. + */ ScramInitialServerResult(final ScramInitialServerMessage scramInitialChallenge, final ScramDigestPassword scramDigestPassword) { this.scramInitialChallenge = scramInitialChallenge; this.scramDigestPassword = scramDigestPassword; } + /** + * Returns the SCRAM challenge message. + * + * @return ScramInitialServerMessage + */ public ScramInitialServerMessage getScramInitialChallenge() { return scramInitialChallenge; } + /** + * Returns the digest password for the SCRAM authentication. + * + * @return ScramDigestPassword + */ public ScramDigestPassword getScramDigestPassword() { return scramDigestPassword; } diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramMechanism.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramMechanism.java index 7f5ad535398..80ae5e41a93 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramMechanism.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramMechanism.java @@ -30,10 +30,12 @@ import org.wildfly.security.sasl.WildFlySasl; /** + * Implementation of the SCRAM authentication mechanism. + * * @author David M. Lloyd */ public final class ScramMechanism { - /** Hash size; may be less than the output size of the MD/MAC */ + // Hash size; may be less than the output size of the MD/MAC private final int hashSize; private final String messageDigestName; private final String hmacName; @@ -41,6 +43,15 @@ public final class ScramMechanism { private final String passwordAlgorithm; private final String toString; + /** + * Constructs a new {@code ScramMechanism}. + * + * @param hashSize the size of the hash of the SCRAM mechanism. + * @param messageDigestName the name of the message digest algorithm. + * @param hmacName the name of the HMAC algorithm. + * @param plus {@code true} to use the PLUS channel binding, {@code false} otherwise. + * @param passwordAlgorithm the name of the password algorithm in {@link ScramDigestPassword}. + */ private ScramMechanism(final int hashSize, final String messageDigestName, final String hmacName, final boolean plus, final String passwordAlgorithm) { this.hashSize = hashSize; this.messageDigestName = messageDigestName; @@ -75,6 +86,7 @@ private ScramMechanism(final int hashSize, final String messageDigestName, final * @param bindingCallback the optional channel binding callback result (may be {@code null}) * @param minimumIterationCount the minimum iteration count to allow * @param maximumIterationCount the maximum iteration count to allow + * @param providers the security providers. * @return the SCRAM client, or {@code null} if the client cannot be created from this mechanism variant * @throws AuthenticationMechanismException if the mechanism fails for some reason * @see WildFlySasl#SCRAM_MIN_ITERATION_COUNT @@ -94,6 +106,18 @@ public ScramClient createClient(final String authorizationId, final CallbackHand return new ScramClient(this, authorizationId, callbackHandler, secureRandom, bindingData, bindingType, minimumIterationCount, maximumIterationCount, providers); } + /** + * Create a SCRAM server for this mechanism. + * + * @param callbackHandler the callback handler (may not be {@code null}). + * @param random an optional secure random implementation to use (may be {@code null}). + * @param bindingCallback the optional channel binding callback result (may be {@code null}). + * @param minimumIterationCount the minimum iteration count to allow. + * @param maximumIterationCount the maximum iteration count to allow. + * @param providers the security providers. + * @return the SCRAM server, or {@code null} if the server cannot be created from this mechanism variant. + * @throws AuthenticationMechanismException if the mechanism fails for some reason. + */ public ScramServer createServer(final CallbackHandler callbackHandler, final SecureRandom random, final ChannelBindingCallback bindingCallback, final int minimumIterationCount, final int maximumIterationCount, final Supplier providers) throws AuthenticationMechanismException { final byte[] bindingData; final String bindingType; @@ -108,26 +132,57 @@ public ScramServer createServer(final CallbackHandler callbackHandler, final Sec return new ScramServer(this, callbackHandler, random, bindingData, bindingType, minimumIterationCount, maximumIterationCount, providers); } + /** + * Returns the size of the hash of the SCRAM mechanism. + * + * @return the size of the hash of the SCRAM mechanism. + */ public int getHashSize() { return hashSize; } + /** + * Returns the name of the message digest algorithm. + * + * @return the name of the message digest algorithm. + */ public String getMessageDigestName() { return messageDigestName; } + /** + * Returns the name of the HMAC algorithm. + * + * @return the name of the HMAC algorithm. + */ public String getHmacName() { return hmacName; } + /** + * Returns whether the SCRAM mechanism uses the PLUS channel binding. + * + * @return {@code true} to use the PLUS channel binding, {@code false} otherwise. + */ public boolean isPlus() { return plus; } + /** + * Returns the name of the password algorithm from {@code ScramDigestPassword}. + * + * @return the name of the password algorithm. + */ public String getPasswordAlgorithm() { return passwordAlgorithm; } + /** + * Returns a String representation of the SCRAM mechanism. + * Contains the Digest name, PLUS channel binding and hash size. + * + * @return a String representation of the SCRAM mechanism. + */ public String toString() { return toString; } diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramServer.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramServer.java index ab7360b9b50..2189a52bdb8 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramServer.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramServer.java @@ -55,6 +55,8 @@ import org.wildfly.security.sasl.util.StringPrep; /** + * A server-side implementation for the SCRAM authentication. + * * @author David M. Lloyd */ public final class ScramServer { @@ -67,6 +69,18 @@ public final class ScramServer { private final int minimumIterationCount; private final int maximumIterationCount; + /** + * Constructs a new {@code ScramServer}. + * + * @param mechanism the SCRAM mechanism used for the authentication. + * @param callbackHandler the callback handler for the authentication. + * @param random an optional secure RNG to use. + * @param bindingData the binding data for the "PLUS" channel binding option. + * @param bindingType the binding type for the "PLUS" channel binding option. + * @param minimumIterationCount the minimum number of iterations for password hashing. + * @param maximumIterationCount the maximum number of iterations for password hashing. + * @param providers the security providers. + */ ScramServer(final ScramMechanism mechanism, final CallbackHandler callbackHandler, final SecureRandom random, final byte[] bindingData, final String bindingType, final int minimumIterationCount, final int maximumIterationCount, final Supplier providers) { this.mechanism = mechanism; this.callbackHandler = callbackHandler; @@ -185,6 +199,14 @@ public ScramInitialClientMessage parseInitialClientMessage(ChannelBindingCallbac } } + /** + * Evaluates the initial client response message in SCRAM authentication. + * Generates a server nonce and salted password. + * + * @param clientMessage the initial client response message. + * @return the initial server result, containing the initial server message and the digest password. + * @throws AuthenticationMechanismException if an error occurs during the evaluation. + */ public ScramInitialServerResult evaluateInitialResponse(final ScramInitialClientMessage clientMessage) throws AuthenticationMechanismException { final boolean trace = saslScram.isTraceEnabled(); @@ -240,6 +262,16 @@ public ScramInitialServerResult evaluateInitialResponse(final ScramInitialClient return new ScramInitialServerResult(new ScramInitialServerMessage(clientMessage, serverNonce, salt, iterationCount, messageBytes), password); } + /** + * Parses the final client message and constructs the {@link ScramFinalClientMessage} from this parsed information. + * Also checks if the message has all necessary properties. + * + * @param initialResponse the initial client response message provided by {@link ScramServer#parseInitialClientMessage(ChannelBindingCallback, byte[])}. + * @param initialResult the initial server result provided by {@link ScramServer#evaluateInitialResponse(ScramInitialClientMessage)}. + * @param bytes the byte array representation of the client response. + * @return the final client message. + * @throws AuthenticationMechanismException if an error occurs during the parsing. + */ public ScramFinalClientMessage parseFinalClientMessage(final ScramInitialClientMessage initialResponse, final ScramInitialServerResult initialResult, final byte[] bytes) throws AuthenticationMechanismException { final ScramInitialServerMessage initialChallenge = initialResult.getScramInitialChallenge(); Assert.checkNotNullParam("initialResponse", initialResponse); @@ -352,6 +384,14 @@ public ScramFinalClientMessage parseFinalClientMessage(final ScramInitialClientM } } + /** + * Evaluates a SCRAM final client message and authorizes the user. + * + * @param initialResult the result of the initial server message evaluation provided by {@link ScramServer#evaluateInitialResponse(ScramInitialClientMessage)}. + * @param clientMessage the final client message provided by {@link ScramServer#parseFinalClientMessage(ScramInitialClientMessage, ScramInitialServerResult, byte[])}. + * @return the final server message providing the server signature and response. + * @throws AuthenticationMechanismException if an error occurs during the evaluation. + */ public ScramFinalServerMessage evaluateFinalClientMessage(final ScramInitialServerResult initialResult, final ScramFinalClientMessage clientMessage) throws AuthenticationMechanismException { final boolean trace = saslScram.isTraceEnabled(); @@ -464,26 +504,56 @@ public ScramFinalServerMessage evaluateFinalClientMessage(final ScramInitialServ } } + /** + * Returns the SCRAM mechanism used for the authentication. + * + * @return the SCRAM mechanism used for the authentication. + */ public ScramMechanism getMechanism() { return mechanism; } + /** + * Returns the callback handler for the authentication. + * + * @return the callback handler for the authentication. + */ public CallbackHandler getCallbackHandler() { return callbackHandler; } + /** + * Returns the RNG used for the authentication. + * + * @return the RNG used for the authentication. + */ Random getRandom() { return random != null ? random : ThreadLocalRandom.current(); } + /** + * Returns the copy of the binding data for the "PLUS" channel binding option. + * + * @return the copy of the binding data for the "PLUS" channel binding option. + */ public byte[] getBindingData() { return bindingData == null ? null : bindingData.clone(); } + /** + * Returns the binding data for the "PLUS" channel binding option. + * + * @return the binding data for the "PLUS" channel binding option. + */ byte[] getRawBindingData() { return bindingData; } + /** + * Returns the binding type for the "PLUS" channel binding option. + * + * @return the binding type for the "PLUS" channel binding option. + */ public String getBindingType() { return bindingType; } diff --git a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramUtil.java b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramUtil.java index dacdf27579a..6180f70e28f 100644 --- a/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramUtil.java +++ b/mechanism/scram/src/main/java/org/wildfly/security/mechanism/scram/ScramUtil.java @@ -26,6 +26,8 @@ import org.wildfly.common.iteration.ByteIterator; /** + * Common utility functions used by SCRAM authentication mechanism. + * * @author David M. Lloyd */ class ScramUtil { @@ -47,6 +49,13 @@ class ScramUtil { randomCharDictionary = dict; } + /** + * Generates nonce of specified length. + * + * @param length the length of the nonce. + * @param random the RNG used for creating the nonce. + * @return a byte array containing the nonce. + */ public static byte[] generateNonce(int length, Random random) { final byte[] chars = new byte[length]; for (int i = 0; i < length; i ++) { @@ -55,6 +64,13 @@ public static byte[] generateNonce(int length, Random random) { return chars; } + /** + * Parses positive integer from provided ByteIterator. + * + * @param i the ByteIterator to parse the positive integer from. + * @return the parsed integer. + * @throws NumberFormatException if the ByteIterator doesn't contain number or the number is too big for an integer + */ public static int parsePosInt(final ByteIterator i) { int a, c; if (! i.hasNext()) { @@ -80,6 +96,13 @@ public static int parsePosInt(final ByteIterator i) { return a; } + /** + * Bitwise XOR operation between two byte arrays of the same length. + * XOR operation returns 1 if only one of two corresponding bits is 1. For example: 0101 and 0011 gives 0110. + * + * @param hash the first byte array for the XOR operation. This byte array is modified by the method in place + * @param input the second byte array for the XOR operation. + */ static void xor(final byte[] hash, final byte[] input) { assert hash.length == input.length; for (int i = 0; i < hash.length; i++) {