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