/*
* 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 legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2014-2015 ForgeRock AS.
*/
package org.forgerock.openidm.security.impl;
import static org.forgerock.json.resource.test.assertj.AssertJActionResponseAssert.assertThat;
import org.apache.commons.lang3.StringUtils;
import org.forgerock.services.context.RootContext;
import org.forgerock.json.JsonValue;
import org.forgerock.json.resource.ActionRequest;
import org.forgerock.json.resource.ActionResponse;
import org.forgerock.json.resource.Connection;
import org.forgerock.json.resource.Requests;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.Resources;
import org.forgerock.json.resource.Router;
import org.forgerock.openidm.repo.RepositoryService;
import org.forgerock.openidm.security.KeyStoreHandler;
import org.forgerock.openidm.security.KeyStoreManager;
import org.forgerock.openidm.util.DateUtil;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import sun.misc.BASE64Encoder;
import sun.security.provider.X509Factory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.UnrecoverableEntryException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.HashMap;
import java.util.Map;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.forgerock.json.resource.Router.uriTemplate;
import static org.mockito.Mockito.mock;
public class KeystoreResourceProviderTest {
private final Router router = new Router();
private final Connection connection = Resources.newInternalConnection(router);
private final String KEYSTORE = "keystore";
private final String KEYSTORE_ROUTE = "security/keystore";
private final String KEYSTORE_TYPE = "JCEKS";
private final String KEYSTORE_PASSWORD = "changeit";
private final String TEST_CERT_ALIAS = "testCert";
private KeyStoreHandler keyStoreHandler;
private File keystoreFile;
private KeyStoreManager keyStoreManager;
private RepositoryService repositoryService;
private KeystoreResourceProvider keystoreResourceProvider;
private static class MockKeyStoreManager implements KeyStoreManager {
KeyStoreHandler keyStoreHandler;
KeyStoreHandler trustStoreHandler;
public MockKeyStoreManager(KeyStoreHandler keyStoreHandler, KeyStoreHandler trustStoreHandler) {
this.keyStoreHandler = keyStoreHandler;
this.trustStoreHandler = trustStoreHandler;
}
@Override
public void reload() throws Exception {
if (trustStoreHandler != null) {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStoreHandler.getStore());
}
if (keyStoreHandler != null) {
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStoreHandler.getStore(), keyStoreHandler.getPassword().toCharArray());
}
}
}
@BeforeMethod
public void setup() {
keyStoreManager = new MockKeyStoreManager(keyStoreHandler, null);
repositoryService = mock(RepositoryService.class);
keystoreResourceProvider =
new KeystoreResourceProvider(KEYSTORE, keyStoreHandler, keyStoreManager, repositoryService);
router.addRoute(uriTemplate(KEYSTORE_ROUTE), keystoreResourceProvider);
}
@AfterMethod
public void teardown() throws KeyStoreException {
router.removeAllRoutes();
keyStoreHandler.getStore().deleteEntry(TEST_CERT_ALIAS);
}
@BeforeClass
public void runInitalSetup() throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
keyStore.load(null, KEYSTORE_PASSWORD.toCharArray());
keystoreFile = File.createTempFile(KEYSTORE, KEYSTORE_TYPE);
FileOutputStream fileOutputStream = new FileOutputStream(keystoreFile);
keyStore.store(fileOutputStream, KEYSTORE_PASSWORD.toCharArray());
fileOutputStream.close();
keyStoreHandler = new JcaKeyStoreHandler(KEYSTORE_TYPE, keystoreFile.getAbsolutePath(), KEYSTORE_PASSWORD);
}
@AfterClass
public void runFinalTearDown() {
keystoreFile.delete();
router.removeAllRoutes();
connection.close();
}
@Test
public void testActionGenerateCertReturningPrivateKey()
throws ResourceException, KeyStoreException, CertificateEncodingException,
UnrecoverableEntryException, NoSuchAlgorithmException {
//given
ActionRequest actionRequest =
Requests.newActionRequest(KEYSTORE_ROUTE, keystoreResourceProvider.ACTION_GENERATE_CERT);
actionRequest.setContent(createGenerateCertActionContent(true));
//when
final ActionResponse result = connection.action(new RootContext(), actionRequest);
//then
assertThat(result).withContent().hasObject("privateKey");
checkResultForRequiredFields(result.getJsonContent());
checkKeyStoreEntry(result.getJsonContent());
}
@Test
public void testActionGenerateCertNotReturningPrivateKey()
throws ResourceException, NoSuchAlgorithmException, CertificateEncodingException,
UnrecoverableEntryException, KeyStoreException {
//given
ActionRequest actionRequest =
Requests.newActionRequest(KEYSTORE_ROUTE, keystoreResourceProvider.ACTION_GENERATE_CERT);
actionRequest.setContent(createGenerateCertActionContent(true));
//when
final ActionResponse result = connection.action(new RootContext(), actionRequest);
//then
assertThat(result).withContent().hasObject("privateKey");
checkResultForRequiredFields(result.getJsonContent());
checkKeyStoreEntry(result.getJsonContent());
}
@Test(expectedExceptions = ResourceException.class)
public void testActionGenerateCertWithoutAlias()
throws ResourceException, KeyStoreException, CertificateEncodingException,
UnrecoverableEntryException, NoSuchAlgorithmException {
//given
ActionRequest actionRequest =
Requests.newActionRequest(KEYSTORE_ROUTE, keystoreResourceProvider.ACTION_GENERATE_CERT);
final JsonValue content = createGenerateCertActionContent(true);
content.remove("alias");
actionRequest.setContent(content);
//when
connection.action(new RootContext(), actionRequest);
}
@Test(expectedExceptions = ResourceException.class)
public void testActionGenerateCertWithAliasAlreadyInUse()
throws ResourceException, KeyStoreException, CertificateEncodingException,
UnrecoverableEntryException, NoSuchAlgorithmException {
//given
ActionRequest actionRequest =
Requests.newActionRequest(KEYSTORE_ROUTE, keystoreResourceProvider.ACTION_GENERATE_CERT);
actionRequest.setContent(createGenerateCertActionContent(true));
//when
connection.action(new RootContext(), actionRequest);
connection.action(new RootContext(), actionRequest);
}
private JsonValue createGenerateCertActionContent(final boolean returnPrivateKey) {
final DateUtil dateUtil = DateUtil.getDateUtil();
final Map<String,Object> content = new HashMap<>();
content.put("alias", TEST_CERT_ALIAS);
content.put("algorithm", keystoreResourceProvider.DEFAULT_ALGORITHM);
content.put("signatureAlgorithm", keystoreResourceProvider.DEFAULT_SIGNATURE_ALGORITHM);
content.put("keySize", keystoreResourceProvider.DEFAULT_KEY_SIZE);
content.put("domainName","domainName");
content.put("validFrom", dateUtil.now());
content.put("validTo", dateUtil.parseIfDate(dateUtil.currentDateTime().plusDays(1).toDate().toString()));
content.put("returnPrivateKey", returnPrivateKey);
return new JsonValue(content);
}
private void checkResultForRequiredFields(final JsonValue result) {
assertThat(result != null && !result.isNull());
assertThat(!result.get("_id").isNull());
assertThat(!result.get("type").isNull());
assertThat(!result.get("cert").isNull());
assertThat(!result.get("publicKey").isNull());
}
private void checkKeyStoreEntry(final JsonValue result)
throws UnrecoverableEntryException, NoSuchAlgorithmException, KeyStoreException,
CertificateEncodingException {
KeyStore.PrivateKeyEntry privateKeyEntry =
(KeyStore.PrivateKeyEntry) keyStoreHandler
.getStore()
.getEntry(TEST_CERT_ALIAS, new KeyStore.PasswordProtection(KEYSTORE_PASSWORD.toCharArray()));
assertThat(privateKeyEntry != null);
Certificate certificate = privateKeyEntry.getCertificate();
assertThat(certificate != null);
final String certAsPEM = convertCertToPEM(certificate.getEncoded());
assertThat(certAsPEM != null || !StringUtils.isBlank(certAsPEM));
certAsPEM.equals(result.get("cert").asString());
PrivateKey privateKey = privateKeyEntry.getPrivateKey();
assertThat(privateKey != null);
}
private String convertCertToPEM(final byte[] encodedCert) {
final StringBuilder certAsPEM = new StringBuilder();
final BASE64Encoder encoder = new BASE64Encoder();
certAsPEM.append(X509Factory.BEGIN_CERT)
.append(new String (encoder.encodeBuffer(encodedCert)))
.append(X509Factory.END_CERT);
return certAsPEM.toString();
}
}