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.io.OutputStream; 026import java.io.Serializable; 027import java.util.LinkedHashMap; 028 029import com.unboundid.ldap.sdk.ExtendedResult; 030import com.unboundid.ldap.sdk.LDAPConnection; 031import com.unboundid.ldap.sdk.LDAPException; 032import com.unboundid.ldap.sdk.ResultCode; 033import com.unboundid.ldap.sdk.Version; 034import com.unboundid.ldap.sdk.unboundidds.extensions. 035 DeregisterYubiKeyOTPDeviceExtendedRequest; 036import com.unboundid.ldap.sdk.unboundidds.extensions. 037 RegisterYubiKeyOTPDeviceExtendedRequest; 038import com.unboundid.util.Debug; 039import com.unboundid.util.LDAPCommandLineTool; 040import com.unboundid.util.PasswordReader; 041import com.unboundid.util.StaticUtils; 042import com.unboundid.util.ThreadSafety; 043import com.unboundid.util.ThreadSafetyLevel; 044import com.unboundid.util.args.ArgumentException; 045import com.unboundid.util.args.ArgumentParser; 046import com.unboundid.util.args.BooleanArgument; 047import com.unboundid.util.args.FileArgument; 048import com.unboundid.util.args.StringArgument; 049 050import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*; 051 052 053 054/** 055 * This class provides a utility that may be used to register a YubiKey OTP 056 * device for a specified user so that it may be used to authenticate that user. 057 * Alternately, it may be used to deregister one or all of the YubiKey OTP 058 * devices that have been registered for the user. 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 */ 070@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 071public final class RegisterYubiKeyOTPDevice 072 extends LDAPCommandLineTool 073 implements Serializable 074{ 075 /** 076 * The serial version UID for this serializable class. 077 */ 078 private static final long serialVersionUID = 5705120716566064832L; 079 080 081 082 // Indicates that the tool should deregister one or all of the YubiKey OTP 083 // devices for the user rather than registering a new device. 084 private BooleanArgument deregister; 085 086 // Indicates that the tool should interactively prompt for the static password 087 // for the user for whom the YubiKey OTP device is to be registered or 088 // deregistered. 089 private BooleanArgument promptForUserPassword; 090 091 // The path to a file containing the static password for the user for whom the 092 // YubiKey OTP device is to be registered or deregistered. 093 private FileArgument userPasswordFile; 094 095 // The username for the user for whom the YubiKey OTP device is to be 096 // registered or deregistered. 097 private StringArgument authenticationID; 098 099 // The static password for the user for whom the YubiKey OTP device is to be 100 // registered or deregistered. 101 private StringArgument userPassword; 102 103 // A one-time password generated by the YubiKey OTP device to be registered 104 // or deregistered. 105 private StringArgument otp; 106 107 108 109 /** 110 * Parse the provided command line arguments and perform the appropriate 111 * processing. 112 * 113 * @param args The command line arguments provided to this program. 114 */ 115 public static void main(final String... args) 116 { 117 final ResultCode resultCode = main(args, System.out, System.err); 118 if (resultCode != ResultCode.SUCCESS) 119 { 120 System.exit(resultCode.intValue()); 121 } 122 } 123 124 125 126 /** 127 * Parse the provided command line arguments and perform the appropriate 128 * processing. 129 * 130 * @param args The command line arguments provided to this program. 131 * @param outStream The output stream to which standard out should be 132 * written. It may be {@code null} if output should be 133 * suppressed. 134 * @param errStream The output stream to which standard error should be 135 * written. It may be {@code null} if error messages 136 * should be suppressed. 137 * 138 * @return A result code indicating whether the processing was successful. 139 */ 140 public static ResultCode main(final String[] args, 141 final OutputStream outStream, 142 final OutputStream errStream) 143 { 144 final RegisterYubiKeyOTPDevice tool = 145 new RegisterYubiKeyOTPDevice(outStream, errStream); 146 return tool.runTool(args); 147 } 148 149 150 151 /** 152 * Creates a new instance of this tool. 153 * 154 * @param outStream The output stream to which standard out should be 155 * written. It may be {@code null} if output should be 156 * suppressed. 157 * @param errStream The output stream to which standard error should be 158 * written. It may be {@code null} if error messages 159 * should be suppressed. 160 */ 161 public RegisterYubiKeyOTPDevice(final OutputStream outStream, 162 final OutputStream errStream) 163 { 164 super(outStream, errStream); 165 166 deregister = null; 167 otp = null; 168 promptForUserPassword = null; 169 userPasswordFile = null; 170 authenticationID = null; 171 userPassword = null; 172 } 173 174 175 176 /** 177 * {@inheritDoc} 178 */ 179 @Override() 180 public String getToolName() 181 { 182 return "register-yubikey-otp-device"; 183 } 184 185 186 187 /** 188 * {@inheritDoc} 189 */ 190 @Override() 191 public String getToolDescription() 192 { 193 return INFO_REGISTER_YUBIKEY_OTP_DEVICE_TOOL_DESCRIPTION.get( 194 UnboundIDYubiKeyOTPBindRequest.UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME); 195 } 196 197 198 199 /** 200 * {@inheritDoc} 201 */ 202 @Override() 203 public String getToolVersion() 204 { 205 return Version.NUMERIC_VERSION_STRING; 206 } 207 208 209 210 /** 211 * {@inheritDoc} 212 */ 213 @Override() 214 public void addNonLDAPArguments(final ArgumentParser parser) 215 throws ArgumentException 216 { 217 deregister = new BooleanArgument(null, "deregister", 1, 218 INFO_REGISTER_YUBIKEY_OTP_DEVICE_DESCRIPTION_DEREGISTER.get("--otp")); 219 deregister.addLongIdentifier("de-register", true); 220 parser.addArgument(deregister); 221 222 otp = new StringArgument(null, "otp", false, 1, 223 INFO_REGISTER_YUBIKEY_OTP_DEVICE_PLACEHOLDER_OTP.get(), 224 INFO_REGISTER_YUBIKEY_OTP_DEVICE_DESCRIPTION_OTP.get()); 225 parser.addArgument(otp); 226 227 authenticationID = new StringArgument(null, "authID", false, 1, 228 INFO_REGISTER_YUBIKEY_OTP_DEVICE_PLACEHOLDER_AUTHID.get(), 229 INFO_REGISTER_YUBIKEY_OTP_DEVICE_DESCRIPTION_AUTHID.get()); 230 authenticationID.addLongIdentifier("authenticationID", true); 231 authenticationID.addLongIdentifier("auth-id", true); 232 authenticationID.addLongIdentifier("authentication-id", true); 233 parser.addArgument(authenticationID); 234 235 userPassword = new StringArgument(null, "userPassword", false, 1, 236 INFO_REGISTER_YUBIKEY_OTP_DEVICE_PLACEHOLDER_USER_PW.get(), 237 INFO_REGISTER_YUBIKEY_OTP_DEVICE_DESCRIPTION_USER_PW.get( 238 authenticationID.getIdentifierString())); 239 userPassword.setSensitive(true); 240 userPassword.addLongIdentifier("user-password", true); 241 parser.addArgument(userPassword); 242 243 userPasswordFile = new FileArgument(null, "userPasswordFile", false, 1, 244 null, 245 INFO_REGISTER_YUBIKEY_OTP_DEVICE_DESCRIPTION_USER_PW_FILE.get( 246 authenticationID.getIdentifierString()), 247 true, true, true, false); 248 userPasswordFile.addLongIdentifier("user-password-file", true); 249 parser.addArgument(userPasswordFile); 250 251 promptForUserPassword = new BooleanArgument(null, "promptForUserPassword", 252 INFO_REGISTER_YUBIKEY_OTP_DEVICE_DESCRIPTION_PROMPT_FOR_USER_PW.get( 253 authenticationID.getIdentifierString())); 254 promptForUserPassword.addLongIdentifier("prompt-for-user-password", true); 255 parser.addArgument(promptForUserPassword); 256 257 258 // At most one of the userPassword, userPasswordFile, and 259 // promptForUserPassword arguments must be present. 260 parser.addExclusiveArgumentSet(userPassword, userPasswordFile, 261 promptForUserPassword); 262 263 // If any of the userPassword, userPasswordFile, or promptForUserPassword 264 // arguments is present, then the authenticationID argument must also be 265 // present. 266 parser.addDependentArgumentSet(userPassword, authenticationID); 267 parser.addDependentArgumentSet(userPasswordFile, authenticationID); 268 parser.addDependentArgumentSet(promptForUserPassword, authenticationID); 269 } 270 271 272 273 /** 274 * {@inheritDoc} 275 */ 276 @Override() 277 public void doExtendedNonLDAPArgumentValidation() 278 throws ArgumentException 279 { 280 // If the deregister argument was not provided, then the otp argument must 281 // have been given. 282 if ((! deregister.isPresent()) && (! otp.isPresent())) 283 { 284 throw new ArgumentException( 285 ERR_REGISTER_YUBIKEY_OTP_DEVICE_NO_OTP_TO_REGISTER.get( 286 otp.getIdentifierString())); 287 } 288 } 289 290 291 292 /** 293 * {@inheritDoc} 294 */ 295 @Override() 296 public boolean supportsInteractiveMode() 297 { 298 return true; 299 } 300 301 302 303 /** 304 * {@inheritDoc} 305 */ 306 @Override() 307 public boolean defaultsToInteractiveMode() 308 { 309 return true; 310 } 311 312 313 314 /** 315 * {@inheritDoc} 316 */ 317 @Override() 318 protected boolean supportsOutputFile() 319 { 320 return true; 321 } 322 323 324 325 /** 326 * {@inheritDoc} 327 */ 328 @Override() 329 protected boolean defaultToPromptForBindPassword() 330 { 331 return true; 332 } 333 334 335 336 /** 337 * Indicates whether this tool supports the use of a properties file for 338 * specifying default values for arguments that aren't specified on the 339 * command line. 340 * 341 * @return {@code true} if this tool supports the use of a properties file 342 * for specifying default values for arguments that aren't specified 343 * on the command line, or {@code false} if not. 344 */ 345 @Override() 346 public boolean supportsPropertiesFile() 347 { 348 return true; 349 } 350 351 352 353 /** 354 * Indicates whether the LDAP-specific arguments should include alternate 355 * versions of all long identifiers that consist of multiple words so that 356 * they are available in both camelCase and dash-separated versions. 357 * 358 * @return {@code true} if this tool should provide multiple versions of 359 * long identifiers for LDAP-specific arguments, or {@code false} if 360 * not. 361 */ 362 @Override() 363 protected boolean includeAlternateLongIdentifiers() 364 { 365 return true; 366 } 367 368 369 370 /** 371 * Indicates whether this tool should provide a command-line argument that 372 * allows for low-level SSL debugging. If this returns {@code true}, then an 373 * "--enableSSLDebugging}" argument will be added that sets the 374 * "javax.net.debug" system property to "all" before attempting any 375 * communication. 376 * 377 * @return {@code true} if this tool should offer an "--enableSSLDebugging" 378 * argument, or {@code false} if not. 379 */ 380 @Override() 381 protected boolean supportsSSLDebugging() 382 { 383 return true; 384 } 385 386 387 388 /** 389 * {@inheritDoc} 390 */ 391 @Override() 392 protected boolean logToolInvocationByDefault() 393 { 394 return true; 395 } 396 397 398 399 /** 400 * {@inheritDoc} 401 */ 402 @Override() 403 public ResultCode doToolProcessing() 404 { 405 // Establish a connection to the Directory Server. 406 final LDAPConnection conn; 407 try 408 { 409 conn = getConnection(); 410 } 411 catch (final LDAPException le) 412 { 413 Debug.debugException(le); 414 wrapErr(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 415 ERR_REGISTER_YUBIKEY_OTP_DEVICE_CANNOT_CONNECT.get( 416 StaticUtils.getExceptionMessage(le))); 417 return le.getResultCode(); 418 } 419 420 try 421 { 422 // Get the authentication ID and static password to include in the 423 // request. 424 final String authID = authenticationID.getValue(); 425 426 final byte[] staticPassword; 427 if (userPassword.isPresent()) 428 { 429 staticPassword = StaticUtils.getBytes(userPassword.getValue()); 430 } 431 else if (userPasswordFile.isPresent()) 432 { 433 try 434 { 435 final char[] pwChars = getPasswordFileReader().readPassword( 436 userPasswordFile.getValue()); 437 staticPassword = StaticUtils.getBytes(new String(pwChars)); 438 } 439 catch (final Exception e) 440 { 441 Debug.debugException(e); 442 wrapErr(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 443 ERR_REGISTER_YUBIKEY_OTP_DEVICE_CANNOT_READ_PW.get( 444 StaticUtils.getExceptionMessage(e))); 445 return ResultCode.LOCAL_ERROR; 446 } 447 } 448 else if (promptForUserPassword.isPresent()) 449 { 450 try 451 { 452 getOut().print(INFO_REGISTER_YUBIKEY_OTP_DEVICE_ENTER_PW.get(authID)); 453 staticPassword = PasswordReader.readPassword(); 454 } 455 catch (final Exception e) 456 { 457 Debug.debugException(e); 458 wrapErr(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 459 ERR_REGISTER_YUBIKEY_OTP_DEVICE_CANNOT_READ_PW.get( 460 StaticUtils.getExceptionMessage(e))); 461 return ResultCode.LOCAL_ERROR; 462 } 463 } 464 else 465 { 466 staticPassword = null; 467 } 468 469 470 // Construct and process the appropriate register or deregister request. 471 if (deregister.isPresent()) 472 { 473 final DeregisterYubiKeyOTPDeviceExtendedRequest r = 474 new DeregisterYubiKeyOTPDeviceExtendedRequest(authID, 475 staticPassword, otp.getValue()); 476 477 ExtendedResult deregisterResult; 478 try 479 { 480 deregisterResult = conn.processExtendedOperation(r); 481 } 482 catch (final LDAPException le) 483 { 484 deregisterResult = new ExtendedResult(le); 485 } 486 487 if (deregisterResult.getResultCode() == ResultCode.SUCCESS) 488 { 489 if (otp.isPresent()) 490 { 491 wrapOut(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 492 INFO_REGISTER_YUBIKEY_OTP_DEVICE_DEREGISTER_SUCCESS_ONE.get( 493 authID)); 494 } 495 else 496 { 497 wrapOut(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 498 INFO_REGISTER_YUBIKEY_OTP_DEVICE_DEREGISTER_SUCCESS_ALL.get( 499 authID)); 500 } 501 return ResultCode.SUCCESS; 502 } 503 else 504 { 505 wrapErr(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 506 ERR_REGISTER_YUBIKEY_OTP_DEVICE_DEREGISTER_FAILED.get(authID, 507 String.valueOf(deregisterResult))); 508 return deregisterResult.getResultCode(); 509 } 510 } 511 else 512 { 513 final RegisterYubiKeyOTPDeviceExtendedRequest r = 514 new RegisterYubiKeyOTPDeviceExtendedRequest(authID, staticPassword, 515 otp.getValue()); 516 517 ExtendedResult registerResult; 518 try 519 { 520 registerResult = conn.processExtendedOperation(r); 521 } 522 catch (final LDAPException le) 523 { 524 registerResult = new ExtendedResult(le); 525 } 526 527 if (registerResult.getResultCode() == ResultCode.SUCCESS) 528 { 529 wrapOut(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 530 INFO_REGISTER_YUBIKEY_OTP_DEVICE_REGISTER_SUCCESS.get(authID)); 531 return ResultCode.SUCCESS; 532 } 533 else 534 { 535 wrapErr(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 536 ERR_REGISTER_YUBIKEY_OTP_DEVICE_REGISTER_FAILED.get(authID, 537 String.valueOf(registerResult))); 538 return registerResult.getResultCode(); 539 } 540 } 541 } 542 finally 543 { 544 conn.close(); 545 } 546 } 547 548 549 550 /** 551 * {@inheritDoc} 552 */ 553 @Override() 554 public LinkedHashMap<String[],String> getExampleUsages() 555 { 556 final LinkedHashMap<String[],String> exampleMap = 557 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2)); 558 559 String[] args = 560 { 561 "--hostname", "server.example.com", 562 "--port", "389", 563 "--bindDN", "uid=admin,dc=example,dc=com", 564 "--bindPassword", "adminPassword", 565 "--authenticationID", "u:test.user", 566 "--userPassword", "testUserPassword", 567 "--otp", "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr" 568 }; 569 exampleMap.put(args, 570 INFO_REGISTER_YUBIKEY_OTP_DEVICE_EXAMPLE_REGISTER.get()); 571 572 args = new String[] 573 { 574 "--hostname", "server.example.com", 575 "--port", "389", 576 "--bindDN", "uid=admin,dc=example,dc=com", 577 "--bindPassword", "adminPassword", 578 "--deregister", 579 "--authenticationID", "dn:uid=test.user,ou=People,dc=example,dc=com" 580 }; 581 exampleMap.put(args, 582 INFO_REGISTER_YUBIKEY_OTP_DEVICE_EXAMPLE_DEREGISTER.get()); 583 584 return exampleMap; 585 } 586}