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.ThreadSafety; 051import com.unboundid.util.ThreadSafetyLevel; 052import com.unboundid.util.Validator; 053 054import static com.unboundid.ldap.sdk.LDAPMessages.*; 055 056 057 058/** 059 * This class provides the basis for bind requests that use the salted 060 * challenge-response authentication mechanism (SCRAM) described in 061 * <A HREF="http://www.ietf.org/rfc/rfc5802.txt">RFC 5802</A> and updated in 062 * <A HREF="https://tools.ietf.org/html/rfc7677">RFC 7677</A>. Subclasses 063 * should extend this class to provide support for specific algorithms. 064 * <BR><BR> 065 * Note that this implementation does not support the PLUS variants of these 066 * algorithms, which requires channel binding support. 067 */ 068@Extensible() 069@ThreadSafety(level= ThreadSafetyLevel.INTERFACE_NOT_THREADSAFE) 070public abstract class SCRAMBindRequest 071 extends SASLBindRequest 072{ 073 /** 074 * The serial version UID for this serializable class. 075 */ 076 private static final long serialVersionUID = -1141722265190138366L; 077 078 079 080 // The password for this bind request. 081 private final ASN1OctetString password; 082 083 // The username for this bind request. 084 private final String username; 085 086 087 088 /** 089 * Creates a new SCRAM bind request with the provided information. 090 * 091 * @param username The username for this bind request. It must not be 092 * {@code null} or empty. 093 * @param password The password for this bind request. It must not be 094 * {@code null} or empty. 095 * @param controls The set of controls to include in the bind request. It 096 * may be {@code null} or empty if no controls are needed. 097 */ 098 public SCRAMBindRequest(final String username, final ASN1OctetString password, 099 final Control... controls) 100 { 101 super(controls); 102 103 Validator.ensureNotNullOrEmpty(username, 104 "SCRAMBindRequest.username must not be null or empty"); 105 Validator.ensureTrue( 106 ((password != null) && (password.getValueLength() > 0)), 107 "SCRAMBindRequest.password must not be null or empty"); 108 109 this.username = username; 110 this.password = password; 111 } 112 113 114 115 /** 116 * Retrieves the username for this bind request. 117 * 118 * @return The password for this bind request. 119 */ 120 public final String getUsername() 121 { 122 return username; 123 } 124 125 126 127 /** 128 * Retrieves the password for this bind request, as a string. 129 * 130 * @return The password for this bind request, as a string. 131 */ 132 public final String getPasswordString() 133 { 134 return password.stringValue(); 135 } 136 137 138 139 /** 140 * Retrieves the bytes that comprise the password for this bind request. 141 * 142 * @return The bytes that comprise the password for this bind request. 143 */ 144 public final byte[] getPasswordBytes() 145 { 146 return password.getValue(); 147 } 148 149 150 151 /** 152 * Retrieves the name of the digest algorithm that will be used in the 153 * authentication processing. 154 * 155 * @return The name of the digest algorithm that will be used in the 156 * authentication processing. 157 */ 158 protected abstract String getDigestAlgorithmName(); 159 160 161 162 /** 163 * Retrieves the name of the MAC algorithm that will be used in the 164 * authentication processing. 165 * 166 * @return The name of the MAC algorithm that will be used in the 167 * authentication processing. 168 */ 169 protected abstract String getMACAlgorithmName(); 170 171 172 173 /** 174 * {@inheritDoc} 175 */ 176 @Override() 177 protected final BindResult process(final LDAPConnection connection, 178 final int depth) 179 throws LDAPException 180 { 181 // Generate the client first message and send it to the server. 182 final SCRAMClientFirstMessage clientFirstMessage = 183 new SCRAMClientFirstMessage(this); 184 if (Debug.debugEnabled()) 185 { 186 Debug.debug(Level.INFO, DebugType.LDAP, 187 "Sending " + getSASLMechanismName() + " client first message " + 188 clientFirstMessage); 189 } 190 191 final BindResult serverFirstResult = sendBindRequest(connection, null, 192 new ASN1OctetString(clientFirstMessage.getClientFirstMessage()), 193 getControls(), getResponseTimeoutMillis(connection)); 194 195 196 // If the result code from the server first result is anything other than 197 // SASL_BIND_IN_PROGRESS, then return that result as a failure. 198 if (serverFirstResult.getResultCode() != ResultCode.SASL_BIND_IN_PROGRESS) 199 { 200 return serverFirstResult; 201 } 202 203 204 // Parse the server first result, and use it to compute the client final 205 // message. 206 final SCRAMServerFirstMessage serverFirstMessage = 207 new SCRAMServerFirstMessage(this, clientFirstMessage, 208 serverFirstResult); 209 if (Debug.debugEnabled()) 210 { 211 Debug.debug(Level.INFO, DebugType.LDAP, 212 "Received " + getSASLMechanismName() + " server first message " + 213 serverFirstMessage); 214 } 215 216 final SCRAMClientFinalMessage clientFinalMessage = 217 new SCRAMClientFinalMessage(this, clientFirstMessage, 218 serverFirstMessage); 219 if (Debug.debugEnabled()) 220 { 221 Debug.debug(Level.INFO, DebugType.LDAP, 222 "Sending " + getSASLMechanismName() + " client final message " + 223 clientFinalMessage); 224 } 225 226 227 // Send the server final bind request to the server and get the result. 228 // We don't care what the result code was, because the server final message 229 // processing will handle both success and failure. 230 final BindResult serverFinalResult = sendBindRequest(connection, null, 231 new ASN1OctetString(clientFinalMessage.getClientFinalMessage()), 232 getControls(), getResponseTimeoutMillis(connection)); 233 234 final SCRAMServerFinalMessage serverFinalMessage = 235 new SCRAMServerFinalMessage(this, clientFirstMessage, 236 clientFinalMessage, serverFinalResult); 237 if (Debug.debugEnabled()) 238 { 239 Debug.debug(Level.INFO, DebugType.LDAP, 240 "Received " + getSASLMechanismName() + " server final message " + 241 serverFinalMessage); 242 } 243 244 245 // If we've gotten here, then the bind was successful. Return the server 246 // final result. 247 return serverFinalResult; 248 } 249 250 251 252 /** 253 * Computes a MAC of the provided data with the given key. 254 * 255 * @param key The bytes to use as the key for the MAC. 256 * @param data The data for which to generate the MAC. 257 * 258 * @return The MAC that was computed. 259 * 260 * @throws LDAPBindException If a problem is encountered while computing the 261 * MAC. 262 */ 263 final byte[] mac(final byte[] key, final byte[] data) 264 throws LDAPBindException 265 { 266 return getMac(key).doFinal(data); 267 } 268 269 270 271 /** 272 * Retrieves a MAC generator for the provided key. 273 * 274 * @param key The bytes to use as the key for the MAC. 275 * 276 * @return The MAC generator. 277 * 278 * @throws LDAPBindException If a problem is encountered while obtaining the 279 * MAC generator. 280 */ 281 final Mac getMac(final byte[] key) 282 throws LDAPBindException 283 { 284 try 285 { 286 final Mac mac = Mac.getInstance(getMACAlgorithmName()); 287 final SecretKeySpec macKey = 288 new SecretKeySpec(key, getMACAlgorithmName()); 289 mac.init(macKey); 290 return mac; 291 } 292 catch (final Exception e) 293 { 294 Debug.debugException(e); 295 throw new LDAPBindException(new BindResult(-1, 296 ResultCode.LOCAL_ERROR, 297 ERR_SCRAM_BIND_REQUEST_CANNOT_GET_MAC.get(getSASLMechanismName(), 298 getMACAlgorithmName()), 299 null, null, null, null)); 300 } 301 } 302 303 304 305 /** 306 * Computes a message digest of the provided data with the given key. 307 * 308 * @param data The data for which to generate the digest. 309 * 310 * @return The digest that was computed. 311 * 312 * @throws LDAPBindException If a problem is encountered while computing the 313 * digest. 314 */ 315 final byte[] digest(final byte[] data) 316 throws LDAPBindException 317 { 318 try 319 { 320 final MessageDigest digest = 321 MessageDigest.getInstance(getDigestAlgorithmName()); 322 return digest.digest(data); 323 } 324 catch (final Exception e) 325 { 326 Debug.debugException(e); 327 throw new LDAPBindException(new BindResult(-1, 328 ResultCode.LOCAL_ERROR, 329 ERR_SCRAM_BIND_REQUEST_CANNOT_GET_DIGEST.get( 330 getSASLMechanismName(), getDigestAlgorithmName()), 331 null, null, null, null)); 332 } 333 } 334 335 336 337 /** 338 * {@inheritDoc} 339 */ 340 @Override() 341 public abstract SCRAMBindRequest getRebindRequest(final String host, 342 final int port); 343 344 345 346 /** 347 * {@inheritDoc} 348 */ 349 @Override() 350 public abstract SCRAMBindRequest duplicate(); 351 352 353 354 /** 355 * {@inheritDoc} 356 */ 357 @Override() 358 public abstract SCRAMBindRequest duplicate(final Control[] controls); 359 360 361 362 /** 363 * {@inheritDoc} 364 */ 365 @Override() 366 public abstract void toString(final StringBuilder buffer); 367 368 369 370 /** 371 * {@inheritDoc} 372 */ 373 @Override() 374 public abstract void toCode(final List<String> lineList, 375 final String requestID, 376 final int indentSpaces, 377 final boolean includeProcessing); 378}