001/* 002 * Copyright 2016-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2019 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 064 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 065 * for proprietary functionality or for external specifications that are not 066 * considered stable or mature enough to be guaranteed to work in an 067 * interoperable way with 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 // The static password for the user, if provided. 135 private final ASN1OctetString staticPassword; 136 137 // The message ID from the last LDAP message sent from this request. 138 private volatile int messageID = -1; 139 140 // The authentication ID for the user. 141 private final String authenticationID; 142 143 // The authorization ID for the bind request, if provided. 144 private final String authorizationID; 145 146 // The one-time password generated by a YubiKey device. 147 private final String yubiKeyOTP; 148 149 150 151 /** 152 * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided 153 * information. 154 * 155 * @param authenticationID The authentication ID for the bind request. It 156 * must not be {@code null}, and must have the form 157 * "dn:" followed by the DN of the target user or 158 * "u:" followed by the the username of the target 159 * user. 160 * @param authorizationID The authorization ID for the bind request. It 161 * may be {@code null} if the authorization identity 162 * should be the same as the authentication 163 * identity. 164 * @param staticPassword The static password for the user specified as the 165 * authentication identity. It may be {@code null} 166 * if authentication should be performed using only 167 * the YubiKey OTP. 168 * @param yubiKeyOTP The one-time password generated by the YubiKey 169 * device. It must not be {@code null}. 170 * @param controls The set of controls to include in the bind 171 * request. It may be {@code null} or empty if 172 * there should not be any request controls. 173 */ 174 public UnboundIDYubiKeyOTPBindRequest(final String authenticationID, 175 final String authorizationID, 176 final String staticPassword, 177 final String yubiKeyOTP, 178 final Control... controls) 179 { 180 this(authenticationID, authorizationID, toASN1OctetString(staticPassword), 181 yubiKeyOTP, controls); 182 } 183 184 185 186 /** 187 * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided 188 * information. 189 * 190 * @param authenticationID The authentication ID for the bind request. It 191 * must not be {@code null}, and must have the form 192 * "dn:" followed by the DN of the target user or 193 * "u:" followed by the the username of the target 194 * user. 195 * @param authorizationID The authorization ID for the bind request. It 196 * may be {@code null} if the authorization identity 197 * should be the same as the authentication 198 * identity. 199 * @param staticPassword The static password for the user specified as the 200 * authentication identity. It may be {@code null} 201 * if authentication should be performed using only 202 * the YubiKey OTP. 203 * @param yubiKeyOTP The one-time password generated by the YubiKey 204 * device. It must not be {@code null}. 205 * @param controls The set of controls to include in the bind 206 * request. It may be {@code null} or empty if 207 * there should not be any request controls. 208 */ 209 public UnboundIDYubiKeyOTPBindRequest(final String authenticationID, 210 final String authorizationID, 211 final byte[] staticPassword, 212 final String yubiKeyOTP, 213 final Control... controls) 214 { 215 this(authenticationID, authorizationID, toASN1OctetString(staticPassword), 216 yubiKeyOTP, controls); 217 } 218 219 220 221 /** 222 * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided 223 * information. 224 * 225 * @param authenticationID The authentication ID for the bind request. It 226 * must not be {@code null}, and must have the form 227 * "dn:" followed by the DN of the target user or 228 * "u:" followed by the the username of the target 229 * user. 230 * @param authorizationID The authorization ID for the bind request. It 231 * may be {@code null} if the authorization identity 232 * should be the same as the authentication 233 * identity. 234 * @param staticPassword The static password for the user specified as the 235 * authentication identity. It may be {@code null} 236 * if authentication should be performed using only 237 * the YubiKey OTP. 238 * @param yubiKeyOTP The one-time password generated by the YubiKey 239 * device. It must not be {@code null}. 240 * @param controls The set of controls to include in the bind 241 * request. It may be {@code null} or empty if 242 * there should not be any request controls. 243 */ 244 private UnboundIDYubiKeyOTPBindRequest(final String authenticationID, 245 final String authorizationID, 246 final ASN1OctetString staticPassword, 247 final String yubiKeyOTP, 248 final Control... controls) 249 { 250 super(controls); 251 252 Validator.ensureNotNull(authenticationID); 253 Validator.ensureNotNull(yubiKeyOTP); 254 255 this.authenticationID = authenticationID; 256 this.authorizationID = authorizationID; 257 this.staticPassword = staticPassword; 258 this.yubiKeyOTP = yubiKeyOTP; 259 } 260 261 262 263 /** 264 * Retrieves an ASN.1 octet string that represents the appropriate encoding 265 * for the provided password. 266 * 267 * @param password The password object to convert to an ASN.1 octet string. 268 * It may be {@code null} if no static password is required. 269 * Otherwise, it must either be a string or a byte array. 270 * 271 * @return The ASN.1 octet string created from the provided password object, 272 * or {@code null} if the provided password object was null. 273 */ 274 private static ASN1OctetString toASN1OctetString(final Object password) 275 { 276 if (password == null) 277 { 278 return null; 279 } 280 else if (password instanceof byte[]) 281 { 282 return new ASN1OctetString(TYPE_STATIC_PASSWORD, (byte[]) password); 283 } 284 else 285 { 286 return new ASN1OctetString(TYPE_STATIC_PASSWORD, 287 String.valueOf(password)); 288 } 289 } 290 291 292 293 /** 294 * Creates a new UNBOUNDID-YUBIKEY-OTP SASL bind request decoded from the 295 * provided SASL credentials. 296 * 297 * @param saslCredentials The SASL credentials to decode in order to create 298 * the UNBOUNDID-YUBIKEY-OTP SASL bind request. It 299 * must not be {@code null}. 300 * @param controls The set of controls to include in the bind 301 * request. This may be {@code null} or empty if no 302 * controls should be included in the request. 303 * 304 * @return The UNBOUNDID-YUBIKEY-OTP SASL bind request decoded from the 305 * provided credentials. 306 * 307 * @throws LDAPException If the provided credentials cannot be decoded to a 308 * valid UNBOUNDID-YUBIKEY-OTP bind request. 309 */ 310 public static UnboundIDYubiKeyOTPBindRequest decodeCredentials( 311 final ASN1OctetString saslCredentials, 312 final Control... controls) 313 throws LDAPException 314 { 315 try 316 { 317 ASN1OctetString staticPassword = null; 318 String authenticationID = null; 319 String authorizationID = null; 320 String yubiKeyOTP = null; 321 322 for (final ASN1Element e : 323 ASN1Sequence.decodeAsSequence(saslCredentials.getValue()).elements()) 324 { 325 switch (e.getType()) 326 { 327 case TYPE_AUTHENTICATION_ID: 328 authenticationID = 329 ASN1OctetString.decodeAsOctetString(e).stringValue(); 330 break; 331 case TYPE_AUTHORIZATION_ID: 332 authorizationID = 333 ASN1OctetString.decodeAsOctetString(e).stringValue(); 334 break; 335 case TYPE_STATIC_PASSWORD: 336 staticPassword = ASN1OctetString.decodeAsOctetString(e); 337 break; 338 case TYPE_YUBIKEY_OTP: 339 yubiKeyOTP = ASN1OctetString.decodeAsOctetString(e).stringValue(); 340 break; 341 default: 342 throw new LDAPException(ResultCode.DECODING_ERROR, 343 ERR_YUBIKEY_OTP_DECODE_UNRECOGNIZED_CRED_ELEMENT.get( 344 UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME, 345 StaticUtils.toHex(e.getType()))); 346 } 347 } 348 349 if (authenticationID == null) 350 { 351 throw new LDAPException(ResultCode.DECODING_ERROR, 352 ERR_YUBIKEY_OTP_DECODE_NO_AUTH_ID.get( 353 UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME)); 354 } 355 356 if (yubiKeyOTP == null) 357 { 358 throw new LDAPException(ResultCode.DECODING_ERROR, 359 ERR_YUBIKEY_OTP_NO_OTP.get(UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME)); 360 } 361 362 return new UnboundIDYubiKeyOTPBindRequest(authenticationID, 363 authorizationID, staticPassword, yubiKeyOTP, controls); 364 } 365 catch (final LDAPException le) 366 { 367 Debug.debugException(le); 368 throw le; 369 } 370 catch (final Exception e) 371 { 372 Debug.debugException(e); 373 throw new LDAPException(ResultCode.DECODING_ERROR, 374 ERR_YUBIKEY_OTP_DECODE_ERROR.get( 375 UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME, 376 StaticUtils.getExceptionMessage(e)), 377 e); 378 } 379 } 380 381 382 383 /** 384 * Retrieves the authentication ID for the bind request. 385 * 386 * @return The authentication ID for the bind request. 387 */ 388 public String getAuthenticationID() 389 { 390 return authenticationID; 391 } 392 393 394 395 /** 396 * Retrieves the authorization ID for the bind request, if any. 397 * 398 * @return The authorization ID for the bind request, or {@code null} if the 399 * authorization identity should match the authentication identity. 400 */ 401 public String getAuthorizationID() 402 { 403 return authorizationID; 404 } 405 406 407 408 /** 409 * Retrieves the string representation of the static password for the bind 410 * request, if any. 411 * 412 * @return The string representation of the static password for the bind 413 * request, or {@code null} if there is no static password. 414 */ 415 public String getStaticPasswordString() 416 { 417 if (staticPassword == null) 418 { 419 return null; 420 } 421 else 422 { 423 return staticPassword.stringValue(); 424 } 425 } 426 427 428 429 /** 430 * Retrieves the bytes that comprise the static password for the bind request, 431 * if any. 432 * 433 * @return The bytes that comprise the static password for the bind request, 434 * or {@code null} if there is no static password. 435 */ 436 public byte[] getStaticPasswordBytes() 437 { 438 if (staticPassword == null) 439 { 440 return null; 441 } 442 else 443 { 444 return staticPassword.getValue(); 445 } 446 } 447 448 449 450 /** 451 * Retrieves the YubiKey-generated one-time password to include in the bind 452 * request. 453 * 454 * @return The YubiKey-generated one-time password to include in the bind 455 * request. 456 */ 457 public String getYubiKeyOTP() 458 { 459 return yubiKeyOTP; 460 } 461 462 463 464 /** 465 * Sends this bind request to the target server over the provided connection 466 * and returns the corresponding response. 467 * 468 * @param connection The connection to use to send this bind request to the 469 * server and read the associated response. 470 * @param depth The current referral depth for this request. It should 471 * always be one for the initial request, and should only 472 * be incremented when following referrals. 473 * 474 * @return The bind response read from the server. 475 * 476 * @throws LDAPException If a problem occurs while sending the request or 477 * reading the response. 478 */ 479 @Override() 480 protected BindResult process(final LDAPConnection connection, final int depth) 481 throws LDAPException 482 { 483 messageID = InternalSDKHelper.nextMessageID(connection); 484 return sendBindRequest(connection, "", encodeCredentials(), getControls(), 485 getResponseTimeoutMillis(connection)); 486 } 487 488 489 490 /** 491 * Retrieves an ASN.1 octet string containing the encoded credentials for this 492 * bind request. 493 * 494 * @return An ASN.1 octet string containing the encoded credentials for this 495 * bind request. 496 */ 497 public ASN1OctetString encodeCredentials() 498 { 499 return encodeCredentials(authenticationID, authorizationID, staticPassword, 500 yubiKeyOTP); 501 } 502 503 504 505 /** 506 * Encodes the provided information into an ASN.1 octet string suitable for 507 * use as the SASL credentials for an UNBOUNDID-YUBIKEY-OTP bind request. 508 * 509 * @param authenticationID The authentication ID for the bind request. It 510 * must not be {@code null}, and must have the form 511 * "dn:" followed by the DN of the target user or 512 * "u:" followed by the the username of the target 513 * user. 514 * @param authorizationID The authorization ID for the bind request. It 515 * may be {@code null} if the authorization identity 516 * should be the same as the authentication 517 * identity. 518 * @param staticPassword The static password for the user specified as the 519 * authentication identity. It may be {@code null} 520 * if authentication should be performed using only 521 * the YubiKey OTP. 522 * @param yubiKeyOTP The one-time password generated by the YubiKey 523 * device. It must not be {@code null}. 524 * 525 * @return An ASN.1 octet string suitable for use as the SASL credentials for 526 * an UNBOUNDID-YUBIKEY-OTP bind request. 527 */ 528 public static ASN1OctetString encodeCredentials(final String authenticationID, 529 final String authorizationID, 530 final ASN1OctetString staticPassword, 531 final String yubiKeyOTP) 532 { 533 Validator.ensureNotNull(authenticationID); 534 Validator.ensureNotNull(yubiKeyOTP); 535 536 final ArrayList<ASN1Element> elements = new ArrayList<>(4); 537 elements.add(new ASN1OctetString(TYPE_AUTHENTICATION_ID, authenticationID)); 538 539 if (authorizationID != null) 540 { 541 elements.add(new ASN1OctetString(TYPE_AUTHORIZATION_ID, authorizationID)); 542 } 543 544 if (staticPassword != null) 545 { 546 elements.add(new ASN1OctetString(TYPE_STATIC_PASSWORD, 547 staticPassword.getValue())); 548 } 549 550 elements.add(new ASN1OctetString(TYPE_YUBIKEY_OTP, yubiKeyOTP)); 551 552 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 553 } 554 555 556 557 /** 558 * {@inheritDoc} 559 */ 560 @Override() 561 public UnboundIDYubiKeyOTPBindRequest duplicate() 562 { 563 return duplicate(getControls()); 564 } 565 566 567 568 /** 569 * {@inheritDoc} 570 */ 571 @Override() 572 public UnboundIDYubiKeyOTPBindRequest duplicate(final Control[] controls) 573 { 574 final UnboundIDYubiKeyOTPBindRequest bindRequest = 575 new UnboundIDYubiKeyOTPBindRequest(authenticationID, authorizationID, 576 staticPassword, yubiKeyOTP, controls); 577 bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 578 return bindRequest; 579 } 580 581 582 583 /** 584 * {@inheritDoc} 585 */ 586 @Override() 587 public String getSASLMechanismName() 588 { 589 return UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME; 590 } 591 592 593 594 /** 595 * {@inheritDoc} 596 */ 597 @Override() 598 public int getLastMessageID() 599 { 600 return messageID; 601 } 602 603 604 605 /** 606 * {@inheritDoc} 607 */ 608 @Override() 609 public void toString(final StringBuilder buffer) 610 { 611 buffer.append("UnboundYubiKeyOTPBindRequest(authenticationID='"); 612 buffer.append(authenticationID); 613 614 if (authorizationID != null) 615 { 616 buffer.append("', authorizationID='"); 617 buffer.append(authorizationID); 618 } 619 620 buffer.append("', staticPasswordProvided="); 621 buffer.append(staticPassword != null); 622 623 final Control[] controls = getControls(); 624 if (controls.length > 0) 625 { 626 buffer.append(", controls={"); 627 for (int i=0; i < controls.length; i++) 628 { 629 if (i > 0) 630 { 631 buffer.append(", "); 632 } 633 634 buffer.append(controls[i]); 635 } 636 buffer.append('}'); 637 } 638 639 buffer.append(')'); 640 } 641}