001/* 002 * Copyright 2012-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2012-2024 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) 2012-2024 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.unboundidds; 037 038 039 040import java.util.ArrayList; 041import java.util.List; 042 043import com.unboundid.asn1.ASN1Element; 044import com.unboundid.asn1.ASN1OctetString; 045import com.unboundid.asn1.ASN1Sequence; 046import com.unboundid.ldap.sdk.Control; 047import com.unboundid.ldap.sdk.LDAPException; 048import com.unboundid.ldap.sdk.ResultCode; 049import com.unboundid.ldap.sdk.ToCodeArgHelper; 050import com.unboundid.ldap.sdk.ToCodeHelper; 051import com.unboundid.util.Debug; 052import com.unboundid.util.NotMutable; 053import com.unboundid.util.NotNull; 054import com.unboundid.util.Nullable; 055import com.unboundid.util.StaticUtils; 056import com.unboundid.util.ThreadSafety; 057import com.unboundid.util.ThreadSafetyLevel; 058import com.unboundid.util.Validator; 059 060import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*; 061 062 063 064/** 065 * This class provides an implementation of the UNBOUNDID-TOTP SASL bind request 066 * that contains a point-in-time version of the one-time password and can be 067 * used for a single bind but is not suitable for repeated use. This version of 068 * the bind request should be used for authentication in which the one-time 069 * password is provided by an external source rather than being generated by 070 * the LDAP SDK. 071 * <BR> 072 * <BLOCKQUOTE> 073 * <B>NOTE:</B> This class, and other classes within the 074 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 075 * supported for use against Ping Identity, UnboundID, and 076 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 077 * for proprietary functionality or for external specifications that are not 078 * considered stable or mature enough to be guaranteed to work in an 079 * interoperable way with other types of LDAP servers. 080 * </BLOCKQUOTE> 081 * <BR> 082 * Because the one-time password is provided rather than generated, this version 083 * of the bind request is not suitable for cases in which the authentication 084 * process may need to be repeated (e.g., for use in a connection pool, 085 * following referrals, or if the auto-reconnect feature is enabled), then the 086 * reusable variant (supported by the {@link ReusableTOTPBindRequest} class) 087 * which generates the one-time password should be used instead. 088 */ 089@NotMutable() 090@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 091public final class SingleUseTOTPBindRequest 092 extends UnboundIDTOTPBindRequest 093{ 094 /** 095 * The serial version UID for this serializable class. 096 */ 097 private static final long serialVersionUID = -4429898810534930296L; 098 099 100 101 // The hard-coded TOTP password to include in the bind request. 102 @NotNull private final String totpPassword; 103 104 105 106 /** 107 * Creates a new SASL TOTP bind request with the provided information. 108 * 109 * @param authenticationID The authentication identity for the bind request. 110 * It must not be {@code null}, and must be in the 111 * form "u:" followed by a username, or "dn:" 112 * followed by a DN. 113 * @param authorizationID The authorization identity for the bind request. 114 * It may be {@code null} if the authorization 115 * identity should be the same as the authentication 116 * identity. If an authorization identity is 117 * specified, it must be in the form "u:" followed 118 * by a username, or "dn:" followed by a DN. The 119 * value "dn:" may indicate an authorization 120 * identity of the anonymous user. 121 * @param totpPassword The hard-coded TOTP password to include in the 122 * bind request. It must not be {@code null}. 123 * @param staticPassword The static password for the target user. It may 124 * be {@code null} if only the one-time password is 125 * to be used for authentication (which may or may 126 * not be allowed by the server). 127 * @param controls The set of controls to include in the bind 128 * request. 129 */ 130 public SingleUseTOTPBindRequest(@NotNull final String authenticationID, 131 @Nullable final String authorizationID, 132 @NotNull final String totpPassword, 133 @Nullable final String staticPassword, 134 @Nullable final Control... controls) 135 { 136 super(authenticationID, authorizationID, staticPassword, controls); 137 138 Validator.ensureNotNull(totpPassword); 139 this.totpPassword = totpPassword; 140 } 141 142 143 144 /** 145 * Creates a new SASL TOTP bind request with the provided information. 146 * 147 * @param authenticationID The authentication identity for the bind request. 148 * It must not be {@code null}, and must be in the 149 * form "u:" followed by a username, or "dn:" 150 * followed by a DN. 151 * @param authorizationID The authorization identity for the bind request. 152 * It may be {@code null} if the authorization 153 * identity should be the same as the authentication 154 * identity. If an authorization identity is 155 * specified, it must be in the form "u:" followed 156 * by a username, or "dn:" followed by a DN. The 157 * value "dn:" may indicate an authorization 158 * identity of the anonymous user. 159 * @param totpPassword The hard-coded TOTP password to include in the 160 * bind request. It must not be {@code null}. 161 * @param staticPassword The static password for the target user. It may 162 * be {@code null} if only the one-time password is 163 * to be used for authentication (which may or may 164 * not be allowed by the server). 165 * @param controls The set of controls to include in the bind 166 * request. 167 */ 168 public SingleUseTOTPBindRequest(@NotNull final String authenticationID, 169 @Nullable final String authorizationID, 170 @NotNull final String totpPassword, 171 @Nullable final byte[] staticPassword, 172 @Nullable final Control... controls) 173 { 174 super(authenticationID, authorizationID, staticPassword, controls); 175 176 Validator.ensureNotNull(totpPassword); 177 this.totpPassword = totpPassword; 178 } 179 180 181 182 /** 183 * Creates a new SASL TOTP bind request with the provided information. 184 * 185 * @param authenticationID The authentication identity for the bind request. 186 * It must not be {@code null}, and must be in the 187 * form "u:" followed by a username, or "dn:" 188 * followed by a DN. 189 * @param authorizationID The authorization identity for the bind request. 190 * It may be {@code null} if the authorization 191 * identity should be the same as the authentication 192 * identity. If an authorization identity is 193 * specified, it must be in the form "u:" followed 194 * by a username, or "dn:" followed by a DN. The 195 * value "dn:" may indicate an authorization 196 * identity of the anonymous user. 197 * @param totpPassword The hard-coded TOTP password to include in the 198 * bind request. It must not be {@code null}. 199 * @param staticPassword The static password for the target user. It may 200 * be {@code null} if only the one-time password is 201 * to be used for authentication (which may or may 202 * not be allowed by the server). 203 * @param controls The set of controls to include in the bind 204 * request. 205 */ 206 private SingleUseTOTPBindRequest(@NotNull final String authenticationID, 207 @Nullable final String authorizationID, 208 @NotNull final String totpPassword, 209 @Nullable final ASN1OctetString staticPassword, 210 @Nullable final Control... controls) 211 { 212 super(authenticationID, authorizationID, staticPassword, controls); 213 214 Validator.ensureNotNull(totpPassword); 215 this.totpPassword = totpPassword; 216 } 217 218 219 220 /** 221 * Creates a new single-use TOTP bind request from the information contained 222 * in the provided encoded SASL credentials. 223 * 224 * @param saslCredentials The encoded SASL credentials to be decoded in 225 * order to create this single-use TOTP bind request. 226 * It must not be {@code null}. 227 * @param controls The set of controls to include in the bind 228 * request. 229 * 230 * @return The single-use TOTP bind request decoded from the provided 231 * credentials. 232 * 233 * @throws LDAPException If the provided credentials are not valid for an 234 * UNBOUNDID-TOTP bind request. 235 */ 236 @NotNull() 237 public static SingleUseTOTPBindRequest decodeSASLCredentials( 238 @NotNull final ASN1OctetString saslCredentials, 239 @Nullable final Control... controls) 240 throws LDAPException 241 { 242 try 243 { 244 String authenticationID = null; 245 String authorizationID = null; 246 String totpPassword = null; 247 ASN1OctetString staticPassword = null; 248 249 final ASN1Sequence s = 250 ASN1Sequence.decodeAsSequence(saslCredentials.getValue()); 251 for (final ASN1Element e : s.elements()) 252 { 253 switch (e.getType()) 254 { 255 case TYPE_AUTHENTICATION_ID: 256 authenticationID = e.decodeAsOctetString().stringValue(); 257 break; 258 case TYPE_AUTHORIZATION_ID: 259 authorizationID = e.decodeAsOctetString().stringValue(); 260 break; 261 case TYPE_TOTP_PASSWORD: 262 totpPassword = e.decodeAsOctetString().stringValue(); 263 break; 264 case TYPE_STATIC_PASSWORD: 265 staticPassword = e.decodeAsOctetString(); 266 break; 267 default: 268 throw new LDAPException(ResultCode.DECODING_ERROR, 269 ERR_SINGLE_USE_TOTP_DECODE_INVALID_ELEMENT_TYPE.get( 270 StaticUtils.toHex(e.getType()))); 271 } 272 } 273 274 if (authenticationID == null) 275 { 276 throw new LDAPException(ResultCode.DECODING_ERROR, 277 ERR_SINGLE_USE_TOTP_DECODE_MISSING_AUTHN_ID.get()); 278 } 279 280 if (totpPassword == null) 281 { 282 throw new LDAPException(ResultCode.DECODING_ERROR, 283 ERR_SINGLE_USE_TOTP_DECODE_MISSING_TOTP_PW.get()); 284 } 285 286 return new SingleUseTOTPBindRequest(authenticationID, authorizationID, 287 totpPassword, staticPassword, controls); 288 } 289 catch (final Exception e) 290 { 291 Debug.debugException(e); 292 throw new LDAPException(ResultCode.DECODING_ERROR, 293 ERR_SINGLE_USE_TOTP_DECODE_ERROR.get( 294 StaticUtils.getExceptionMessage(e)), 295 e); 296 } 297 } 298 299 300 301 /** 302 * Retrieves the hard-coded TOTP password to include in the bind request. 303 * 304 * @return The hard-coded TOTP password to include in the bind request. 305 */ 306 @NotNull() 307 public String getTOTPPassword() 308 { 309 return totpPassword; 310 } 311 312 313 314 /** 315 * {@inheritDoc} 316 */ 317 @Override() 318 @NotNull() 319 protected ASN1OctetString getSASLCredentials() 320 { 321 return encodeCredentials(getAuthenticationID(), getAuthorizationID(), 322 totpPassword, getStaticPassword()); 323 } 324 325 326 327 /** 328 * {@inheritDoc} 329 */ 330 @Override() 331 @Nullable() 332 public SingleUseTOTPBindRequest getRebindRequest(@NotNull final String host, 333 final int port) 334 { 335 // Automatic rebinding is not supported for single-use TOTP binds. 336 return null; 337 } 338 339 340 341 /** 342 * {@inheritDoc} 343 */ 344 @Override() 345 @NotNull() 346 public SingleUseTOTPBindRequest duplicate() 347 { 348 return duplicate(getControls()); 349 } 350 351 352 353 /** 354 * {@inheritDoc} 355 */ 356 @Override() 357 @NotNull() 358 public SingleUseTOTPBindRequest duplicate(@Nullable final Control[] controls) 359 { 360 final SingleUseTOTPBindRequest bindRequest = 361 new SingleUseTOTPBindRequest(getAuthenticationID(), 362 getAuthorizationID(), totpPassword, getStaticPassword(), 363 controls); 364 bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 365 bindRequest.setIntermediateResponseListener( 366 getIntermediateResponseListener()); 367 bindRequest.setReferralDepth(getReferralDepth()); 368 bindRequest.setReferralConnector(getReferralConnectorInternal()); 369 return bindRequest; 370 } 371 372 373 374 /** 375 * {@inheritDoc} 376 */ 377 @Override() 378 public void toCode(@NotNull final List<String> lineList, 379 @NotNull final String requestID, 380 final int indentSpaces, final boolean includeProcessing) 381 { 382 // Create the request variable. 383 final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(5); 384 constructorArgs.add(ToCodeArgHelper.createString(getAuthenticationID(), 385 "Authentication ID")); 386 constructorArgs.add(ToCodeArgHelper.createString(getAuthorizationID(), 387 "Authorization ID")); 388 constructorArgs.add(ToCodeArgHelper.createString( 389 "---redacted-totp-password---", "TOTP Password")); 390 constructorArgs.add(ToCodeArgHelper.createString( 391 ((getStaticPassword() == null) 392 ? "null" 393 : "---redacted-static-password---"), 394 "Static Password")); 395 396 final Control[] controls = getControls(); 397 if (controls.length > 0) 398 { 399 constructorArgs.add(ToCodeArgHelper.createControlArray(controls, 400 "Bind Controls")); 401 } 402 403 ToCodeHelper.generateMethodCall(lineList, indentSpaces, 404 "SingleUseTOTPBindRequest", requestID + "Request", 405 "new SingleUseTOTPBindRequest", constructorArgs); 406 407 408 // Add lines for processing the request and obtaining the result. 409 if (includeProcessing) 410 { 411 // Generate a string with the appropriate indent. 412 final StringBuilder buffer = new StringBuilder(); 413 for (int i=0; i < indentSpaces; i++) 414 { 415 buffer.append(' '); 416 } 417 final String indent = buffer.toString(); 418 419 lineList.add(""); 420 lineList.add(indent + "try"); 421 lineList.add(indent + '{'); 422 lineList.add(indent + " BindResult " + requestID + 423 "Result = connection.bind(" + requestID + "Request);"); 424 lineList.add(indent + " // The bind was processed successfully."); 425 lineList.add(indent + '}'); 426 lineList.add(indent + "catch (LDAPException e)"); 427 lineList.add(indent + '{'); 428 lineList.add(indent + " // The bind failed. Maybe the following will " + 429 "help explain why."); 430 lineList.add(indent + " // Note that the connection is now likely in " + 431 "an unauthenticated state."); 432 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 433 lineList.add(indent + " String message = e.getMessage();"); 434 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 435 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 436 lineList.add(indent + " Control[] responseControls = " + 437 "e.getResponseControls();"); 438 lineList.add(indent + '}'); 439 } 440 } 441}