001/*
002 * Copyright 2019-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2019-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2019-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk;
037
038
039
040import java.security.MessageDigest;
041import java.util.List;
042import java.util.logging.Level;
043import javax.crypto.Mac;
044import javax.crypto.spec.SecretKeySpec;
045
046import com.unboundid.asn1.ASN1OctetString;
047import com.unboundid.util.Debug;
048import com.unboundid.util.DebugType;
049import com.unboundid.util.Extensible;
050import com.unboundid.util.NotNull;
051import com.unboundid.util.Nullable;
052import com.unboundid.util.ThreadSafety;
053import com.unboundid.util.ThreadSafetyLevel;
054import com.unboundid.util.Validator;
055
056import static com.unboundid.ldap.sdk.LDAPMessages.*;
057
058
059
060/**
061 * This class provides the basis for bind requests that use the salted
062 * challenge-response authentication mechanism (SCRAM) described in
063 * <A HREF="http://www.ietf.org/rfc/rfc5802.txt">RFC 5802</A> and updated in
064 * <A HREF="https://tools.ietf.org/html/rfc7677">RFC 7677</A>.  Subclasses
065 * should extend this class to provide support for specific algorithms.
066 * <BR><BR>
067 * Note that this implementation does not support the PLUS variants of these
068 * algorithms, which requires channel binding support.
069 */
070@Extensible()
071@ThreadSafety(level= ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE)
072public abstract class SCRAMBindRequest
073       extends SASLBindRequest
074{
075  /**
076   * The serial version UID for this serializable class.
077   */
078  private static final long serialVersionUID = -1141722265190138366L;
079
080
081
082  // The password for this bind request.
083  @NotNull private final ASN1OctetString password;
084
085  // The username for this bind request.
086  @NotNull private final String username;
087
088
089
090  /**
091   * Creates a new SCRAM bind request with the provided information.
092   *
093   * @param  username  The username for this bind request.  It must not be
094   *                   {@code null} or empty.
095   * @param  password  The password for this bind request.  It must not be
096   *                   {@code null} or empty.
097   * @param  controls  The set of controls to include in the bind request.  It
098   *                   may be {@code null} or empty if no controls are needed.
099   */
100  public SCRAMBindRequest(@NotNull final String username,
101                          @NotNull final ASN1OctetString password,
102                          @Nullable final Control... controls)
103  {
104    super(controls);
105
106    Validator.ensureNotNullOrEmpty(username,
107         "SCRAMBindRequest.username must not be null or empty");
108    Validator.ensureTrue(
109         ((password != null) && (password.getValueLength() > 0)),
110         "SCRAMBindRequest.password must not be null or empty");
111
112    this.username = username;
113    this.password = password;
114  }
115
116
117
118  /**
119   * Retrieves the username for this bind request.
120   *
121   * @return  The password for this bind request.
122   */
123  @NotNull()
124  public final String getUsername()
125  {
126    return username;
127  }
128
129
130
131  /**
132   * Retrieves the password for this bind request, as a string.
133   *
134   * @return  The password for this bind request, as a string.
135   */
136  @NotNull()
137  public final String getPasswordString()
138  {
139    return password.stringValue();
140  }
141
142
143
144  /**
145   * Retrieves the bytes that comprise the password for this bind request.
146   *
147   * @return  The bytes that comprise the password for this bind request.
148   */
149  @NotNull()
150  public final byte[] getPasswordBytes()
151  {
152    return password.getValue();
153  }
154
155
156
157  /**
158   * Retrieves the name of the digest algorithm that will be used in the
159   * authentication processing.
160   *
161   * @return  The name of the digest algorithm that will be used in the
162   *          authentication processing.
163   */
164  @NotNull()
165  protected abstract String getDigestAlgorithmName();
166
167
168
169  /**
170   * Retrieves the name of the MAC algorithm that will be used in the
171   * authentication processing.
172   *
173   * @return  The name of the MAC algorithm that will be used in the
174   *          authentication processing.
175   */
176  @NotNull()
177  protected abstract String getMACAlgorithmName();
178
179
180
181  /**
182   * {@inheritDoc}
183   */
184  @Override()
185  @NotNull()
186  protected final BindResult process(@NotNull final LDAPConnection connection,
187                                     final int depth)
188            throws LDAPException
189  {
190    // Generate the client first message and send it to the server.
191    final SCRAMClientFirstMessage clientFirstMessage =
192         new SCRAMClientFirstMessage(this);
193    if (Debug.debugEnabled())
194    {
195      Debug.debug(Level.INFO, DebugType.LDAP,
196           "Sending " + getSASLMechanismName() + " client first message " +
197                clientFirstMessage);
198    }
199
200    final BindResult serverFirstResult = sendBindRequest(connection, null,
201         new ASN1OctetString(clientFirstMessage.getClientFirstMessage()),
202         getControls(), getResponseTimeoutMillis(connection));
203
204
205    // If the result code from the server first result is anything other than
206    // SASL_BIND_IN_PROGRESS, then return that result as a failure.
207    if (serverFirstResult.getResultCode() != ResultCode.SASL_BIND_IN_PROGRESS)
208    {
209      return serverFirstResult;
210    }
211
212
213    // Parse the server first result, and use it to compute the client final
214    // message.
215    final SCRAMServerFirstMessage serverFirstMessage =
216         new SCRAMServerFirstMessage(this, clientFirstMessage,
217              serverFirstResult);
218    if (Debug.debugEnabled())
219    {
220      Debug.debug(Level.INFO, DebugType.LDAP,
221           "Received " + getSASLMechanismName() + " server first message " +
222                serverFirstMessage);
223    }
224
225    final SCRAMClientFinalMessage clientFinalMessage =
226         new SCRAMClientFinalMessage(this, clientFirstMessage,
227              serverFirstMessage);
228    if (Debug.debugEnabled())
229    {
230      Debug.debug(Level.INFO, DebugType.LDAP,
231           "Sending " + getSASLMechanismName() + " client final message " +
232                clientFinalMessage);
233    }
234
235
236    // Send the server final bind request to the server and get the result.
237    // We don't care what the result code was, because the server final message
238    // processing will handle both success and failure.
239    final BindResult serverFinalResult = sendBindRequest(connection, null,
240         new ASN1OctetString(clientFinalMessage.getClientFinalMessage()),
241         getControls(), getResponseTimeoutMillis(connection));
242
243    final SCRAMServerFinalMessage serverFinalMessage =
244         new SCRAMServerFinalMessage(this, clientFirstMessage,
245              clientFinalMessage, serverFinalResult);
246    if (Debug.debugEnabled())
247    {
248      Debug.debug(Level.INFO, DebugType.LDAP,
249           "Received " + getSASLMechanismName() + " server final message " +
250                serverFinalMessage);
251    }
252
253
254    // If we've gotten here, then the bind was successful.  Return the server
255    // final result.
256    return serverFinalResult;
257  }
258
259
260
261  /**
262   * Computes a MAC of the provided data with the given key.
263   *
264   * @param  key   The bytes to use as the key for the MAC.
265   * @param  data  The data for which to generate the MAC.
266   *
267   * @return  The MAC that was computed.
268   *
269   * @throws  LDAPBindException  If a problem is encountered while computing the
270   *                             MAC.
271   */
272  @NotNull()
273  final byte[] mac(@NotNull final byte[] key, @NotNull final byte[] data)
274        throws LDAPBindException
275  {
276    return getMac(key).doFinal(data);
277  }
278
279
280
281  /**
282   * Retrieves a MAC generator for the provided key.
283   *
284   * @param  key  The bytes to use as the key for the MAC.
285   *
286   * @return  The MAC generator.
287   *
288   * @throws  LDAPBindException  If a problem is encountered while obtaining the
289   *                             MAC generator.
290   */
291  @NotNull()
292  final Mac getMac(@NotNull final byte[] key)
293        throws LDAPBindException
294  {
295    try
296    {
297      final Mac mac = Mac.getInstance(getMACAlgorithmName());
298      final SecretKeySpec macKey =
299           new SecretKeySpec(key, getMACAlgorithmName());
300      mac.init(macKey);
301      return mac;
302    }
303    catch (final Exception e)
304    {
305      Debug.debugException(e);
306      throw new LDAPBindException(new BindResult(-1,
307           ResultCode.LOCAL_ERROR,
308           ERR_SCRAM_BIND_REQUEST_CANNOT_GET_MAC.get(getSASLMechanismName(),
309                getMACAlgorithmName()),
310           null, null, null, null));
311    }
312  }
313
314
315
316  /**
317   * Computes a message digest of the provided data with the given key.
318   *
319   * @param  data  The data for which to generate the digest.
320   *
321   * @return  The digest that was computed.
322   *
323   * @throws  LDAPBindException  If a problem is encountered while computing the
324   *                             digest.
325   */
326  @NotNull()
327  final byte[] digest(@NotNull final byte[] data)
328        throws LDAPBindException
329  {
330    try
331    {
332      final MessageDigest digest =
333           MessageDigest.getInstance(getDigestAlgorithmName());
334      return digest.digest(data);
335    }
336    catch (final Exception e)
337    {
338      Debug.debugException(e);
339      throw new LDAPBindException(new BindResult(-1,
340           ResultCode.LOCAL_ERROR,
341           ERR_SCRAM_BIND_REQUEST_CANNOT_GET_DIGEST.get(
342                getSASLMechanismName(), getDigestAlgorithmName()),
343           null, null, null, null));
344    }
345  }
346
347
348
349  /**
350   * {@inheritDoc}
351   */
352  @Override()
353  @NotNull()
354  public abstract SCRAMBindRequest getRebindRequest(
355                                        @NotNull final String host,
356                                        final int port);
357
358
359
360  /**
361   * {@inheritDoc}
362   */
363  @Override()
364  @NotNull()
365  public abstract SCRAMBindRequest duplicate();
366
367
368
369  /**
370   * {@inheritDoc}
371   */
372  @Override()
373  @NotNull()
374  public abstract SCRAMBindRequest duplicate(@NotNull final Control[] controls);
375
376
377
378  /**
379   * {@inheritDoc}
380   */
381  @Override()
382  public abstract void toString(@NotNull final StringBuilder buffer);
383
384
385
386  /**
387   * {@inheritDoc}
388   */
389  @Override()
390  public abstract void toCode(@NotNull final List<String> lineList,
391                              @NotNull final String requestID,
392                              final int indentSpaces,
393                              final boolean includeProcessing);
394}