001/* 002 * Copyright 2012-2022 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2012-2022 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-2022 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 messageID = InternalSDKHelper.nextMessageID(connection); 365 return sendBindRequest(connection, "", getSASLCredentials(), getControls(), 366 getResponseTimeoutMillis(connection)); 367 } 368 369 370 371 /** 372 * Retrieves the encoded SASL credentials that may be included in an 373 * UNBOUNDID-TOTP SASL bind request. 374 * 375 * @return The encoded SASL credentials that may be included in an 376 * UNBOUNDID-TOTP SASL bind request. 377 * 378 * @throws LDAPException If a problem is encountered while attempting to 379 * obtain the encoded credentials. 380 */ 381 @NotNull() 382 protected abstract ASN1OctetString getSASLCredentials() 383 throws LDAPException; 384 385 386 387 /** 388 * Encodes the provided information in a form suitable for inclusion in an 389 * UNBOUNDID-TOTP SASL bind request. 390 * 391 * @param authenticationID The authentication identity for the bind request. 392 * It must not be {@code null}, and must be in the 393 * form "u:" followed by a username, or "dn:" 394 * followed by a DN. 395 * @param authorizationID The authorization identity for the bind request. 396 * It may be {@code null} if the authorization 397 * identity should be the same as the authentication 398 * identity. If an authorization identity is 399 * specified, it must be in the form "u:" followed 400 * by a username, or "dn:" followed by a DN. The 401 * value "dn:" may indicate an authorization 402 * identity of the anonymous user. 403 * @param totpPassword The TOTP password to include in the bind request. 404 * It must not be {@code null}. 405 * @param staticPassword The static password for the target user. It may 406 * be {@code null} if only the one-time password is 407 * to be used for authentication (which may or may 408 * not be allowed by the server). 409 * 410 * @return The encoded SASL credentials. 411 */ 412 @NotNull() 413 public static ASN1OctetString encodeCredentials( 414 @NotNull final String authenticationID, 415 @Nullable final String authorizationID, 416 @NotNull final String totpPassword, 417 @Nullable final ASN1OctetString staticPassword) 418 { 419 Validator.ensureNotNull(authenticationID); 420 Validator.ensureNotNull(totpPassword); 421 422 final ArrayList<ASN1Element> elements = new ArrayList<>(4); 423 elements.add(new ASN1OctetString(TYPE_AUTHENTICATION_ID, authenticationID)); 424 425 if (authorizationID != null) 426 { 427 elements.add(new ASN1OctetString(TYPE_AUTHORIZATION_ID, authorizationID)); 428 } 429 430 elements.add(new ASN1OctetString(TYPE_TOTP_PASSWORD, totpPassword)); 431 432 if (staticPassword != null) 433 { 434 if (staticPassword.getType() == TYPE_STATIC_PASSWORD) 435 { 436 elements.add(staticPassword); 437 } 438 else 439 { 440 elements.add(new ASN1OctetString(TYPE_STATIC_PASSWORD, 441 staticPassword.getValue())); 442 } 443 } 444 445 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 446 } 447 448 449 450 /** 451 * {@inheritDoc} 452 */ 453 @Override() 454 public final int getLastMessageID() 455 { 456 return messageID; 457 } 458 459 460 461 /** 462 * {@inheritDoc} 463 */ 464 @Override() 465 public final void toString(@NotNull final StringBuilder buffer) 466 { 467 buffer.append("UnboundIDTOTPBindRequest(authID='"); 468 buffer.append(authenticationID); 469 buffer.append("', "); 470 471 if (authorizationID != null) 472 { 473 buffer.append("authzID='"); 474 buffer.append(authorizationID); 475 buffer.append("', "); 476 } 477 478 buffer.append("includesStaticPassword="); 479 buffer.append(staticPassword != null); 480 481 482 final Control[] controls = getControls(); 483 if (controls.length > 0) 484 { 485 buffer.append(", controls={"); 486 for (int i=0; i < controls.length; i++) 487 { 488 if (i > 0) 489 { 490 buffer.append(", "); 491 } 492 493 buffer.append(controls[i]); 494 } 495 buffer.append('}'); 496 } 497 498 buffer.append(')'); 499 } 500}