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