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 * {@inheritDoc} 372 */ 373 @Override() 374 protected boolean logToolInvocationByDefault() 375 { 376 return true; 377 } 378 379 380 381 /** 382 * {@inheritDoc} 383 */ 384 @Override() 385 public ResultCode doToolProcessing() 386 { 387 // Establish a connection to the Directory Server. 388 final LDAPConnection conn; 389 try 390 { 391 conn = getConnection(); 392 } 393 catch (final LDAPException le) 394 { 395 Debug.debugException(le); 396 wrapErr(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 397 ERR_REGISTER_YUBIKEY_OTP_DEVICE_CANNOT_CONNECT.get( 398 StaticUtils.getExceptionMessage(le))); 399 return le.getResultCode(); 400 } 401 402 try 403 { 404 // Get the authentication ID and static password to include in the 405 // request. 406 final String authID = authenticationID.getValue(); 407 408 final byte[] staticPassword; 409 if (userPassword.isPresent()) 410 { 411 staticPassword = StaticUtils.getBytes(userPassword.getValue()); 412 } 413 else if (userPasswordFile.isPresent()) 414 { 415 try 416 { 417 final char[] pwChars = getPasswordFileReader().readPassword( 418 userPasswordFile.getValue()); 419 staticPassword = StaticUtils.getBytes(new String(pwChars)); 420 } 421 catch (final Exception e) 422 { 423 Debug.debugException(e); 424 wrapErr(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 425 ERR_REGISTER_YUBIKEY_OTP_DEVICE_CANNOT_READ_PW.get( 426 StaticUtils.getExceptionMessage(e))); 427 return ResultCode.LOCAL_ERROR; 428 } 429 } 430 else if (promptForUserPassword.isPresent()) 431 { 432 try 433 { 434 getOut().print(INFO_REGISTER_YUBIKEY_OTP_DEVICE_ENTER_PW.get(authID)); 435 staticPassword = PasswordReader.readPassword(); 436 } 437 catch (final Exception e) 438 { 439 Debug.debugException(e); 440 wrapErr(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 441 ERR_REGISTER_YUBIKEY_OTP_DEVICE_CANNOT_READ_PW.get( 442 StaticUtils.getExceptionMessage(e))); 443 return ResultCode.LOCAL_ERROR; 444 } 445 } 446 else 447 { 448 staticPassword = null; 449 } 450 451 452 // Construct and process the appropriate register or deregister request. 453 if (deregister.isPresent()) 454 { 455 final DeregisterYubiKeyOTPDeviceExtendedRequest r = 456 new DeregisterYubiKeyOTPDeviceExtendedRequest(authID, 457 staticPassword, otp.getValue()); 458 459 ExtendedResult deregisterResult; 460 try 461 { 462 deregisterResult = conn.processExtendedOperation(r); 463 } 464 catch (final LDAPException le) 465 { 466 deregisterResult = new ExtendedResult(le); 467 } 468 469 if (deregisterResult.getResultCode() == ResultCode.SUCCESS) 470 { 471 if (otp.isPresent()) 472 { 473 wrapOut(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 474 INFO_REGISTER_YUBIKEY_OTP_DEVICE_DEREGISTER_SUCCESS_ONE.get( 475 authID)); 476 } 477 else 478 { 479 wrapOut(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 480 INFO_REGISTER_YUBIKEY_OTP_DEVICE_DEREGISTER_SUCCESS_ALL.get( 481 authID)); 482 } 483 return ResultCode.SUCCESS; 484 } 485 else 486 { 487 wrapErr(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 488 ERR_REGISTER_YUBIKEY_OTP_DEVICE_DEREGISTER_FAILED.get(authID, 489 String.valueOf(deregisterResult))); 490 return deregisterResult.getResultCode(); 491 } 492 } 493 else 494 { 495 final RegisterYubiKeyOTPDeviceExtendedRequest r = 496 new RegisterYubiKeyOTPDeviceExtendedRequest(authID, staticPassword, 497 otp.getValue()); 498 499 ExtendedResult registerResult; 500 try 501 { 502 registerResult = conn.processExtendedOperation(r); 503 } 504 catch (final LDAPException le) 505 { 506 registerResult = new ExtendedResult(le); 507 } 508 509 if (registerResult.getResultCode() == ResultCode.SUCCESS) 510 { 511 wrapOut(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 512 INFO_REGISTER_YUBIKEY_OTP_DEVICE_REGISTER_SUCCESS.get(authID)); 513 return ResultCode.SUCCESS; 514 } 515 else 516 { 517 wrapErr(0, StaticUtils.TERMINAL_WIDTH_COLUMNS, 518 ERR_REGISTER_YUBIKEY_OTP_DEVICE_REGISTER_FAILED.get(authID, 519 String.valueOf(registerResult))); 520 return registerResult.getResultCode(); 521 } 522 } 523 } 524 finally 525 { 526 conn.close(); 527 } 528 } 529 530 531 532 /** 533 * {@inheritDoc} 534 */ 535 @Override() 536 public LinkedHashMap<String[],String> getExampleUsages() 537 { 538 final LinkedHashMap<String[],String> exampleMap = 539 new LinkedHashMap<>(StaticUtils.computeMapCapacity(2)); 540 541 String[] args = 542 { 543 "--hostname", "server.example.com", 544 "--port", "389", 545 "--bindDN", "uid=admin,dc=example,dc=com", 546 "--bindPassword", "adminPassword", 547 "--authenticationID", "u:test.user", 548 "--userPassword", "testUserPassword", 549 "--otp", "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqr" 550 }; 551 exampleMap.put(args, 552 INFO_REGISTER_YUBIKEY_OTP_DEVICE_EXAMPLE_REGISTER.get()); 553 554 args = new String[] 555 { 556 "--hostname", "server.example.com", 557 "--port", "389", 558 "--bindDN", "uid=admin,dc=example,dc=com", 559 "--bindPassword", "adminPassword", 560 "--deregister", 561 "--authenticationID", "dn:uid=test.user,ou=People,dc=example,dc=com" 562 }; 563 exampleMap.put(args, 564 INFO_REGISTER_YUBIKEY_OTP_DEVICE_EXAMPLE_DEREGISTER.get()); 565 566 return exampleMap; 567 } 568}