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