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; 041 042import com.unboundid.asn1.ASN1Element; 043import com.unboundid.asn1.ASN1OctetString; 044import com.unboundid.asn1.ASN1Sequence; 045import com.unboundid.ldap.sdk.BindResult; 046import com.unboundid.ldap.sdk.Control; 047import com.unboundid.ldap.sdk.InternalSDKHelper; 048import com.unboundid.ldap.sdk.LDAPConnection; 049import com.unboundid.ldap.sdk.LDAPException; 050import com.unboundid.ldap.sdk.SASLBindRequest; 051import com.unboundid.util.NotExtensible; 052import com.unboundid.util.NotNull; 053import com.unboundid.util.Nullable; 054import com.unboundid.util.ThreadSafety; 055import com.unboundid.util.ThreadSafetyLevel; 056import com.unboundid.util.Validator; 057 058 059 060/** 061 * This class provides support for an UnboundID-proprietary SASL mechanism that 062 * uses the time-based one-time password mechanism (TOTP) as described in 063 * <A HREF="http://www.ietf.org/rfc/rfc6238.txt">RFC 6238</A>, optionally (based 064 * on the server configuration) in conjunction with a static password for a form 065 * of multifactor authentication. 066 * <BR> 067 * <BLOCKQUOTE> 068 * <B>NOTE:</B> This class, and other classes within the 069 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 070 * supported for use against Ping Identity, UnboundID, and 071 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 072 * for proprietary functionality or for external specifications that are not 073 * considered stable or mature enough to be guaranteed to work in an 074 * interoperable way with other types of LDAP servers. 075 * </BLOCKQUOTE> 076 * <BR> 077 * The name for this SASL mechanism is "UNBOUNDID-TOTP". An UNBOUNDID-TOTP SASL 078 * bind request MUST include SASL credentials with the following ASN.1 encoding: 079 * <BR><BR> 080 * <PRE> 081 * UnboundIDTOTPCredentials ::= SEQUENCE { 082 * authenticationID [0] OCTET STRING, 083 * authorizationID [1] OCTET STRING OPTIONAL, 084 * totpPassword [2] OCTET STRING, 085 * staticPassword [3] OCTET STRING OPTIONAL } 086 * </PRE> 087 * <BR><BR> 088 * Note that this class is abstract, with two different concrete 089 * implementations: the {@link SingleUseTOTPBindRequest} class may be used for 090 * cases in which the one-time password will be obtained from an external source 091 * (e.g., provided by the user, perhaps using the Google Authenticator 092 * application), and the {@link ReusableTOTPBindRequest} class may be used for 093 * cases in which the one-time password should be generated by the LDAP SDK 094 * itself. Because the {@code SingleUseTOTPBindRequest} class contains a 095 * point-in-time password, it cannot be used for re-authentication (e.g., for 096 * use with a connection pool, following referrals, or with the auto-reconnect 097 * feature). If TOTP authentication should be used in contexts where one or 098 * more of these may be needed, then the dynamic variant should be used. 099 */ 100@NotExtensible() 101@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 102public abstract class UnboundIDTOTPBindRequest 103 extends SASLBindRequest 104{ 105 /** 106 * The name for the UnboundID TOTP SASL mechanism. 107 */ 108 @NotNull public static final String UNBOUNDID_TOTP_MECHANISM_NAME = 109 "UNBOUNDID-TOTP"; 110 111 112 113 /** 114 * The BER type for the authentication ID included in the request. 115 */ 116 static final byte TYPE_AUTHENTICATION_ID = (byte) 0x80; 117 118 119 120 /** 121 * The BER type for the authorization ID included in the request. 122 */ 123 static final byte TYPE_AUTHORIZATION_ID = (byte) 0x81; 124 125 126 127 /** 128 * The BER type for the TOTP password included in the request. 129 */ 130 static final byte TYPE_TOTP_PASSWORD = (byte) 0x82; 131 132 133 134 /** 135 * The BER type for the static password included in the request. 136 */ 137 static final byte TYPE_STATIC_PASSWORD = (byte) 0x83; 138 139 140 141 /** 142 * The serial version UID for this serializable class. 143 */ 144 private static final long serialVersionUID = -8751931123826994145L; 145 146 147 148 // The static password for the target user, if provided. 149 @Nullable private final ASN1OctetString staticPassword; 150 151 // The message ID from the last LDAP message sent from this request. 152 private volatile int messageID = -1; 153 154 // The authentication identity for the bind. 155 @NotNull private final String authenticationID; 156 157 // The authorization identity for the bind, if provided. 158 @Nullable private final String authorizationID; 159 160 161 162 /** 163 * Creates a new TOTP bind request with the provided information. 164 * 165 * @param authenticationID The authentication identity for the bind request. 166 * It must not be {@code null}, and must be in the 167 * form "u:" followed by a username, or "dn:" 168 * followed by a DN. 169 * @param authorizationID The authorization identity for the bind request. 170 * It may be {@code null} if the authorization 171 * identity should be the same as the authentication 172 * identity. If an authorization identity is 173 * specified, it must be in the form "u:" followed 174 * by a username, or "dn:" followed by a DN. The 175 * value "dn:" may indicate an authorization 176 * identity of the anonymous user. 177 * @param staticPassword The static password for the target user. It may 178 * be {@code null} if only the one-time password is 179 * to be used for authentication (which may or may 180 * not be allowed by the server). 181 * @param controls The set of controls to include in the bind 182 * request. 183 */ 184 protected UnboundIDTOTPBindRequest(@NotNull final String authenticationID, 185 @Nullable final String authorizationID, 186 @Nullable final String staticPassword, 187 @Nullable final Control... controls) 188 { 189 super(controls); 190 191 Validator.ensureNotNull(authenticationID); 192 193 this.authenticationID = authenticationID; 194 this.authorizationID = authorizationID; 195 196 if (staticPassword == null) 197 { 198 this.staticPassword = null; 199 } 200 else 201 { 202 this.staticPassword = 203 new ASN1OctetString(TYPE_STATIC_PASSWORD, staticPassword); 204 } 205 } 206 207 208 209 /** 210 * Creates a new TOTP bind request with the provided information. 211 * 212 * @param authenticationID The authentication identity for the bind request. 213 * It must not be {@code null}, and must be in the 214 * form "u:" followed by a username, or "dn:" 215 * followed by a DN. 216 * @param authorizationID The authorization identity for the bind request. 217 * It may be {@code null} if the authorization 218 * identity should be the same as the authentication 219 * identity. If an authorization identity is 220 * specified, it must be in the form "u:" followed 221 * by a username, or "dn:" followed by a DN. The 222 * value "dn:" may indicate an authorization 223 * identity of the anonymous user. 224 * @param staticPassword The static password for the target user. It may 225 * be {@code null} if only the one-time password is 226 * to be used for authentication (which may or may 227 * not be allowed by the server). 228 * @param controls The set of controls to include in the bind 229 * request. 230 */ 231 protected UnboundIDTOTPBindRequest(@NotNull final String authenticationID, 232 @Nullable final String authorizationID, 233 @Nullable final byte[] staticPassword, 234 @Nullable final Control... controls) 235 { 236 super(controls); 237 238 Validator.ensureNotNull(authenticationID); 239 240 this.authenticationID = authenticationID; 241 this.authorizationID = authorizationID; 242 243 if (staticPassword == null) 244 { 245 this.staticPassword = null; 246 } 247 else 248 { 249 this.staticPassword = 250 new ASN1OctetString(TYPE_STATIC_PASSWORD, staticPassword); 251 } 252 } 253 254 255 256 /** 257 * Creates a new TOTP bind request with the provided information. 258 * 259 * @param authenticationID The authentication identity for the bind request. 260 * It must not be {@code null}, and must be in the 261 * form "u:" followed by a username, or "dn:" 262 * followed by a DN. 263 * @param authorizationID The authorization identity for the bind request. 264 * It may be {@code null} if the authorization 265 * identity should be the same as the authentication 266 * identity. If an authorization identity is 267 * specified, it must be in the form "u:" followed 268 * by a username, or "dn:" followed by a DN. The 269 * value "dn:" may indicate an authorization 270 * identity of the anonymous user. 271 * @param staticPassword The static password for the target user. It may 272 * be {@code null} if only the one-time password is 273 * to be used for authentication (which may or may 274 * not be allowed by the server). If it is 275 * non-{@code null}, then it must have the 276 * appropriate BER type. 277 * @param controls The set of controls to include in the bind 278 * request. 279 */ 280 protected UnboundIDTOTPBindRequest(@NotNull final String authenticationID, 281 @Nullable final String authorizationID, 282 @Nullable final ASN1OctetString staticPassword, 283 @Nullable final Control... controls) 284 { 285 super(controls); 286 287 Validator.ensureNotNull(authenticationID); 288 289 if (staticPassword != null) 290 { 291 Validator.ensureTrue(staticPassword.getType() == TYPE_STATIC_PASSWORD); 292 } 293 294 this.authenticationID = authenticationID; 295 this.authorizationID = authorizationID; 296 this.staticPassword = staticPassword; 297 } 298 299 300 301 /** 302 * Retrieves the authentication ID for the bind request. 303 * 304 * @return The authentication ID for the bind request. 305 */ 306 @NotNull() 307 public final String getAuthenticationID() 308 { 309 return authenticationID; 310 } 311 312 313 314 /** 315 * Retrieves the authorization ID for the bind request, if one was provided. 316 * 317 * @return The authorization ID for the bind request, or {@code null} if the 318 * authorization ID should be the same as the authentication ID. 319 */ 320 @Nullable() 321 public final String getAuthorizationID() 322 { 323 return authorizationID; 324 } 325 326 327 328 /** 329 * Retrieves the static password for the bind request, if one was provided. 330 * 331 * @return The static password for the bind request, or {@code null} if no 332 * static password was provided and only the one-time password should 333 * be used for authentication. 334 */ 335 @Nullable() 336 public final ASN1OctetString getStaticPassword() 337 { 338 return staticPassword; 339 } 340 341 342 343 /** 344 * {@inheritDoc} 345 */ 346 @Override() 347 @NotNull() 348 public final String getSASLMechanismName() 349 { 350 return UNBOUNDID_TOTP_MECHANISM_NAME; 351 } 352 353 354 355 /** 356 * {@inheritDoc} 357 */ 358 @Override() 359 @NotNull() 360 protected final BindResult process(@NotNull final LDAPConnection connection, 361 final int depth) 362 throws LDAPException 363 { 364 setReferralDepth(depth); 365 366 messageID = InternalSDKHelper.nextMessageID(connection); 367 return sendBindRequest(connection, "", getSASLCredentials(), getControls(), 368 getResponseTimeoutMillis(connection)); 369 } 370 371 372 373 /** 374 * Retrieves the encoded SASL credentials that may be included in an 375 * UNBOUNDID-TOTP SASL bind request. 376 * 377 * @return The encoded SASL credentials that may be included in an 378 * UNBOUNDID-TOTP SASL bind request. 379 * 380 * @throws LDAPException If a problem is encountered while attempting to 381 * obtain the encoded credentials. 382 */ 383 @NotNull() 384 protected abstract ASN1OctetString getSASLCredentials() 385 throws LDAPException; 386 387 388 389 /** 390 * Encodes the provided information in a form suitable for inclusion in an 391 * UNBOUNDID-TOTP SASL bind request. 392 * 393 * @param authenticationID The authentication identity for the bind request. 394 * It must not be {@code null}, and must be in the 395 * form "u:" followed by a username, or "dn:" 396 * followed by a DN. 397 * @param authorizationID The authorization identity for the bind request. 398 * It may be {@code null} if the authorization 399 * identity should be the same as the authentication 400 * identity. If an authorization identity is 401 * specified, it must be in the form "u:" followed 402 * by a username, or "dn:" followed by a DN. The 403 * value "dn:" may indicate an authorization 404 * identity of the anonymous user. 405 * @param totpPassword The TOTP password to include in the bind request. 406 * It must not be {@code null}. 407 * @param staticPassword The static password for the target user. It may 408 * be {@code null} if only the one-time password is 409 * to be used for authentication (which may or may 410 * not be allowed by the server). 411 * 412 * @return The encoded SASL credentials. 413 */ 414 @NotNull() 415 public static ASN1OctetString encodeCredentials( 416 @NotNull final String authenticationID, 417 @Nullable final String authorizationID, 418 @NotNull final String totpPassword, 419 @Nullable final ASN1OctetString staticPassword) 420 { 421 Validator.ensureNotNull(authenticationID); 422 Validator.ensureNotNull(totpPassword); 423 424 final ArrayList<ASN1Element> elements = new ArrayList<>(4); 425 elements.add(new ASN1OctetString(TYPE_AUTHENTICATION_ID, authenticationID)); 426 427 if (authorizationID != null) 428 { 429 elements.add(new ASN1OctetString(TYPE_AUTHORIZATION_ID, authorizationID)); 430 } 431 432 elements.add(new ASN1OctetString(TYPE_TOTP_PASSWORD, totpPassword)); 433 434 if (staticPassword != null) 435 { 436 if (staticPassword.getType() == TYPE_STATIC_PASSWORD) 437 { 438 elements.add(staticPassword); 439 } 440 else 441 { 442 elements.add(new ASN1OctetString(TYPE_STATIC_PASSWORD, 443 staticPassword.getValue())); 444 } 445 } 446 447 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 448 } 449 450 451 452 /** 453 * {@inheritDoc} 454 */ 455 @Override() 456 public final int getLastMessageID() 457 { 458 return messageID; 459 } 460 461 462 463 /** 464 * {@inheritDoc} 465 */ 466 @Override() 467 public final void toString(@NotNull final StringBuilder buffer) 468 { 469 buffer.append("UnboundIDTOTPBindRequest(authID='"); 470 buffer.append(authenticationID); 471 buffer.append("', "); 472 473 if (authorizationID != null) 474 { 475 buffer.append("authzID='"); 476 buffer.append(authorizationID); 477 buffer.append("', "); 478 } 479 480 buffer.append("includesStaticPassword="); 481 buffer.append(staticPassword != null); 482 483 484 final Control[] controls = getControls(); 485 if (controls.length > 0) 486 { 487 buffer.append(", controls={"); 488 for (int i=0; i < controls.length; i++) 489 { 490 if (i > 0) 491 { 492 buffer.append(", "); 493 } 494 495 buffer.append(controls[i]); 496 } 497 buffer.append('}'); 498 } 499 500 buffer.append(')'); 501 } 502}