/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2013-2015 ForgeRock AS. All Rights Reserved * * 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.impl; import static org.forgerock.json.resource.Responses.newResourceResponse; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; import org.apache.commons.lang3.tuple.Pair; import org.bouncycastle.jce.PKCS10CertificationRequest; import org.forgerock.json.JsonValueException; import org.forgerock.services.context.Context; import org.forgerock.json.JsonValue; import org.forgerock.json.resource.ActionRequest; import org.forgerock.json.resource.ActionResponse; import org.forgerock.json.resource.BadRequestException; import org.forgerock.json.resource.ConflictException; import org.forgerock.json.resource.InternalServerErrorException; import org.forgerock.json.resource.NotSupportedException; import org.forgerock.json.resource.PatchRequest; import org.forgerock.json.resource.ReadRequest; import org.forgerock.json.resource.ResourceException; import org.forgerock.json.resource.ResourceResponse; import org.forgerock.json.resource.Responses; import org.forgerock.json.resource.SingletonResourceProvider; import org.forgerock.json.resource.UpdateRequest; import org.forgerock.openidm.repo.RepositoryService; import org.forgerock.openidm.security.KeyStoreHandler; import org.forgerock.openidm.security.KeyStoreManager; import org.forgerock.openidm.util.ResourceUtil; import org.forgerock.util.promise.Promise; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Services requests on a specific keystore. */ public class KeystoreResourceProvider extends SecurityResourceProvider implements SingletonResourceProvider { /** * Setup logging for the {@link SecurityManager}. */ private final static Logger logger = LoggerFactory.getLogger(KeystoreResourceProvider.class); public KeystoreResourceProvider(String resourceName, KeyStoreHandler store, KeyStoreManager manager, RepositoryService repoService) { super(resourceName, store, manager, repoService); } @Override public Promise<ActionResponse, ResourceException> actionInstance(Context context, ActionRequest request) { try { String alias = request.getContent().get("alias").asString(); if (ACTION_GENERATE_CERT.equalsIgnoreCase(request.getAction()) || ACTION_GENERATE_CSR.equalsIgnoreCase(request.getAction())) { if (alias == null) { return new BadRequestException("A valid resource ID must be specified in the request").asPromise(); } String algorithm = request.getContent().get("algorithm").defaultTo(DEFAULT_ALGORITHM).asString(); String signatureAlgorithm = request.getContent().get("signatureAlgorithm") .defaultTo(DEFAULT_SIGNATURE_ALGORITHM).asString(); int keySize = request.getContent().get("keySize").defaultTo(DEFAULT_KEY_SIZE).asInteger(); JsonValue result = null; if (ACTION_GENERATE_CERT.equalsIgnoreCase(request.getAction())) { // Generate self-signed certificate if (store.getStore().containsAlias(alias)) { return new ConflictException("The resource with ID '" + alias + "' could not be created because there is already another resource with the same ID") .asPromise(); } else { logger.info("Generating a new self-signed certificate with the alias {}", alias); String domainName = request.getContent().get("domainName").required().asString(); String validFrom = request.getContent().get("validFrom").asString(); String validTo = request.getContent().get("validTo").asString(); // Generate the cert Pair<X509Certificate, PrivateKey> pair = generateCertificate(domainName, algorithm, keySize, signatureAlgorithm, validFrom, validTo); Certificate cert = pair.getKey(); PrivateKey key = pair.getValue(); // Add it to the store and reload logger.debug("Adding certificate entry under the alias {}", alias); store.getStore().setEntry(alias, new KeyStore.PrivateKeyEntry(key, new Certificate[]{cert}), new KeyStore.PasswordProtection(store.getPassword().toCharArray())); store.store(); manager.reload(); // Save the store to the repo (if clustered) saveStore(); result = returnCertificate(alias, cert); if (request.getContent().get("returnPrivateKey").defaultTo(false).asBoolean()) { result.put("privateKey", getKeyMap(key)); } } } else { // Generate CSR Pair<PKCS10CertificationRequest, PrivateKey> csr = generateCSR(alias, algorithm, signatureAlgorithm, keySize, request.getContent()); result = returnCertificateRequest(alias, csr.getKey()); if (request.getContent().get("returnPrivateKey").defaultTo(false).asBoolean()) { result.put("privateKey", getKeyMap(csr.getRight())); } } return Responses.newActionResponse(result).asPromise(); } else { return new BadRequestException("Unsupported action " + request.getAction()).asPromise(); } } catch (JsonValueException e) { return new BadRequestException(e.getMessage(), e).asPromise(); } catch (Exception e) { return new InternalServerErrorException(e).asPromise(); } } @Override public Promise<ResourceResponse, ResourceException> patchInstance(Context context, PatchRequest request) { return new NotSupportedException("Patch operations are not supported").asPromise(); } @Override public Promise<ResourceResponse, ResourceException> readInstance(final Context context, final ReadRequest request) { try { JsonValue content = new JsonValue(new LinkedHashMap<String, Object>(5)); content.put("type", store.getStore().getType()); content.put("provider", store.getStore().getProvider()); Enumeration<String> aliases = store.getStore().aliases(); List<String> aliasList = new ArrayList<>(); while (aliases.hasMoreElements()) { aliasList.add(aliases.nextElement()); } content.put("aliases", aliasList); return newResourceResponse(resourceName, null, content).asPromise(); } catch (KeyStoreException e) { return new InternalServerErrorException(e).asPromise(); } } @Override public Promise<ResourceResponse, ResourceException> updateInstance(Context context, UpdateRequest request) { return new NotSupportedException("Update operations are not supported").asPromise(); } }