/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2013-2015 ForgeRock AS. * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * http://forgerock.org/license/CDDLv1.0.html * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at http://forgerock.org/license/CDDLv1.0.html * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" */ package org.forgerock.openidm.security; import static org.forgerock.json.resource.Router.uriTemplate; import java.security.Security; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.ConfigurationPolicy; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Properties; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.forgerock.services.context.Context; import org.forgerock.services.context.RootContext; import org.forgerock.json.resource.ActionRequest; import org.forgerock.json.resource.ActionResponse; import org.forgerock.json.resource.CreateRequest; import org.forgerock.json.resource.DeleteRequest; import org.forgerock.json.resource.InternalServerErrorException; import org.forgerock.json.resource.PatchRequest; import org.forgerock.json.resource.QueryRequest; import org.forgerock.json.resource.QueryResourceHandler; import org.forgerock.json.resource.QueryResponse; import org.forgerock.json.resource.ReadRequest; import org.forgerock.json.resource.RequestHandler; import org.forgerock.json.resource.Requests; import org.forgerock.json.resource.ResourceException; import org.forgerock.json.resource.ResourceResponse; import org.forgerock.json.resource.Router; import org.forgerock.json.resource.UpdateRequest; import org.forgerock.openidm.cluster.ClusterUtils; import org.forgerock.openidm.core.IdentityServer; import org.forgerock.openidm.core.ServerConstants; import org.forgerock.openidm.crypto.factory.CryptoUpdateService; import org.forgerock.openidm.jetty.Config; import org.forgerock.openidm.jetty.Param; import org.forgerock.openidm.repo.RepositoryService; import org.forgerock.openidm.security.impl.CertificateResourceProvider; import org.forgerock.openidm.security.impl.EntryResourceProvider; import org.forgerock.openidm.security.impl.JcaKeyStoreHandler; import org.forgerock.openidm.security.impl.KeystoreResourceProvider; import org.forgerock.openidm.security.impl.PrivateKeyResourceProvider; import org.forgerock.util.promise.Promise; import org.osgi.framework.Constants; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A Security Manager Service which handles operations on the java security * keystore and truststore files. */ @Component(name = SecurityManager.PID, policy = ConfigurationPolicy.IGNORE, metatype = true, description = "OpenIDM Security Management Service", immediate = true) @Service @Properties({ @Property(name = Constants.SERVICE_VENDOR, value = ServerConstants.SERVER_VENDOR_NAME), @Property(name = Constants.SERVICE_DESCRIPTION, value = "Security Management Service"), @Property(name = ServerConstants.ROUTER_PREFIX, value = "/security/*") }) public class SecurityManager implements RequestHandler, KeyStoreManager { public static final String PID = "org.forgerock.openidm.security"; /** * Setup logging for the {@link SecurityManager}. */ private final static Logger logger = LoggerFactory.getLogger(SecurityManager.class); @Reference protected RepositoryService repoService; @Reference private CryptoUpdateService cryptoUpdateService; private final Router router = new Router(); private KeyStoreHandler trustStoreHandler = null; private KeyStoreHandler keyStoreHandler = null; @Activate void activate(ComponentContext compContext) throws Exception { logger.debug("Activating Security Management Service {}", compContext); // Add the Bouncy Castle provider Security.addProvider(new BouncyCastleProvider()); String keyStoreType = Param.getKeystoreType(); String keyStoreLocation = Param.getKeystoreLocation(); String keyStorePassword = Param.getKeystorePassword(false); String trustStoreType = Param.getTruststoreType(); String trustStoreLocation = Param.getTruststoreLocation(); String trustStorePassword = Param.getTruststorePassword(false); // Set System properties if (System.getProperty("javax.net.ssl.keyStore") == null) { System.setProperty("javax.net.ssl.keyStore", keyStoreLocation); System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword); System.setProperty("javax.net.ssl.keyStoreType", keyStoreType); } if (System.getProperty("javax.net.ssl.trustStore") == null) { System.setProperty("javax.net.ssl.trustStore", trustStoreLocation); System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword); System.setProperty("javax.net.ssl.trustStoreType", trustStoreType); } keyStoreHandler = new JcaKeyStoreHandler(keyStoreType, keyStoreLocation, keyStorePassword); KeystoreResourceProvider keystoreProvider = new KeystoreResourceProvider("keystore", keyStoreHandler, this, repoService); EntryResourceProvider keystoreCertProvider = new CertificateResourceProvider("keystore", keyStoreHandler, this, repoService); EntryResourceProvider privateKeyProvider = new PrivateKeyResourceProvider("keystore", keyStoreHandler, this, repoService); router.addRoute(uriTemplate("/keystore"), keystoreProvider); router.addRoute(uriTemplate("/keystore/cert"), keystoreCertProvider); router.addRoute(uriTemplate("/keystore/privatekey"), privateKeyProvider); trustStoreHandler = new JcaKeyStoreHandler(trustStoreType, trustStoreLocation, trustStorePassword); KeystoreResourceProvider truststoreProvider = new KeystoreResourceProvider("truststore", trustStoreHandler, this, repoService); EntryResourceProvider truststoreCertProvider = new CertificateResourceProvider("truststore", trustStoreHandler, this, repoService); router.addRoute(uriTemplate("/truststore"), truststoreProvider); router.addRoute(uriTemplate("/truststore/cert"), truststoreCertProvider); String instanceType = IdentityServer.getInstance().getProperty("openidm.instance.type", ClusterUtils.TYPE_STANDALONE); String propValue = Param.getProperty("openidm.https.keystore.cert.alias"); String privateKeyAlias = (propValue == null) ? "openidm-localhost" : propValue; try { if (instanceType.equals(ClusterUtils.TYPE_CLUSTERED_ADDITIONAL)) { // Load keystore and truststore from the repository keystoreProvider.loadStoreFromRepo(); truststoreProvider.loadStoreFromRepo(); // Reload the SSL context reload(); // Update CryptoService cryptoUpdateService.updateKeySelector(keyStoreHandler.getStore(), keyStorePassword); } else { // Check if the default alias exists in keystore and truststore final boolean defaultPrivateKeyEntryExists = privateKeyProvider.hasEntry(privateKeyAlias); final boolean defaultTruststoreEntryExists = truststoreCertProvider.hasEntry(privateKeyAlias); if (!defaultPrivateKeyEntryExists && !defaultTruststoreEntryExists) { // dafault keystore/truststore entries do not exist // Create the default private key createDefaultKeystoreAndTruststoreEntries(privateKeyAlias, privateKeyProvider, keystoreCertProvider, truststoreCertProvider); // Reload the SSL context reload(); Config.updateConfig(null); } else if (!defaultPrivateKeyEntryExists && defaultTruststoreEntryExists) { // no default keystore entry, but truststore has default entry // this should only happen if the enduser is manually editing the keystore/truststore logger.error("Keystore and truststore out of sync. The keystore doesn't contain the default " + "entry, but the truststore does."); throw new InternalServerErrorException("Keystore and truststore out of sync. The keystore " + "doesn't contain the default entry, but the truststore does."); } else if (defaultPrivateKeyEntryExists && !defaultTruststoreEntryExists) { // default keystore entry exists, but truststore default entry does not exist // this should only happen if the enduser is manually editing the keystore/truststore logger.error("Keystore and truststore out of sync. The keystore contains the default entry, but " + "the truststore doesn't"); throw new InternalServerErrorException("Keystore and truststore out of sync. The keystore " + "contains the default entry, but the truststore doesn't"); } else { // the default entry exists in both the truststore and keystore // do nothing } // If this is the first/primary node in a cluster, then save the keystore and truststore to the repository if (instanceType.equals(ClusterUtils.TYPE_CLUSTERED_FIRST)) { keystoreProvider.saveStoreToRepo(); truststoreProvider.saveStoreToRepo(); } } } catch (Exception e) { logger.warn("Error initializing keys", e); } } @Deactivate void deactivate(ComponentContext compContext) { logger.debug("Deactivating Security Management Service {}", compContext); router.removeAllRoutes(); } // ----- Implementation of KeyStoreManager interface public void reload() throws Exception { TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStoreHandler.getStore()); TrustManager [] trustManagers = tmf.getTrustManagers(); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStoreHandler.getStore(), keyStoreHandler.getPassword().toCharArray()); KeyManager [] keyManagers = kmf.getKeyManagers(); SSLContext context = SSLContext.getInstance("SSL"); context.init(keyManagers, trustManagers, null); SSLContext.setDefault(context); } // ----- Implementation of RequestHandler interface @Override public Promise<ActionResponse, ResourceException> handleAction(final Context context, final ActionRequest request) { return router.handleAction(context, request); } @Override public Promise<ResourceResponse, ResourceException> handleCreate(final Context context, final CreateRequest request) { return router.handleCreate(context, request); } @Override public Promise<ResourceResponse, ResourceException> handleDelete(final Context context, final DeleteRequest request) { return router.handleDelete(context, request); } @Override public Promise<ResourceResponse, ResourceException> handlePatch(final Context context, final PatchRequest request) { return router.handlePatch(context, request); } @Override public Promise<QueryResponse, ResourceException> handleQuery(final Context context, final QueryRequest request, QueryResourceHandler queryResourceHandler) { return router.handleQuery(context, request, queryResourceHandler); } @Override public Promise<ResourceResponse, ResourceException> handleRead(final Context context, final ReadRequest request) { return router.handleRead(context, request); } @Override public Promise<ResourceResponse, ResourceException> handleUpdate(final Context context, final UpdateRequest request) { return router.handleUpdate(context, request); } private void createDefaultKeystoreAndTruststoreEntries(final String alias, final EntryResourceProvider privateKeyProvider, final EntryResourceProvider keystoreCertProvider, final EntryResourceProvider truststoreCertProvider) throws Exception { //create the keystore default entry privateKeyProvider.createDefaultEntry(alias); //get the keystore default entry cert final ReadRequest readRequest = Requests.newReadRequest("/keystore/cert"); Promise<ResourceResponse, ResourceException> result = keystoreCertProvider.readInstance(new RootContext(), alias, readRequest); //add the keystore default entry cert to the truststore final CreateRequest createRequest = Requests.newCreateRequest("/truststore/cert", alias, result.getOrThrow().getContent()); result = truststoreCertProvider.createInstance(new RootContext(), createRequest); result.getOrThrow(); } }