001/* 002 * Copyright 2016-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2018 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.unboundidds; 022 023 024 025import java.util.ArrayList; 026 027import com.unboundid.asn1.ASN1Element; 028import com.unboundid.asn1.ASN1OctetString; 029import com.unboundid.asn1.ASN1Sequence; 030import com.unboundid.ldap.sdk.BindResult; 031import com.unboundid.ldap.sdk.Control; 032import com.unboundid.ldap.sdk.InternalSDKHelper; 033import com.unboundid.ldap.sdk.LDAPConnection; 034import com.unboundid.ldap.sdk.LDAPException; 035import com.unboundid.ldap.sdk.ResultCode; 036import com.unboundid.ldap.sdk.SASLBindRequest; 037import com.unboundid.ldap.sdk.unboundidds.extensions. 038 DeregisterYubiKeyOTPDeviceExtendedRequest; 039import com.unboundid.ldap.sdk.unboundidds.extensions. 040 RegisterYubiKeyOTPDeviceExtendedRequest; 041import com.unboundid.util.Debug; 042import com.unboundid.util.NotMutable; 043import com.unboundid.util.StaticUtils; 044import com.unboundid.util.ThreadSafety; 045import com.unboundid.util.ThreadSafetyLevel; 046import com.unboundid.util.Validator; 047 048import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*; 049 050 051 052/** 053 * This class provides an implementation of a SASL bind request that may be used 054 * to authenticate to a Directory Server using the UNBOUNDID-YUBIKEY-OTP 055 * mechanism. The credentials include at least an authentication ID and a 056 * one-time password generated by a YubiKey device. The request may also 057 * include a static password (which may or may not be required by the server) 058 * and an optional authorization ID. 059 * <BR> 060 * <BLOCKQUOTE> 061 * <B>NOTE:</B> This class, and other classes within the 062 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 063 * supported for use against Ping Identity, UnboundID, and Alcatel-Lucent 8661 064 * server products. These classes provide support for proprietary 065 * functionality or for external specifications that are not considered stable 066 * or mature enough to be guaranteed to work in an interoperable way with 067 * other types of LDAP servers. 068 * </BLOCKQUOTE> 069 * <BR> 070 * The UNBOUNDID-YUBIKEY-OTP bind request MUST include SASL credentials with the 071 * following ASN.1 encoding: 072 * <BR><BR> 073 * <PRE> 074 * UnboundIDYubiKeyCredentials ::= SEQUENCE { 075 * authenticationID [0] OCTET STRING, 076 * authorizationID [1] OCTET STRING OPTIONAL, 077 * staticPassword [2] OCTET STRING OPTIONAL, 078 * yubiKeyOTP [3] OCTET STRING, 079 * ... } 080 * </PRE> 081 * 082 * 083 * @see RegisterYubiKeyOTPDeviceExtendedRequest 084 * @see DeregisterYubiKeyOTPDeviceExtendedRequest 085 */ 086@NotMutable() 087@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 088public final class UnboundIDYubiKeyOTPBindRequest 089 extends SASLBindRequest 090{ 091 /** 092 * The name for the UnboundID YubiKey SASL mechanism. 093 */ 094 public static final String UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME = 095 "UNBOUNDID-YUBIKEY-OTP"; 096 097 098 099 /** 100 * The BER type for the authentication ID element of the credentials sequence. 101 */ 102 private static final byte TYPE_AUTHENTICATION_ID = (byte) 0x80; 103 104 105 106 /** 107 * The BER type for the authorization ID element of the credentials sequence. 108 */ 109 private static final byte TYPE_AUTHORIZATION_ID = (byte) 0x81; 110 111 112 113 /** 114 * The BER type for the static password element of the credentials sequence. 115 */ 116 private static final byte TYPE_STATIC_PASSWORD = (byte) 0x82; 117 118 119 120 /** 121 * The BER type for the YubiKey OTP element of the credentials sequence. 122 */ 123 private static final byte TYPE_YUBIKEY_OTP = (byte) 0x83; 124 125 126 127 /** 128 * The serial version UID for this serializable class. 129 */ 130 private static final long serialVersionUID = -6124016046606933247L; 131 132 133 134 // This is an ugly hack to prevent checkstyle from complaining about the 135 // imports for classes only referenced in the javadoc. Checkstyle apparently 136 // doesn't recognize that so we just need to use it in some way in this class 137 // to placate checkstyle. 138 static 139 { 140 final RegisterYubiKeyOTPDeviceExtendedRequest rr = null; 141 final DeregisterYubiKeyOTPDeviceExtendedRequest dr = null; 142 } 143 144 145 146 // The static password for the user, if provided. 147 private final ASN1OctetString staticPassword; 148 149 // The message ID from the last LDAP message sent from this request. 150 private volatile int messageID = -1; 151 152 // The authentication ID for the user. 153 private final String authenticationID; 154 155 // The authorization ID for the bind request, if provided. 156 private final String authorizationID; 157 158 // The one-time password generated by a YubiKey device. 159 private final String yubiKeyOTP; 160 161 162 163 /** 164 * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided 165 * information. 166 * 167 * @param authenticationID The authentication ID for the bind request. It 168 * must not be {@code null}, and must have the form 169 * "dn:" followed by the DN of the target user or 170 * "u:" followed by the the username of the target 171 * user. 172 * @param authorizationID The authorization ID for the bind request. It 173 * may be {@code null} if the authorization identity 174 * should be the same as the authentication 175 * identity. 176 * @param staticPassword The static password for the user specified as the 177 * authentication identity. It may be {@code null} 178 * if authentication should be performed using only 179 * the YubiKey OTP. 180 * @param yubiKeyOTP The one-time password generated by the YubiKey 181 * device. It must not be {@code null}. 182 * @param controls The set of controls to include in the bind 183 * request. It may be {@code null} or empty if 184 * there should not be any request controls. 185 */ 186 public UnboundIDYubiKeyOTPBindRequest(final String authenticationID, 187 final String authorizationID, 188 final String staticPassword, 189 final String yubiKeyOTP, 190 final Control... controls) 191 { 192 this(authenticationID, authorizationID, toASN1OctetString(staticPassword), 193 yubiKeyOTP, controls); 194 } 195 196 197 198 /** 199 * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided 200 * information. 201 * 202 * @param authenticationID The authentication ID for the bind request. It 203 * must not be {@code null}, and must have the form 204 * "dn:" followed by the DN of the target user or 205 * "u:" followed by the the username of the target 206 * user. 207 * @param authorizationID The authorization ID for the bind request. It 208 * may be {@code null} if the authorization identity 209 * should be the same as the authentication 210 * identity. 211 * @param staticPassword The static password for the user specified as the 212 * authentication identity. It may be {@code null} 213 * if authentication should be performed using only 214 * the YubiKey OTP. 215 * @param yubiKeyOTP The one-time password generated by the YubiKey 216 * device. It must not be {@code null}. 217 * @param controls The set of controls to include in the bind 218 * request. It may be {@code null} or empty if 219 * there should not be any request controls. 220 */ 221 public UnboundIDYubiKeyOTPBindRequest(final String authenticationID, 222 final String authorizationID, 223 final byte[] staticPassword, 224 final String yubiKeyOTP, 225 final Control... controls) 226 { 227 this(authenticationID, authorizationID, toASN1OctetString(staticPassword), 228 yubiKeyOTP, controls); 229 } 230 231 232 233 /** 234 * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided 235 * information. 236 * 237 * @param authenticationID The authentication ID for the bind request. It 238 * must not be {@code null}, and must have the form 239 * "dn:" followed by the DN of the target user or 240 * "u:" followed by the the username of the target 241 * user. 242 * @param authorizationID The authorization ID for the bind request. It 243 * may be {@code null} if the authorization identity 244 * should be the same as the authentication 245 * identity. 246 * @param staticPassword The static password for the user specified as the 247 * authentication identity. It may be {@code null} 248 * if authentication should be performed using only 249 * the YubiKey OTP. 250 * @param yubiKeyOTP The one-time password generated by the YubiKey 251 * device. It must not be {@code null}. 252 * @param controls The set of controls to include in the bind 253 * request. It may be {@code null} or empty if 254 * there should not be any request controls. 255 */ 256 private UnboundIDYubiKeyOTPBindRequest(final String authenticationID, 257 final String authorizationID, 258 final ASN1OctetString staticPassword, 259 final String yubiKeyOTP, 260 final Control... controls) 261 { 262 super(controls); 263 264 Validator.ensureNotNull(authenticationID); 265 Validator.ensureNotNull(yubiKeyOTP); 266 267 this.authenticationID = authenticationID; 268 this.authorizationID = authorizationID; 269 this.staticPassword = staticPassword; 270 this.yubiKeyOTP = yubiKeyOTP; 271 } 272 273 274 275 /** 276 * Retrieves an ASN.1 octet string that represents the appropriate encoding 277 * for the provided password. 278 * 279 * @param password The password object to convert to an ASN.1 octet string. 280 * It may be {@code null} if no static password is required. 281 * Otherwise, it must either be a string or a byte array. 282 * 283 * @return The ASN.1 octet string created from the provided password object, 284 * or {@code null} if the provided password object was null. 285 */ 286 private static ASN1OctetString toASN1OctetString(final Object password) 287 { 288 if (password == null) 289 { 290 return null; 291 } 292 else if (password instanceof byte[]) 293 { 294 return new ASN1OctetString(TYPE_STATIC_PASSWORD, (byte[]) password); 295 } 296 else 297 { 298 return new ASN1OctetString(TYPE_STATIC_PASSWORD, 299 String.valueOf(password)); 300 } 301 } 302 303 304 305 /** 306 * Creates a new UNBOUNDID-YUBIKEY-OTP SASL bind request decoded from the 307 * provided SASL credentials. 308 * 309 * @param saslCredentials The SASL credentials to decode in order to create 310 * the UNBOUNDID-YUBIKEY-OTP SASL bind request. It 311 * must not be {@code null}. 312 * @param controls The set of controls to include in the bind 313 * request. This may be {@code null} or empty if no 314 * controls should be included in the request. 315 * 316 * @return The UNBOUNDID-YUBIKEY-OTP SASL bind request decoded from the 317 * provided credentials. 318 * 319 * @throws LDAPException If the provided credentials cannot be decoded to a 320 * valid UNBOUNDID-YUBIKEY-OTP bind request. 321 */ 322 public static UnboundIDYubiKeyOTPBindRequest decodeCredentials( 323 final ASN1OctetString saslCredentials, 324 final Control... controls) 325 throws LDAPException 326 { 327 try 328 { 329 ASN1OctetString staticPassword = null; 330 String authenticationID = null; 331 String authorizationID = null; 332 String yubiKeyOTP = null; 333 334 for (final ASN1Element e : 335 ASN1Sequence.decodeAsSequence(saslCredentials.getValue()).elements()) 336 { 337 switch (e.getType()) 338 { 339 case TYPE_AUTHENTICATION_ID: 340 authenticationID = 341 ASN1OctetString.decodeAsOctetString(e).stringValue(); 342 break; 343 case TYPE_AUTHORIZATION_ID: 344 authorizationID = 345 ASN1OctetString.decodeAsOctetString(e).stringValue(); 346 break; 347 case TYPE_STATIC_PASSWORD: 348 staticPassword = ASN1OctetString.decodeAsOctetString(e); 349 break; 350 case TYPE_YUBIKEY_OTP: 351 yubiKeyOTP = ASN1OctetString.decodeAsOctetString(e).stringValue(); 352 break; 353 default: 354 throw new LDAPException(ResultCode.DECODING_ERROR, 355 ERR_YUBIKEY_OTP_DECODE_UNRECOGNIZED_CRED_ELEMENT.get( 356 UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME, 357 StaticUtils.toHex(e.getType()))); 358 } 359 } 360 361 if (authenticationID == null) 362 { 363 throw new LDAPException(ResultCode.DECODING_ERROR, 364 ERR_YUBIKEY_OTP_DECODE_NO_AUTH_ID.get( 365 UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME)); 366 } 367 368 if (yubiKeyOTP == null) 369 { 370 throw new LDAPException(ResultCode.DECODING_ERROR, 371 ERR_YUBIKEY_OTP_NO_OTP.get(UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME)); 372 } 373 374 return new UnboundIDYubiKeyOTPBindRequest(authenticationID, 375 authorizationID, staticPassword, yubiKeyOTP, controls); 376 } 377 catch (final LDAPException le) 378 { 379 Debug.debugException(le); 380 throw le; 381 } 382 catch (final Exception e) 383 { 384 Debug.debugException(e); 385 throw new LDAPException(ResultCode.DECODING_ERROR, 386 ERR_YUBIKEY_OTP_DECODE_ERROR.get( 387 UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME, 388 StaticUtils.getExceptionMessage(e)), 389 e); 390 } 391 } 392 393 394 395 /** 396 * Retrieves the authentication ID for the bind request. 397 * 398 * @return The authentication ID for the bind request. 399 */ 400 public String getAuthenticationID() 401 { 402 return authenticationID; 403 } 404 405 406 407 /** 408 * Retrieves the authorization ID for the bind request, if any. 409 * 410 * @return The authorization ID for the bind request, or {@code null} if the 411 * authorization identity should match the authentication identity. 412 */ 413 public String getAuthorizationID() 414 { 415 return authorizationID; 416 } 417 418 419 420 /** 421 * Retrieves the string representation of the static password for the bind 422 * request, if any. 423 * 424 * @return The string representation of the static password for the bind 425 * request, or {@code null} if there is no static password. 426 */ 427 public String getStaticPasswordString() 428 { 429 if (staticPassword == null) 430 { 431 return null; 432 } 433 else 434 { 435 return staticPassword.stringValue(); 436 } 437 } 438 439 440 441 /** 442 * Retrieves the bytes that comprise the static password for the bind request, 443 * if any. 444 * 445 * @return The bytes that comprise the static password for the bind request, 446 * or {@code null} if there is no static password. 447 */ 448 public byte[] getStaticPasswordBytes() 449 { 450 if (staticPassword == null) 451 { 452 return null; 453 } 454 else 455 { 456 return staticPassword.getValue(); 457 } 458 } 459 460 461 462 /** 463 * Retrieves the YubiKey-generated one-time password to include in the bind 464 * request. 465 * 466 * @return The YubiKey-generated one-time password to include in the bind 467 * request. 468 */ 469 public String getYubiKeyOTP() 470 { 471 return yubiKeyOTP; 472 } 473 474 475 476 /** 477 * Sends this bind request to the target server over the provided connection 478 * and returns the corresponding response. 479 * 480 * @param connection The connection to use to send this bind request to the 481 * server and read the associated response. 482 * @param depth The current referral depth for this request. It should 483 * always be one for the initial request, and should only 484 * be incremented when following referrals. 485 * 486 * @return The bind response read from the server. 487 * 488 * @throws LDAPException If a problem occurs while sending the request or 489 * reading the response. 490 */ 491 @Override() 492 protected BindResult process(final LDAPConnection connection, final int depth) 493 throws LDAPException 494 { 495 messageID = InternalSDKHelper.nextMessageID(connection); 496 return sendBindRequest(connection, "", encodeCredentials(), getControls(), 497 getResponseTimeoutMillis(connection)); 498 } 499 500 501 502 /** 503 * Retrieves an ASN.1 octet string containing the encoded credentials for this 504 * bind request. 505 * 506 * @return An ASN.1 octet string containing the encoded credentials for this 507 * bind request. 508 */ 509 public ASN1OctetString encodeCredentials() 510 { 511 return encodeCredentials(authenticationID, authorizationID, staticPassword, 512 yubiKeyOTP); 513 } 514 515 516 517 /** 518 * Encodes the provided information into an ASN.1 octet string suitable for 519 * use as the SASL credentials for an UNBOUNDID-YUBIKEY-OTP bind request. 520 * 521 * @param authenticationID The authentication ID for the bind request. It 522 * must not be {@code null}, and must have the form 523 * "dn:" followed by the DN of the target user or 524 * "u:" followed by the the username of the target 525 * user. 526 * @param authorizationID The authorization ID for the bind request. It 527 * may be {@code null} if the authorization identity 528 * should be the same as the authentication 529 * identity. 530 * @param staticPassword The static password for the user specified as the 531 * authentication identity. It may be {@code null} 532 * if authentication should be performed using only 533 * the YubiKey OTP. 534 * @param yubiKeyOTP The one-time password generated by the YubiKey 535 * device. It must not be {@code null}. 536 * 537 * @return An ASN.1 octet string suitable for use as the SASL credentials for 538 * an UNBOUNDID-YUBIKEY-OTP bind request. 539 */ 540 public static ASN1OctetString encodeCredentials(final String authenticationID, 541 final String authorizationID, 542 final ASN1OctetString staticPassword, 543 final String yubiKeyOTP) 544 { 545 Validator.ensureNotNull(authenticationID); 546 Validator.ensureNotNull(yubiKeyOTP); 547 548 final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(4); 549 elements.add(new ASN1OctetString(TYPE_AUTHENTICATION_ID, authenticationID)); 550 551 if (authorizationID != null) 552 { 553 elements.add(new ASN1OctetString(TYPE_AUTHORIZATION_ID, authorizationID)); 554 } 555 556 if (staticPassword != null) 557 { 558 elements.add(new ASN1OctetString(TYPE_STATIC_PASSWORD, 559 staticPassword.getValue())); 560 } 561 562 elements.add(new ASN1OctetString(TYPE_YUBIKEY_OTP, yubiKeyOTP)); 563 564 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 565 } 566 567 568 569 /** 570 * {@inheritDoc} 571 */ 572 @Override() 573 public UnboundIDYubiKeyOTPBindRequest duplicate() 574 { 575 return duplicate(getControls()); 576 } 577 578 579 580 /** 581 * {@inheritDoc} 582 */ 583 @Override() 584 public UnboundIDYubiKeyOTPBindRequest duplicate(final Control[] controls) 585 { 586 final UnboundIDYubiKeyOTPBindRequest bindRequest = 587 new UnboundIDYubiKeyOTPBindRequest(authenticationID, authorizationID, 588 staticPassword, yubiKeyOTP, controls); 589 bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 590 return bindRequest; 591 } 592 593 594 595 /** 596 * {@inheritDoc} 597 */ 598 @Override() 599 public String getSASLMechanismName() 600 { 601 return UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME; 602 } 603 604 605 606 /** 607 * {@inheritDoc} 608 */ 609 @Override() 610 public int getLastMessageID() 611 { 612 return messageID; 613 } 614 615 616 617 /** 618 * {@inheritDoc} 619 */ 620 @Override() 621 public void toString(final StringBuilder buffer) 622 { 623 buffer.append("UnboundYubiKeyOTPBindRequest(authenticationID='"); 624 buffer.append(authenticationID); 625 626 if (authorizationID != null) 627 { 628 buffer.append("', authorizationID='"); 629 buffer.append(authorizationID); 630 } 631 632 buffer.append("', staticPasswordProvided="); 633 buffer.append(staticPassword != null); 634 635 final Control[] controls = getControls(); 636 if (controls.length > 0) 637 { 638 buffer.append(", controls={"); 639 for (int i=0; i < controls.length; i++) 640 { 641 if (i > 0) 642 { 643 buffer.append(", "); 644 } 645 646 buffer.append(controls[i]); 647 } 648 buffer.append('}'); 649 } 650 651 buffer.append(')'); 652 } 653}