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.nio.charset.StandardCharsets; 041import java.util.ArrayList; 042import java.util.List; 043 044import com.unboundid.asn1.ASN1OctetString; 045import com.unboundid.ldap.sdk.Control; 046import com.unboundid.ldap.sdk.LDAPException; 047import com.unboundid.ldap.sdk.ToCodeArgHelper; 048import com.unboundid.ldap.sdk.ToCodeHelper; 049import com.unboundid.util.NotMutable; 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 056 057 058/** 059 * This class provides an implementation of the UNBOUNDID-TOTP SASL bind request 060 * that may be used to repeatedly generate one-time password values. Because it 061 * is configured with the shared secret rather than a point-in-time version of 062 * the password, it can be used for cases in which the authentication process 063 * may need to be repeated (e.g., for use in a connection pool, following 064 * referrals, or if the auto-reconnect feature is enabled). If the shared 065 * secret is not known and the one-time password will be provided from an 066 * external source (e.g., entered by a user), then the 067 * {@link SingleUseTOTPBindRequest} variant should be used instead. 068 * <BR> 069 * <BLOCKQUOTE> 070 * <B>NOTE:</B> This class, and other classes within the 071 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 072 * supported for use against Ping Identity, UnboundID, and 073 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 074 * for proprietary functionality or for external specifications that are not 075 * considered stable or mature enough to be guaranteed to work in an 076 * interoperable way with other types of LDAP servers. 077 * </BLOCKQUOTE> 078 */ 079@NotMutable() 080@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 081public final class ReusableTOTPBindRequest 082 extends UnboundIDTOTPBindRequest 083{ 084 /** 085 * The serial version UID for this serializable class. 086 */ 087 private static final long serialVersionUID = -8283436883838802510L; 088 089 090 091 // The shared secret key to use when generating the TOTP password. 092 @NotNull private final byte[] sharedSecret; 093 094 // The duration (in seconds) of the time interval to use when generating the 095 // TOTP password. 096 private final int totpIntervalDurationSeconds; 097 098 // The number of digits to include in the generated TOTP password. 099 private final int totpNumDigits; 100 101 102 103 /** 104 * Creates a new SASL TOTP bind request with the provided information. 105 * 106 * @param authenticationID The authentication identity for the bind request. 107 * It must not be {@code null}, and must be in the 108 * form "u:" followed by a username, or "dn:" 109 * followed by a DN. 110 * @param authorizationID The authorization identity for the bind request. 111 * It may be {@code null} if the authorization 112 * identity should be the same as the authentication 113 * identity. If an authorization identity is 114 * specified, it must be in the form "u:" followed 115 * by a username, or "dn:" followed by a DN. The 116 * value "dn:" may indicate an authorization 117 * identity of the anonymous user. 118 * @param sharedSecret The shared secret key to use when generating the 119 * TOTP password. 120 * @param staticPassword The static password for the target user. It may 121 * be {@code null} if only the one-time password is 122 * to be used for authentication (which may or may 123 * not be allowed by the server). 124 * @param controls The set of controls to include in the bind 125 * request. 126 */ 127 public ReusableTOTPBindRequest(@NotNull final String authenticationID, 128 @Nullable final String authorizationID, 129 @NotNull final byte[] sharedSecret, 130 @Nullable final String staticPassword, 131 @Nullable final Control... controls) 132 { 133 this(authenticationID, authorizationID, sharedSecret, staticPassword, 134 OneTimePassword.DEFAULT_TOTP_INTERVAL_DURATION_SECONDS, 135 OneTimePassword.DEFAULT_TOTP_NUM_DIGITS, controls); 136 } 137 138 139 140 /** 141 * Creates a new SASL TOTP bind request with the provided information. 142 * 143 * @param authenticationID The authentication identity for the bind request. 144 * It must not be {@code null}, and must be in the 145 * form "u:" followed by a username, or "dn:" 146 * followed by a DN. 147 * @param authorizationID The authorization identity for the bind request. 148 * It may be {@code null} if the authorization 149 * identity should be the same as the authentication 150 * identity. If an authorization identity is 151 * specified, it must be in the form "u:" followed 152 * by a username, or "dn:" followed by a DN. The 153 * value "dn:" may indicate an authorization 154 * identity of the anonymous user. 155 * @param sharedSecret The shared secret key to use when generating the 156 * TOTP password. 157 * @param staticPassword The static password for the target user. It may 158 * be {@code null} if only the one-time password is 159 * to be used for authentication (which may or may 160 * not be allowed by the server). 161 * @param controls The set of controls to include in the bind 162 * request. 163 */ 164 public ReusableTOTPBindRequest(@NotNull final String authenticationID, 165 @Nullable final String authorizationID, 166 @NotNull final byte[] sharedSecret, 167 @Nullable final byte[] staticPassword, 168 @Nullable final Control... controls) 169 { 170 this(authenticationID, authorizationID, sharedSecret, staticPassword, 171 OneTimePassword.DEFAULT_TOTP_INTERVAL_DURATION_SECONDS, 172 OneTimePassword.DEFAULT_TOTP_NUM_DIGITS, controls); 173 } 174 175 176 177 /** 178 * Creates a new SASL TOTP bind request with the provided information. 179 * 180 * @param authenticationID The authentication identity for the 181 * bind request. It must not be 182 * {@code null}, and must be in the form 183 * "u:" followed by a username, or "dn:" 184 * followed by a DN. 185 * @param authorizationID The authorization identity for the 186 * bind request. It may be {@code null} 187 * if the authorization identity should 188 * be the same as the authentication 189 * identity. If an authorization 190 * identity is specified, it must be in 191 * the form "u:" followed by a username, 192 * or "dn:" followed by a DN. The value 193 * "dn:" may indicate an authorization 194 * identity of the anonymous user. 195 * @param sharedSecret The shared secret key to use when 196 * generating the TOTP password. 197 * @param staticPassword The static password for the target 198 * user. It may be {@code null} if only 199 * the one-time password is to be used 200 * for authentication (which may or may 201 * not be allowed by the server). 202 * @param totpIntervalDurationSeconds The duration (in seconds) of the time 203 * interval to use for TOTP processing. 204 * It must be greater than zero. 205 * @param totpNumDigits The number of digits to include in the 206 * generated TOTP password. It must be 207 * greater than or equal to six and less 208 * than or equal to eight. 209 * @param controls The set of controls to include in the 210 * bind request. 211 */ 212 public ReusableTOTPBindRequest(@NotNull final String authenticationID, 213 @Nullable final String authorizationID, 214 @NotNull final byte[] sharedSecret, 215 @Nullable final String staticPassword, 216 final int totpIntervalDurationSeconds, 217 final int totpNumDigits, 218 @Nullable final Control... controls) 219 { 220 super(authenticationID, authorizationID, staticPassword, controls); 221 222 Validator.ensureTrue(totpIntervalDurationSeconds > 0); 223 Validator.ensureTrue((totpNumDigits >= 6) && (totpNumDigits <= 8)); 224 225 this.sharedSecret = sharedSecret; 226 this.totpIntervalDurationSeconds = totpIntervalDurationSeconds; 227 this.totpNumDigits = totpNumDigits; 228 } 229 230 231 232 /** 233 * Creates a new SASL TOTP bind request with the provided information. 234 * 235 * @param authenticationID The authentication identity for the 236 * bind request. It must not be 237 * {@code null}, and must be in the form 238 * "u:" followed by a username, or "dn:" 239 * followed by a DN. 240 * @param authorizationID The authorization identity for the 241 * bind request. It may be {@code null} 242 * if the authorization identity should 243 * be the same as the authentication 244 * identity. If an authorization 245 * identity is specified, it must be in 246 * the form "u:" followed by a username, 247 * or "dn:" followed by a DN. The value 248 * "dn:" may indicate an authorization 249 * identity of the anonymous user. 250 * @param sharedSecret The shared secret key to use when 251 * generating the TOTP password. 252 * @param staticPassword The static password for the target 253 * user. It may be {@code null} if only 254 * the one-time password is to be used 255 * for authentication (which may or may 256 * not be allowed by the server). 257 * @param totpIntervalDurationSeconds The duration (in seconds) of the time 258 * interval to use for TOTP processing. 259 * It must be greater than zero. 260 * @param totpNumDigits The number of digits to include in the 261 * generated TOTP password. It must be 262 * greater than or equal to six and less 263 * than or equal to eight. 264 * @param controls The set of controls to include in the 265 * bind request. 266 */ 267 public ReusableTOTPBindRequest(@NotNull final String authenticationID, 268 @Nullable final String authorizationID, 269 @NotNull final byte[] sharedSecret, 270 @Nullable final byte[] staticPassword, 271 final int totpIntervalDurationSeconds, 272 final int totpNumDigits, 273 @Nullable final Control... controls) 274 { 275 super(authenticationID, authorizationID, staticPassword, controls); 276 277 Validator.ensureTrue(totpIntervalDurationSeconds > 0); 278 Validator.ensureTrue((totpNumDigits >= 6) && (totpNumDigits <= 8)); 279 280 this.sharedSecret = sharedSecret; 281 this.totpIntervalDurationSeconds = totpIntervalDurationSeconds; 282 this.totpNumDigits = totpNumDigits; 283 } 284 285 286 287 /** 288 * Creates a new SASL TOTP bind request with the provided information. 289 * 290 * @param authenticationID The authentication identity for the 291 * bind request. It must not be 292 * {@code null}, and must be in the form 293 * "u:" followed by a username, or "dn:" 294 * followed by a DN. 295 * @param authorizationID The authorization identity for the 296 * bind request. It may be {@code null} 297 * if the authorization identity should 298 * be the same as the authentication 299 * identity. If an authorization 300 * identity is specified, it must be in 301 * the form "u:" followed by a username, 302 * or "dn:" followed by a DN. The value 303 * "dn:" may indicate an authorization 304 * identity of the anonymous user. 305 * @param sharedSecret The shared secret key to use when 306 * generating the TOTP password. 307 * @param staticPassword The static password for the target 308 * user. It may be {@code null} if only 309 * the one-time password is to be used 310 * for authentication (which may or may 311 * not be allowed by the server). 312 * @param totpIntervalDurationSeconds The duration (in seconds) of the time 313 * interval to use when generating the 314 * TOTP password. It must be greater 315 * than zero. 316 * @param totpNumDigits The number of digits to include in the 317 * generated TOTP password. It must be 318 * greater than or equal to six and less 319 * than or equal to eight. 320 * @param controls The set of controls to include in the 321 * bind request. 322 */ 323 private ReusableTOTPBindRequest(@NotNull final String authenticationID, 324 @Nullable final String authorizationID, 325 @NotNull final byte[] sharedSecret, 326 @Nullable final ASN1OctetString staticPassword, 327 final int totpIntervalDurationSeconds, 328 final int totpNumDigits, 329 @Nullable final Control... controls) 330 { 331 super(authenticationID, authorizationID, staticPassword, controls); 332 333 this.sharedSecret = sharedSecret; 334 this.totpIntervalDurationSeconds = totpIntervalDurationSeconds; 335 this.totpNumDigits = totpNumDigits; 336 } 337 338 339 340 /** 341 * Retrieves the shared secret key to use when generating the TOTP password. 342 * 343 * @return The shared secret key to use when generating the TOTP password. 344 */ 345 @NotNull() 346 public byte[] getSharedSecret() 347 { 348 return sharedSecret; 349 } 350 351 352 353 /** 354 * Retrieves the duration (in seconds) of the time interval to use when 355 * generating the TOTP password. 356 * 357 * @return The duration (in seconds) of the time interval to use when 358 * generating the TOTP password. 359 */ 360 public int getTOTPIntervalDurationSeconds() 361 { 362 return totpIntervalDurationSeconds; 363 } 364 365 366 367 /** 368 * Retrieves the number of digits to include in the generated TOTP password. 369 * 370 * @return The number of digits to include in the generated TOTP password. 371 */ 372 public int getTOTPNumDigits() 373 { 374 return totpNumDigits; 375 } 376 377 378 379 /** 380 * {@inheritDoc} 381 */ 382 @Override() 383 @NotNull() 384 protected ASN1OctetString getSASLCredentials() 385 throws LDAPException 386 { 387 // Generate the TOTP password. 388 final String totpPassword = OneTimePassword.totp(sharedSecret, 389 System.currentTimeMillis(), totpIntervalDurationSeconds, 390 totpNumDigits); 391 392 return encodeCredentials(getAuthenticationID(), getAuthorizationID(), 393 totpPassword, getStaticPassword()); 394 } 395 396 397 398 /** 399 * {@inheritDoc} 400 */ 401 @Override() 402 @NotNull() 403 public ReusableTOTPBindRequest getRebindRequest(@NotNull final String host, 404 final int port) 405 { 406 return duplicate(); 407 } 408 409 410 411 /** 412 * {@inheritDoc} 413 */ 414 @Override() 415 @NotNull() 416 public ReusableTOTPBindRequest duplicate() 417 { 418 return duplicate(getControls()); 419 } 420 421 422 423 /** 424 * {@inheritDoc} 425 */ 426 @Override() 427 @NotNull() 428 public ReusableTOTPBindRequest duplicate(@Nullable final Control[] controls) 429 { 430 final ReusableTOTPBindRequest bindRequest = 431 new ReusableTOTPBindRequest(getAuthenticationID(), 432 getAuthorizationID(), sharedSecret, getStaticPassword(), 433 totpIntervalDurationSeconds, totpNumDigits, controls); 434 bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 435 bindRequest.setIntermediateResponseListener( 436 getIntermediateResponseListener()); 437 bindRequest.setReferralDepth(getReferralDepth()); 438 bindRequest.setReferralConnector(getReferralConnectorInternal()); 439 return bindRequest; 440 } 441 442 443 444 /** 445 * {@inheritDoc} 446 */ 447 @Override() 448 public void toCode(@NotNull final List<String> lineList, 449 @NotNull final String requestID, 450 final int indentSpaces, final boolean includeProcessing) 451 { 452 // Create the request variable. 453 final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(7); 454 constructorArgs.add(ToCodeArgHelper.createString(getAuthenticationID(), 455 "Authentication ID")); 456 constructorArgs.add(ToCodeArgHelper.createString(getAuthorizationID(), 457 "Authorization ID")); 458 constructorArgs.add(ToCodeArgHelper.createByteArray( 459 "---redacted-secret---".getBytes(StandardCharsets.UTF_8), true, 460 "Shared Secret")); 461 constructorArgs.add(ToCodeArgHelper.createString( 462 ((getStaticPassword() == null) ? "null" : "---redacted-password---"), 463 "Static Password")); 464 constructorArgs.add(ToCodeArgHelper.createInteger( 465 totpIntervalDurationSeconds, "Interval Duration (seconds)")); 466 constructorArgs.add(ToCodeArgHelper.createInteger(totpNumDigits, 467 "Number of TOTP Digits")); 468 469 final Control[] controls = getControls(); 470 if (controls.length > 0) 471 { 472 constructorArgs.add(ToCodeArgHelper.createControlArray(controls, 473 "Bind Controls")); 474 } 475 476 ToCodeHelper.generateMethodCall(lineList, indentSpaces, 477 "ReusableTOTPBindRequest", requestID + "Request", 478 "new ReusableTOTPBindRequest", constructorArgs); 479 480 481 // Add lines for processing the request and obtaining the result. 482 if (includeProcessing) 483 { 484 // Generate a string with the appropriate indent. 485 final StringBuilder buffer = new StringBuilder(); 486 for (int i=0; i < indentSpaces; i++) 487 { 488 buffer.append(' '); 489 } 490 final String indent = buffer.toString(); 491 492 lineList.add(""); 493 lineList.add(indent + "try"); 494 lineList.add(indent + '{'); 495 lineList.add(indent + " BindResult " + requestID + 496 "Result = connection.bind(" + requestID + "Request);"); 497 lineList.add(indent + " // The bind was processed successfully."); 498 lineList.add(indent + '}'); 499 lineList.add(indent + "catch (LDAPException e)"); 500 lineList.add(indent + '{'); 501 lineList.add(indent + " // The bind failed. Maybe the following will " + 502 "help explain why."); 503 lineList.add(indent + " // Note that the connection is now likely in " + 504 "an unauthenticated state."); 505 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 506 lineList.add(indent + " String message = e.getMessage();"); 507 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 508 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 509 lineList.add(indent + " Control[] responseControls = " + 510 "e.getResponseControls();"); 511 lineList.add(indent + '}'); 512 } 513 } 514}