001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2007-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.backends; 018 019import static org.forgerock.util.Reject.*; 020import static org.opends.messages.BackendMessages.*; 021import static org.opends.server.config.ConfigConstants.*; 022import static org.opends.server.util.ServerConstants.*; 023import static org.opends.server.util.StaticUtils.*; 024 025import java.io.BufferedReader; 026import java.io.File; 027import java.io.FileInputStream; 028import java.io.FileOutputStream; 029import java.io.FileReader; 030import java.io.FileWriter; 031import java.io.IOException; 032import java.io.PrintWriter; 033import java.net.UnknownHostException; 034import java.security.Key; 035import java.security.KeyStore; 036import java.security.KeyStoreException; 037import java.security.cert.Certificate; 038import java.util.Collections; 039import java.util.Iterator; 040import java.util.LinkedHashMap; 041import java.util.List; 042import java.util.Random; 043import java.util.Set; 044import java.util.SortedSet; 045 046import javax.naming.ldap.Rdn; 047import javax.net.ssl.KeyManager; 048import javax.net.ssl.KeyManagerFactory; 049import javax.net.ssl.TrustManager; 050import javax.net.ssl.TrustManagerFactory; 051 052import org.forgerock.i18n.LocalizableMessage; 053import org.forgerock.i18n.slf4j.LocalizedLogger; 054import org.forgerock.opendj.config.server.ConfigChangeResult; 055import org.forgerock.opendj.config.server.ConfigException; 056import org.forgerock.opendj.config.server.ConfigurationChangeListener; 057import org.forgerock.opendj.ldap.AVA; 058import org.forgerock.opendj.ldap.ByteString; 059import org.forgerock.opendj.ldap.ConditionResult; 060import org.forgerock.opendj.ldap.DN; 061import org.forgerock.opendj.ldap.RDN; 062import org.forgerock.opendj.ldap.ResultCode; 063import org.forgerock.opendj.ldap.SearchScope; 064import org.forgerock.opendj.ldap.schema.AttributeType; 065import org.forgerock.opendj.ldap.schema.CoreSchema; 066import org.forgerock.opendj.server.config.server.TrustStoreBackendCfg; 067import org.forgerock.util.Reject; 068import org.opends.server.api.Backend; 069import org.opends.server.core.AddOperation; 070import org.opends.server.core.DeleteOperation; 071import org.opends.server.core.DirectoryServer; 072import org.opends.server.core.ModifyDNOperation; 073import org.opends.server.core.ModifyOperation; 074import org.opends.server.core.SearchOperation; 075import org.opends.server.core.ServerContext; 076import org.opends.server.types.Attribute; 077import org.opends.server.types.AttributeBuilder; 078import org.opends.server.types.Attributes; 079import org.opends.server.types.BackupConfig; 080import org.opends.server.types.BackupDirectory; 081import org.opends.server.types.DirectoryException; 082import org.opends.server.types.Entry; 083import org.opends.server.types.FilePermission; 084import org.opends.server.types.IndexType; 085import org.opends.server.types.InitializationException; 086import org.opends.server.types.LDIFExportConfig; 087import org.opends.server.types.LDIFImportConfig; 088import org.opends.server.types.LDIFImportResult; 089import org.forgerock.opendj.ldap.schema.ObjectClass; 090import org.opends.server.types.RestoreConfig; 091import org.opends.server.types.SearchFilter; 092import org.opends.server.util.CertificateManager; 093import org.opends.server.util.Platform.KeyType; 094import org.opends.server.util.SetupUtils; 095 096/** 097 * This class defines a backend used to provide an LDAP view of public keys 098 * stored in a key store. 099 */ 100public class TrustStoreBackend extends Backend<TrustStoreBackendCfg> 101 implements ConfigurationChangeListener<TrustStoreBackendCfg> 102{ 103 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 104 105 /** The current configuration state. */ 106 private TrustStoreBackendCfg configuration; 107 /** The set of base DNs for this backend. */ 108 private SortedSet<DN> baseDNs; 109 /** The base entry. */ 110 private Entry baseEntry; 111 112 /** The PIN needed to access the trust store backing file. */ 113 private char[] trustStorePIN; 114 /** The path to the trust store backing file. */ 115 private String trustStoreFile; 116 /** The type of trust store backing file to use. */ 117 private String trustStoreType; 118 119 /** The certificate manager for the trust store. */ 120 private CertificateManager certificateManager; 121 122 /** 123 * Creates a new backend. All backend 124 * implementations must implement a default constructor that use 125 * <CODE>super()</CODE> to invoke this constructor. 126 */ 127 public TrustStoreBackend() 128 { 129 super(); 130 131 // Perform all initialization in initializeBackend. 132 } 133 134 private DN getBaseDN() 135 { 136 return baseDNs.first(); 137 } 138 139 @Override 140 public void configureBackend(TrustStoreBackendCfg config, ServerContext serverContext) throws ConfigException 141 { 142 Reject.ifNull(config); 143 configuration = config; 144 } 145 146 @Override 147 public void openBackend() throws ConfigException, InitializationException 148 { 149 DN configEntryDN = configuration.dn(); 150 151 // Create the set of base DNs that we will handle. In this case, it's just 152 // the DN of the base trust store entry. 153 SortedSet<DN> baseDNSet = configuration.getBaseDN(); 154 if (baseDNSet.size() != 1) 155 { 156 throw new InitializationException(ERR_TRUSTSTORE_REQUIRES_ONE_BASE_DN.get(configEntryDN)); 157 } 158 baseDNs = baseDNSet; 159 160 // Get the path to the trust store file. 161 trustStoreFile = configuration.getTrustStoreFile(); 162 163 // Get the trust store type. If none is specified, then use the default type. 164 trustStoreType = configuration.getTrustStoreType(); 165 if (trustStoreType == null) 166 { 167 trustStoreType = KeyStore.getDefaultType(); 168 } 169 170 try 171 { 172 KeyStore.getInstance(trustStoreType); 173 } 174 catch (KeyStoreException kse) 175 { 176 logger.traceException(kse); 177 throw new InitializationException(ERR_TRUSTSTORE_INVALID_TYPE.get( 178 trustStoreType, configEntryDN, getExceptionMessage(kse))); 179 } 180 181 trustStorePIN = getTrustStorePIN(configEntryDN); 182 183 certificateManager = 184 new CertificateManager(getFileForPath(trustStoreFile).getPath(), 185 trustStoreType, 186 new String(trustStorePIN)); 187 188 // Generate a self-signed certificate, if there is none. 189 generateInstanceCertificateIfAbsent(); 190 191 // Construct the trust store base entry. 192 LinkedHashMap<ObjectClass,String> objectClasses = new LinkedHashMap<>(2); 193 objectClasses.put(CoreSchema.getTopObjectClass(), OC_TOP); 194 objectClasses.put(DirectoryServer.getSchema().getObjectClass("ds-cfg-branch"), "ds-cfg-branch"); 195 196 LinkedHashMap<AttributeType,List<Attribute>> userAttrs = new LinkedHashMap<>(1); 197 for (AVA ava : getBaseDN().rdn()) 198 { 199 AttributeType attrType = ava.getAttributeType(); 200 userAttrs.put(attrType, Attributes.createAsList(attrType, ava.getAttributeValue())); 201 } 202 203 baseEntry = new Entry(getBaseDN(), objectClasses, userAttrs, null); 204 205 // Register this as a change listener. 206 configuration.addTrustStoreChangeListener(this); 207 208 // Register the trust store base as a private suffix. 209 try 210 { 211 DirectoryServer.registerBaseDN(getBaseDN(), this, true); 212 } 213 catch (Exception e) 214 { 215 logger.traceException(e); 216 throw new InitializationException(ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(getBaseDN(), e), e); 217 } 218 } 219 220 /** 221 * Get the PIN needed to access the contents of the trust store file. We will offer several places 222 * to look for the PIN, and we will do so in the following order: 223 * <ol> 224 * <li>In a specified Java property</li> 225 * <li>In a specified environment variable</li> 226 * <li>In a specified file on the server filesystem</li> 227 * <li>As the value of a configuration attribute</li> 228 * </ol> 229 * In any case, the PIN must be in the clear. If no PIN is provided, then it will be assumed that 230 * none is required to access the information in the trust store. 231 */ 232 private char[] getTrustStorePIN(DN configEntryDN) throws InitializationException 233 { 234 final String pinProperty = configuration.getTrustStorePinProperty(); 235 if (pinProperty != null) 236 { 237 String pinStr = System.getProperty(pinProperty); 238 if (pinStr == null) 239 { 240 throw new InitializationException(ERR_TRUSTSTORE_PIN_PROPERTY_NOT_SET.get(pinProperty, configEntryDN)); 241 } 242 return pinStr.toCharArray(); 243 } 244 245 final String pinEnVar = configuration.getTrustStorePinEnvironmentVariable(); 246 if (pinEnVar != null) 247 { 248 String pinStr = System.getenv(pinEnVar); 249 if (pinStr == null) 250 { 251 throw new InitializationException(ERR_TRUSTSTORE_PIN_ENVAR_NOT_SET.get(pinProperty, configEntryDN)); 252 } 253 return pinStr.toCharArray(); 254 } 255 256 final String pinFilePath = configuration.getTrustStorePinFile(); 257 if (pinFilePath != null) 258 { 259 File pinFile = getFileForPath(pinFilePath); 260 if (pinFile.exists()) 261 { 262 String pinStr = readPinFromFile(pinFile, configEntryDN); 263 if (pinStr == null) 264 { 265 throw new InitializationException(ERR_TRUSTSTORE_PIN_FILE_EMPTY.get(pinFilePath, configEntryDN)); 266 } 267 return pinStr.toCharArray(); 268 } 269 270 try 271 { 272 // Generate and store the PIN in the pin file. 273 final char[] trustStorePIN = createKeystorePassword(); 274 createPINFile(pinFile.getPath(), new String(trustStorePIN)); 275 return trustStorePIN; 276 } 277 catch (Exception e) 278 { 279 throw new InitializationException(ERR_TRUSTSTORE_PIN_FILE_CANNOT_CREATE.get(pinFilePath, configEntryDN)); 280 } 281 } 282 283 String pinStr = configuration.getTrustStorePin(); 284 // else branch should be an Error. Otherwise, programs fails. Is there a Unit Test? 285 return pinStr != null ? pinStr.toCharArray() : null; 286 } 287 288 @Override 289 public void closeBackend() 290 { 291 configuration.addTrustStoreChangeListener(this); 292 293 try 294 { 295 DirectoryServer.deregisterBaseDN(getBaseDN()); 296 } 297 catch (Exception e) 298 { 299 logger.traceException(e); 300 } 301 } 302 303 @Override 304 public Set<DN> getBaseDNs() 305 { 306 return baseDNs; 307 } 308 309 @Override 310 public long getEntryCount() 311 { 312 int numEntries = 1; 313 314 try 315 { 316 String[] aliases = certificateManager.getCertificateAliases(); 317 if (aliases != null) 318 { 319 numEntries += aliases.length; 320 } 321 } 322 catch (KeyStoreException e) 323 { 324 logger.traceException(e); 325 } 326 327 return numEntries; 328 } 329 330 @Override 331 public boolean isIndexed(AttributeType attributeType, IndexType indexType) 332 { 333 // All searches in this backend will always be considered indexed. 334 return true; 335 } 336 337 @Override 338 public Entry getEntry(DN entryDN) throws DirectoryException 339 { 340 // If the requested entry was null, then throw an exception. 341 if (entryDN == null) 342 { 343 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 344 ERR_BACKEND_GET_ENTRY_NULL.get(getBackendID())); 345 } 346 347 // If the requested entry was the backend base entry, then retrieve it. 348 if (entryDN.equals(getBaseDN())) 349 { 350 return baseEntry.duplicate(true); 351 } 352 353 // See if the requested entry was one level below the backend base entry. 354 // If so, then it must point to a trust store entry. 355 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 356 if (parentDN != null && parentDN.equals(getBaseDN())) 357 { 358 try 359 { 360 return getCertEntry(entryDN); 361 } 362 catch (DirectoryException e) 363 { 364 logger.traceException(e); 365 } 366 } 367 return null; 368 } 369 370 /** 371 * Generates an entry for a certificate based on the provided DN. The 372 * DN must contain an RDN component that specifies the alias of the 373 * certificate, and that certificate alias must exist in the key store. 374 * 375 * @param entryDN The DN of the certificate to retrieve. 376 * 377 * @return The requested certificate entry. 378 * 379 * @throws DirectoryException If the specified alias does not exist, or if 380 * the DN does not specify any alias. 381 */ 382 private Entry getCertEntry(DN entryDN) 383 throws DirectoryException 384 { 385 // Make sure that the DN specifies a certificate alias. 386 AttributeType t = DirectoryServer.getSchema().getAttributeType(ATTR_CRYPTO_KEY_ID); 387 ByteString v = entryDN.rdn().getAttributeValue(t); 388 if (v == null) 389 { 390 LocalizableMessage message = ERR_TRUSTSTORE_DN_DOES_NOT_SPECIFY_CERTIFICATE.get(entryDN); 391 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message, getBaseDN(), null); 392 } 393 394 String certAlias = v.toString(); 395 ByteString certValue; 396 try 397 { 398 Certificate cert = certificateManager.getCertificate(certAlias); 399 if (cert == null) 400 { 401 LocalizableMessage message = ERR_TRUSTSTORE_CERTIFICATE_NOT_FOUND.get(entryDN, certAlias); 402 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 403 } 404 certValue = ByteString.wrap(cert.getEncoded()); 405 } 406 catch (Exception e) 407 { 408 logger.traceException(e); 409 410 LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_RETRIEVE_CERT.get( 411 certAlias, trustStoreFile, e.getMessage()); 412 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 413 } 414 415 // Construct the certificate entry to return. 416 LinkedHashMap<ObjectClass,String> ocMap = new LinkedHashMap<>(2); 417 ocMap.put(CoreSchema.getTopObjectClass(), OC_TOP); 418 ocMap.put(DirectoryServer.getSchema().getObjectClass(OC_CRYPTO_INSTANCE_KEY), OC_CRYPTO_INSTANCE_KEY); 419 420 LinkedHashMap<AttributeType,List<Attribute>> opAttrs = new LinkedHashMap<>(0); 421 LinkedHashMap<AttributeType,List<Attribute>> userAttrs = new LinkedHashMap<>(3); 422 423 userAttrs.put(t, Attributes.createAsList(t, v)); 424 425 t = DirectoryServer.getSchema().getAttributeType(ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE); 426 AttributeBuilder builder = new AttributeBuilder(t); 427 builder.setOption("binary"); 428 builder.add(certValue); 429 userAttrs.put(t, builder.toAttributeList()); 430 431 Entry e = new Entry(entryDN, ocMap, userAttrs, opAttrs); 432 e.processVirtualAttributes(); 433 return e; 434 } 435 436 @Override 437 public void addEntry(Entry entry, AddOperation addOperation) 438 throws DirectoryException 439 { 440 DN entryDN = entry.getName(); 441 442 if (entryDN.equals(getBaseDN())) 443 { 444 LocalizableMessage message = ERR_TRUSTSTORE_INVALID_BASE.get(entryDN); 445 throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message); 446 } 447 448 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 449 if (parentDN == null) 450 { 451 LocalizableMessage message = ERR_TRUSTSTORE_INVALID_BASE.get(entryDN); 452 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 453 } 454 455 if (parentDN.equals(getBaseDN())) 456 { 457 addCertificate(entry); 458 } 459 else 460 { 461 LocalizableMessage message = ERR_TRUSTSTORE_INVALID_BASE.get(entryDN); 462 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 463 } 464 } 465 466 @Override 467 public void deleteEntry(DN entryDN, DeleteOperation deleteOperation) 468 throws DirectoryException 469 { 470 if (entryDN.equals(getBaseDN())) 471 { 472 LocalizableMessage message = ERR_TRUSTSTORE_INVALID_BASE.get(entryDN); 473 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 474 } 475 476 DN parentDN = DirectoryServer.getParentDNInSuffix(entryDN); 477 if (parentDN == null || !parentDN.equals(getBaseDN())) 478 { 479 LocalizableMessage message = ERR_TRUSTSTORE_INVALID_BASE.get(entryDN); 480 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 481 } 482 483 deleteCertificate(entryDN); 484 } 485 486 @Override 487 public void replaceEntry(Entry oldEntry, Entry newEntry, 488 ModifyOperation modifyOperation) throws DirectoryException 489 { 490 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 491 ERR_BACKEND_MODIFY_NOT_SUPPORTED.get(oldEntry.getName(), getBackendID())); 492 } 493 494 @Override 495 public void renameEntry(DN currentDN, Entry entry, 496 ModifyDNOperation modifyDNOperation) 497 throws DirectoryException 498 { 499 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 500 ERR_BACKEND_MODIFY_DN_NOT_SUPPORTED.get(currentDN, getBackendID())); 501 } 502 503 @Override 504 public void search(SearchOperation searchOperation) 505 throws DirectoryException 506 { 507 // Get the base entry for the search, if possible. If it doesn't exist, 508 // then this will throw an exception. 509 DN baseDN = searchOperation.getBaseDN(); 510 Entry baseEntry = getEntry(baseDN); 511 512 // Look at the base DN and see if it's the trust store base DN, or a 513 // trust store entry DN. 514 SearchScope scope = searchOperation.getScope(); 515 SearchFilter filter = searchOperation.getFilter(); 516 if (getBaseDN().equals(baseDN)) 517 { 518 if ((scope == SearchScope.BASE_OBJECT || scope == SearchScope.WHOLE_SUBTREE) 519 && filter.matchesEntry(baseEntry)) 520 { 521 searchOperation.returnEntry(baseEntry, null); 522 } 523 524 String[] aliases = null; 525 try 526 { 527 aliases = certificateManager.getCertificateAliases(); 528 } 529 catch (KeyStoreException e) 530 { 531 logger.traceException(e); 532 } 533 534 if (aliases == null) 535 { 536 aliases = new String[0]; 537 } 538 539 if (scope != SearchScope.BASE_OBJECT && aliases.length != 0) 540 { 541 AttributeType certAliasType = DirectoryServer.getSchema().getAttributeType(ATTR_CRYPTO_KEY_ID); 542 for (String alias : aliases) 543 { 544 DN certDN = makeChildDN(getBaseDN(), certAliasType, alias); 545 546 Entry certEntry; 547 try 548 { 549 certEntry = getCertEntry(certDN); 550 } 551 catch (Exception e) 552 { 553 logger.traceException(e); 554 continue; 555 } 556 557 if (filter.matchesEntry(certEntry)) 558 { 559 searchOperation.returnEntry(certEntry, null); 560 } 561 } 562 } 563 } 564 else if (getBaseDN().equals(DirectoryServer.getParentDNInSuffix(baseDN))) 565 { 566 Entry certEntry = getCertEntry(baseDN); 567 568 if ((scope == SearchScope.BASE_OBJECT || scope == SearchScope.WHOLE_SUBTREE) 569 && filter.matchesEntry(certEntry)) 570 { 571 searchOperation.returnEntry(certEntry, null); 572 } 573 } 574 else 575 { 576 LocalizableMessage message = ERR_TRUSTSTORE_INVALID_BASE.get(baseDN); 577 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 578 } 579 } 580 581 @Override 582 public Set<String> getSupportedControls() 583 { 584 return Collections.emptySet(); 585 } 586 587 @Override 588 public Set<String> getSupportedFeatures() 589 { 590 return Collections.emptySet(); 591 } 592 593 @Override 594 public boolean supports(BackendOperation backendOperation) 595 { 596 return false; 597 } 598 599 @Override 600 public void exportLDIF(LDIFExportConfig exportConfig) 601 throws DirectoryException 602 { 603 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 604 ERR_BACKEND_IMPORT_AND_EXPORT_NOT_SUPPORTED.get(getBackendID())); 605 } 606 607 @Override 608 public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext) 609 throws DirectoryException 610 { 611 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 612 ERR_BACKEND_IMPORT_AND_EXPORT_NOT_SUPPORTED.get(getBackendID())); 613 } 614 615 @Override 616 public void createBackup(BackupConfig backupConfig) 617 throws DirectoryException 618 { 619 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 620 ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID())); 621 } 622 623 @Override 624 public void removeBackup(BackupDirectory backupDirectory, 625 String backupID) 626 throws DirectoryException 627 { 628 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 629 ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID())); 630 } 631 632 @Override 633 public void restoreBackup(RestoreConfig restoreConfig) 634 throws DirectoryException 635 { 636 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 637 ERR_BACKEND_BACKUP_AND_RESTORE_NOT_SUPPORTED.get(getBackendID())); 638 } 639 640 @Override 641 public ConditionResult hasSubordinates(DN entryDN) 642 throws DirectoryException 643 { 644 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 645 ERR_HAS_SUBORDINATES_NOT_SUPPORTED.get()); 646 } 647 648 @Override 649 public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException 650 { 651 checkNotNull(baseDN, "baseDN must not be null"); 652 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get()); 653 } 654 655 @Override 656 public long getNumberOfChildren(DN parentDN) throws DirectoryException 657 { 658 checkNotNull(parentDN, "parentDN must not be null"); 659 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get()); 660 } 661 662 @Override 663 public boolean isConfigurationChangeAcceptable( 664 TrustStoreBackendCfg configuration, List<LocalizableMessage> unacceptableReasons) 665 { 666 final ConfigChangeResult ccr = new ConfigChangeResult(); 667 final DN cfgEntryDN = configuration.dn(); 668 669 // Get the path to the trust store file. 670 String newTrustStoreFile = configuration.getTrustStoreFile(); 671 try 672 { 673 File f = getFileForPath(newTrustStoreFile); 674 if (!f.exists() || !f.isFile()) 675 { 676 ccr.addMessage(ERR_TRUSTSTORE_NO_SUCH_FILE.get(newTrustStoreFile, cfgEntryDN)); 677 } 678 } 679 catch (Exception e) 680 { 681 logger.traceException(e); 682 683 ccr.addMessage(ERR_TRUSTSTORE_CANNOT_DETERMINE_FILE.get(cfgEntryDN, getExceptionMessage(e))); 684 } 685 686 // Check to see if the trust store type is acceptable. 687 String storeType = configuration.getTrustStoreType(); 688 if (storeType != null) 689 { 690 try 691 { 692 KeyStore.getInstance(storeType); 693 } 694 catch (KeyStoreException kse) 695 { 696 logger.traceException(kse); 697 698 ccr.addMessage(ERR_TRUSTSTORE_INVALID_TYPE.get(storeType, cfgEntryDN, getExceptionMessage(kse))); 699 } 700 } 701 702 // If there is a PIN property, then make sure the corresponding 703 // property is set. 704 String pinProp = configuration.getTrustStorePinProperty(); 705 if (pinProp != null && System.getProperty(pinProp) == null) 706 { 707 ccr.addMessage(ERR_TRUSTSTORE_PIN_PROPERTY_NOT_SET.get(pinProp, cfgEntryDN)); 708 } 709 710 // If there is a PIN environment variable, then make sure the corresponding 711 // environment variable is set. 712 String pinEnVar = configuration.getTrustStorePinEnvironmentVariable(); 713 if (pinEnVar != null && System.getenv(pinEnVar) == null) 714 { 715 ccr.addMessage(ERR_TRUSTSTORE_PIN_ENVAR_NOT_SET.get(pinEnVar, cfgEntryDN)); 716 } 717 718 // If there is a PIN file, then make sure the file is readable if it exists. 719 String pinFile = configuration.getTrustStorePinFile(); 720 if (pinFile != null) 721 { 722 File f = new File(pinFile); 723 if (f.exists()) 724 { 725 String pinStr = readPinFromFile2(f, cfgEntryDN, ccr); 726 if (pinStr == null) 727 { 728 ccr.addMessage(ERR_TRUSTSTORE_PIN_FILE_EMPTY.get(pinFile, cfgEntryDN)); 729 } 730 } 731 } 732 733 final List<LocalizableMessage> messages = ccr.getMessages(); 734 unacceptableReasons.addAll(messages); 735 return messages.isEmpty(); 736 } 737 738 @Override 739 public ConfigChangeResult applyConfigurationChange(TrustStoreBackendCfg cfg) 740 { 741 final ConfigChangeResult ccr = new ConfigChangeResult(); 742 DN configEntryDN = cfg.dn(); 743 744 // Get the path to the trust store file. 745 String newTrustStoreFile = cfg.getTrustStoreFile(); 746 File f = getFileForPath(newTrustStoreFile); 747 if (!f.exists() || !f.isFile()) 748 { 749 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 750 ccr.addMessage(ERR_TRUSTSTORE_NO_SUCH_FILE.get(newTrustStoreFile, configEntryDN)); 751 } 752 753 // Get the trust store type. If none is specified, then use the default 754 // type. 755 String newTrustStoreType = cfg.getTrustStoreType(); 756 if (newTrustStoreType == null) 757 { 758 newTrustStoreType = KeyStore.getDefaultType(); 759 } 760 761 try 762 { 763 KeyStore.getInstance(newTrustStoreType); 764 } 765 catch (KeyStoreException kse) 766 { 767 logger.traceException(kse); 768 769 ccr.addMessage(ERR_TRUSTSTORE_INVALID_TYPE.get(newTrustStoreType, configEntryDN, getExceptionMessage(kse))); 770 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 771 } 772 773 char[] newPIN = getTrustStorePIN2(cfg, ccr); 774 775 if (ccr.getResultCode() == ResultCode.SUCCESS) 776 { 777 trustStoreFile = newTrustStoreFile; 778 trustStoreType = newTrustStoreType; 779 trustStorePIN = newPIN; 780 configuration = cfg; 781 certificateManager = 782 new CertificateManager(getFileForPath(trustStoreFile).getPath(), 783 trustStoreType, 784 new String(trustStorePIN)); 785 } 786 787 return ccr; 788 } 789 790 /** 791 * Get the PIN needed to access the contents of the trust store file. We will offer several places 792 * to look for the PIN, and we will do so in the following order: 793 * <ol> 794 * <li>In a specified Java property</li> 795 * <li>In a specified environment variable</li> 796 * <li>In a specified file on the server filesystem.</li> 797 * <li>As the value of a configuration attribute.</li> 798 * </ol> 799 * In any case, the PIN must be in the clear. If no PIN is provided, then it will be assumed that 800 * none is required to access the information in the trust store. 801 */ 802 private char[] getTrustStorePIN2(TrustStoreBackendCfg cfg, ConfigChangeResult ccr) 803 { 804 String newPINProperty = cfg.getTrustStorePinProperty(); 805 if (newPINProperty == null) 806 { 807 String newPINEnVar = cfg.getTrustStorePinEnvironmentVariable(); 808 if (newPINEnVar == null) 809 { 810 String newPINFile = cfg.getTrustStorePinFile(); 811 if (newPINFile == null) 812 { 813 String pinStr = cfg.getTrustStorePin(); 814 return pinStr != null ? pinStr.toCharArray() : null; 815 } 816 else 817 { 818 File pinFile = getFileForPath(newPINFile); 819 if (! pinFile.exists()) 820 { 821 try 822 { 823 // Generate and store a PIN in the pin file. 824 final char[] newPIN = createKeystorePassword(); 825 createPINFile(pinFile.getPath(), new String(newPIN)); 826 return newPIN; 827 } 828 catch (Exception e) 829 { 830 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 831 ccr.addMessage(ERR_TRUSTSTORE_PIN_FILE_CANNOT_CREATE.get(newPINFile, cfg.dn())); 832 } 833 } 834 else 835 { 836 String pinStr = readPinFromFile2(pinFile, cfg.dn(), ccr); 837 if (pinStr == null) 838 { 839 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 840 ccr.addMessage(ERR_TRUSTSTORE_PIN_FILE_EMPTY.get(newPINFile, cfg.dn())); 841 } 842 else 843 { 844 return pinStr.toCharArray(); 845 } 846 } 847 } 848 } 849 else 850 { 851 String pinStr = System.getenv(newPINEnVar); 852 if (pinStr == null) 853 { 854 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 855 ccr.addMessage(ERR_TRUSTSTORE_PIN_ENVAR_NOT_SET.get(newPINEnVar, cfg.dn())); 856 } 857 else 858 { 859 return pinStr.toCharArray(); 860 } 861 } 862 } 863 else 864 { 865 String pinStr = System.getProperty(newPINProperty); 866 if (pinStr == null) 867 { 868 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 869 ccr.addMessage(ERR_TRUSTSTORE_PIN_PROPERTY_NOT_SET.get(newPINProperty, cfg.dn())); 870 } 871 else 872 { 873 return pinStr.toCharArray(); 874 } 875 } 876 return null; 877 } 878 879 private String readPinFromFile(File pinFile, DN cfgEntryDN) throws InitializationException 880 { 881 try (BufferedReader br = new BufferedReader(new FileReader(pinFile))) 882 { 883 return br.readLine(); 884 } 885 catch (IOException ioe) 886 { 887 LocalizableMessage message = 888 ERR_TRUSTSTORE_PIN_FILE_CANNOT_READ.get(pinFile, cfgEntryDN, getExceptionMessage(ioe)); 889 throw new InitializationException(message, ioe); 890 } 891 } 892 893 private String readPinFromFile2(File pinFile, DN cfgEntryDN, ConfigChangeResult ccr) 894 { 895 try (BufferedReader br = new BufferedReader(new FileReader(pinFile))) 896 { 897 return br.readLine(); 898 } 899 catch (IOException ioe) 900 { 901 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 902 ccr.addMessage(ERR_TRUSTSTORE_PIN_FILE_CANNOT_READ.get(pinFile, cfgEntryDN, getExceptionMessage(ioe))); 903 return null; 904 } 905 } 906 907 /** 908 * Create a new child DN from a given parent DN. The child RDN is formed 909 * from a given attribute type and string value. 910 * @param parentDN The DN of the parent. 911 * @param rdnAttrType The attribute type of the RDN. 912 * @param rdnStringValue The string value of the RDN. 913 * @return A new child DN. 914 */ 915 public static DN makeChildDN(DN parentDN, AttributeType rdnAttrType, 916 String rdnStringValue) 917 { 918 ByteString attrValue = ByteString.valueOfUtf8(rdnStringValue); 919 return parentDN.child(new RDN(rdnAttrType, attrValue)); 920 } 921 922 /** 923 * Retrieves a set of <CODE>KeyManager</CODE> objects that may be used for 924 * interactions requiring access to a key manager. 925 * 926 * @return A set of <CODE>KeyManager</CODE> objects that may be used for 927 * interactions requiring access to a key manager. 928 * 929 * @throws DirectoryException If a problem occurs while attempting to obtain 930 * the set of key managers. 931 */ 932 public KeyManager[] getKeyManagers() 933 throws DirectoryException 934 { 935 final KeyStore keyStore = loadKeyStore(); 936 937 try 938 { 939 String keyManagerAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); 940 KeyManagerFactory keyManagerFactory = 941 KeyManagerFactory.getInstance(keyManagerAlgorithm); 942 keyManagerFactory.init(keyStore, trustStorePIN); 943 return keyManagerFactory.getKeyManagers(); 944 } 945 catch (Exception e) 946 { 947 logger.traceException(e); 948 949 LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_CREATE_FACTORY.get( 950 trustStoreFile, getExceptionMessage(e)); 951 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 952 message, e); 953 } 954 } 955 956 private KeyStore loadKeyStore() throws DirectoryException 957 { 958 try (FileInputStream inputStream = new FileInputStream(getFileForPath(trustStoreFile))) 959 { 960 final KeyStore keyStore = KeyStore.getInstance(trustStoreType); 961 keyStore.load(inputStream, trustStorePIN); 962 return keyStore; 963 } 964 catch (Exception e) 965 { 966 LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_LOAD.get(trustStoreFile, getExceptionMessage(e)); 967 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e); 968 } 969 } 970 971 /** 972 * Retrieves a set of {@code TrustManager} objects that may be used 973 * for interactions requiring access to a trust manager. 974 * 975 * @return A set of {@code TrustManager} objects that may be used 976 * for interactions requiring access to a trust manager. 977 * 978 * @throws DirectoryException If a problem occurs while attempting 979 * to obtain the set of trust managers. 980 */ 981 public TrustManager[] getTrustManagers() 982 throws DirectoryException 983 { 984 KeyStore trustStore = loadKeyStore(); 985 986 try 987 { 988 String trustManagerAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); 989 TrustManagerFactory trustManagerFactory = 990 TrustManagerFactory.getInstance(trustManagerAlgorithm); 991 trustManagerFactory.init(trustStore); 992 return trustManagerFactory.getTrustManagers(); 993 } 994 catch (Exception e) 995 { 996 logger.traceException(e); 997 998 LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_CREATE_FACTORY.get( 999 trustStoreFile, getExceptionMessage(e)); 1000 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 1001 message, e); 1002 } 1003 } 1004 1005 /** 1006 * Returns the key associated with the given alias, using the trust 1007 * store pin to recover it. 1008 * 1009 * @param alias The alias name. 1010 * 1011 * @return The requested key, or null if the given alias does not exist 1012 * or does not identify a key-related entry. 1013 * 1014 * @throws DirectoryException If an error occurs while retrieving the key. 1015 */ 1016 public Key getKey(String alias) 1017 throws DirectoryException 1018 { 1019 KeyStore trustStore = loadKeyStore(); 1020 1021 try 1022 { 1023 return trustStore.getKey(alias, trustStorePIN); 1024 } 1025 catch (Exception e) 1026 { 1027 logger.traceException(e); 1028 1029 LocalizableMessage message = ERR_TRUSTSTORE_ERROR_READING_KEY.get( 1030 alias, trustStoreFile, getExceptionMessage(e)); 1031 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 1032 message, e); 1033 } 1034 } 1035 1036 private void addCertificate(Entry entry) 1037 throws DirectoryException 1038 { 1039 DN entryDN = entry.getName(); 1040 1041 // Make sure that the DN specifies a certificate alias. 1042 AttributeType t = DirectoryServer.getSchema().getAttributeType(ATTR_CRYPTO_KEY_ID); 1043 ByteString v = entryDN.rdn().getAttributeValue(t); 1044 if (v == null) 1045 { 1046 LocalizableMessage message = ERR_TRUSTSTORE_DN_DOES_NOT_SPECIFY_CERTIFICATE.get(entryDN); 1047 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message, getBaseDN(), null); 1048 } 1049 1050 String certAlias = v.toString(); 1051 try 1052 { 1053 if (certificateManager.aliasInUse(certAlias)) 1054 { 1055 LocalizableMessage message = ERR_TRUSTSTORE_ALIAS_IN_USE.get(entryDN); 1056 throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message); 1057 } 1058 1059 if (entry.hasObjectClass(DirectoryServer.getSchema().getObjectClass(OC_SELF_SIGNED_CERT_REQUEST))) 1060 { 1061 try 1062 { 1063 final KeyType keyType = KeyType.getTypeOrDefault(certAlias); 1064 certificateManager.generateSelfSignedCertificate( 1065 keyType, 1066 certAlias, 1067 getADSCertificateSubjectDN(keyType), 1068 getADSCertificateValidity()); 1069 } 1070 catch (Exception e) 1071 { 1072 LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_GENERATE_CERT.get( 1073 certAlias, trustStoreFile, getExceptionMessage(e)); 1074 throw new DirectoryException( 1075 DirectoryServer.getServerErrorResultCode(), message, e); 1076 } 1077 } 1078 else 1079 { 1080 List<Attribute> certAttrs = entry.getAttribute( 1081 ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE); 1082 if (certAttrs.isEmpty()) 1083 { 1084 LocalizableMessage message = 1085 ERR_TRUSTSTORE_ENTRY_MISSING_CERT_ATTR.get(entryDN, ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE); 1086 throw new DirectoryException( 1087 DirectoryServer.getServerErrorResultCode(), message); 1088 } 1089 if (certAttrs.size() != 1) 1090 { 1091 LocalizableMessage message = 1092 ERR_TRUSTSTORE_ENTRY_HAS_MULTIPLE_CERT_ATTRS.get(entryDN, ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE); 1093 throw new DirectoryException( 1094 DirectoryServer.getServerErrorResultCode(), message); 1095 } 1096 1097 Attribute certAttr = certAttrs.get(0); 1098 Iterator<ByteString> i = certAttr.iterator(); 1099 1100 if (!i.hasNext()) 1101 { 1102 LocalizableMessage message = 1103 ERR_TRUSTSTORE_ENTRY_MISSING_CERT_VALUE.get(entryDN, ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE); 1104 throw new DirectoryException( 1105 DirectoryServer.getServerErrorResultCode(), message); 1106 } 1107 1108 ByteString certBytes = i.next(); 1109 1110 if (i.hasNext()) 1111 { 1112 LocalizableMessage message = 1113 ERR_TRUSTSTORE_ENTRY_HAS_MULTIPLE_CERT_VALUES.get(entryDN, ATTR_CRYPTO_PUBLIC_KEY_CERTIFICATE); 1114 throw new DirectoryException( 1115 DirectoryServer.getServerErrorResultCode(), message); 1116 } 1117 1118 try 1119 { 1120 File tempDir = getFileForPath("config"); 1121 File tempFile = File.createTempFile(configuration.getBackendId(), 1122 certAlias, tempDir); 1123 try 1124 { 1125 try (FileOutputStream outputStream = new FileOutputStream(tempFile.getPath(), false)) 1126 { 1127 certBytes.copyTo(outputStream); 1128 } 1129 1130 certificateManager.addCertificate(certAlias, tempFile); 1131 } 1132 finally 1133 { 1134 tempFile.delete(); 1135 } 1136 } 1137 catch (IOException e) 1138 { 1139 LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_WRITE_CERT.get( 1140 certAlias, getExceptionMessage(e)); 1141 throw new DirectoryException( 1142 DirectoryServer.getServerErrorResultCode(), message, e); 1143 } 1144 } 1145 } 1146 catch (Exception e) 1147 { 1148 LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_ADD_CERT.get( 1149 certAlias, trustStoreFile, getExceptionMessage(e)); 1150 throw new DirectoryException( 1151 DirectoryServer.getServerErrorResultCode(), message, e); 1152 } 1153 } 1154 1155 private void deleteCertificate(DN entryDN) 1156 throws DirectoryException 1157 { 1158 // Make sure that the DN specifies a certificate alias. 1159 AttributeType t = DirectoryServer.getSchema().getAttributeType(ATTR_CRYPTO_KEY_ID); 1160 ByteString v = entryDN.rdn().getAttributeValue(t); 1161 if (v == null) 1162 { 1163 LocalizableMessage message = ERR_TRUSTSTORE_DN_DOES_NOT_SPECIFY_CERTIFICATE.get(entryDN); 1164 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message, getBaseDN(), null); 1165 } 1166 1167 String certAlias = v.toString(); 1168 try 1169 { 1170 if (!certificateManager.aliasInUse(certAlias)) 1171 { 1172 LocalizableMessage message = ERR_TRUSTSTORE_INVALID_BASE.get(entryDN); 1173 throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message); 1174 } 1175 1176 certificateManager.removeCertificate(certAlias); 1177 } 1178 catch (Exception e) 1179 { 1180 LocalizableMessage message = ERR_TRUSTSTORE_CANNOT_DELETE_CERT.get( 1181 certAlias, trustStoreFile, getExceptionMessage(e)); 1182 throw new DirectoryException( 1183 DirectoryServer.getServerErrorResultCode(), message, e); 1184 } 1185 } 1186 1187 /** 1188 * Returns the validity period to be used to generate the ADS certificate. 1189 * @return The validity period to be used to generate the ADS certificate. 1190 */ 1191 private static int getADSCertificateValidity() 1192 { 1193 return 20 * 365; 1194 } 1195 1196 /** 1197 * Returns the Subject DN to be used to generate the ADS certificate. 1198 * @return The Subject DN to be used to generate the ADS certificate. 1199 * @throws java.net.UnknownHostException If the server host name could not be 1200 * determined. 1201 */ 1202 private static String getADSCertificateSubjectDN(KeyType keyType) throws UnknownHostException 1203 { 1204 final String hostName = SetupUtils.getHostNameForCertificate(DirectoryServer.getServerRoot()); 1205 return "cn=" + Rdn.escapeValue(hostName) + ",O=OpenDJ " + keyType + " Certificate"; 1206 } 1207 1208 /** 1209 * Create a randomly generated password for a certificate keystore. 1210 * @return A randomly generated password for a certificate keystore. 1211 */ 1212 private static char[] createKeystorePassword() { 1213 int pwdLength = 50; 1214 char[] pwd = new char[pwdLength]; 1215 Random random = new Random(); 1216 for (int pos=0; pos < pwdLength; pos++) { 1217 int type = getRandomInt(random,3); 1218 char nextChar = getRandomChar(random,type); 1219 pwd[pos] = nextChar; 1220 } 1221 return pwd; 1222 } 1223 1224 private static char getRandomChar(Random random, int type) 1225 { 1226 char generatedChar; 1227 int next = random.nextInt(); 1228 int d; 1229 1230 switch (type) 1231 { 1232 case 0: 1233 // Will return a digit 1234 d = next % 10; 1235 if (d < 0) 1236 { 1237 d = d * -1; 1238 } 1239 generatedChar = (char) (d+48); 1240 break; 1241 case 1: 1242 // Will return a lower case letter 1243 d = next % 26; 1244 if (d < 0) 1245 { 1246 d = d * -1; 1247 } 1248 generatedChar = (char) (d + 97); 1249 break; 1250 default: 1251 // Will return a capital letter 1252 d = next % 26; 1253 if (d < 0) 1254 { 1255 d = d * -1; 1256 } 1257 generatedChar = (char) (d + 65) ; 1258 } 1259 1260 return generatedChar; 1261 } 1262 1263 private static int getRandomInt(Random random,int modulo) 1264 { 1265 return random.nextInt() & modulo; 1266 } 1267 1268 /** 1269 * Creates a PIN file on the specified path. 1270 * @param path the path where the PIN file will be created. 1271 * @param pin The PIN to store in the file. 1272 * @throws IOException if something goes wrong. 1273 */ 1274 public static void createPINFile(String path, String pin) 1275 throws IOException 1276 { 1277 try (final FileWriter file = new FileWriter(path); 1278 final PrintWriter out = new PrintWriter(file)) 1279 { 1280 out.println(pin); 1281 out.flush(); 1282 } 1283 1284 try { 1285 if (!FilePermission.setPermissions(new File(path), 1286 new FilePermission(0600))) 1287 { 1288 // Log a warning that the permissions were not set. 1289 logger.warn(WARN_TRUSTSTORE_SET_PERMISSIONS_FAILED, path); 1290 } 1291 } catch(DirectoryException e) { 1292 // Log a warning that the permissions were not set. 1293 logger.warn(WARN_TRUSTSTORE_SET_PERMISSIONS_FAILED, path); 1294 } 1295 } 1296 1297 /** 1298 * Generates a self-signed certificate with well-known alias if there is none. 1299 * @throws InitializationException If an error occurs while interacting with 1300 * the key store. 1301 */ 1302 private void generateInstanceCertificateIfAbsent() throws InitializationException 1303 { 1304 final String certAlias = ADS_CERTIFICATE_ALIAS; 1305 try 1306 { 1307 if (certificateManager.aliasInUse(certAlias)) 1308 { 1309 return; 1310 } 1311 } 1312 catch (Exception e) 1313 { 1314 LocalizableMessage message = 1315 ERR_TRUSTSTORE_CANNOT_ADD_CERT.get(certAlias, trustStoreFile, getExceptionMessage(e)); 1316 throw new InitializationException(message, e); 1317 } 1318 1319 try 1320 { 1321 final KeyType keyType = KeyType.getTypeOrDefault(certAlias); 1322 certificateManager.generateSelfSignedCertificate(keyType, certAlias, getADSCertificateSubjectDN(keyType), 1323 getADSCertificateValidity()); 1324 } 1325 catch (Exception e) 1326 { 1327 LocalizableMessage message = 1328 ERR_TRUSTSTORE_CANNOT_GENERATE_CERT.get(certAlias, trustStoreFile, getExceptionMessage(e)); 1329 throw new InitializationException(message, e); 1330 } 1331 } 1332}