/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2011-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.shell.impl; import javax.crypto.spec.SecretKeySpec; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.io.StringWriter; import java.math.BigInteger; import java.security.KeyStore; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Scanner; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.felix.service.command.CommandSession; import org.apache.felix.service.command.Descriptor; import org.apache.felix.service.command.Parameter; import org.forgerock.json.JsonValue; import org.forgerock.json.crypto.JsonCryptoException; import org.forgerock.openidm.core.IdentityServer; import org.forgerock.openidm.core.ServerConstants; import org.forgerock.openidm.crypto.factory.CryptoServiceFactory; import org.forgerock.openidm.crypto.impl.CryptoServiceImpl; import org.forgerock.openidm.shell.CustomCommandScope; import org.forgerock.openidm.util.FileUtil; /** * Command scope for local operations. */ public class LocalCommandScope extends CustomCommandScope { private static final String DOTTED_PLACEHOLDER = "............................................"; private final ObjectMapper mapper = new ObjectMapper(); /** * {@inheritDoc} */ public Map<String, String> getFunctionMap() { Map<String, String> help = new HashMap<String, String>(); help.put("validate", getLongHeader("validate")); help.put("encrypt", getLongHeader("encrypt")); help.put("secureHash", getLongHeader("secureHash")); help.put("keytool", getLongHeader("keytool")); return help; } /** * {@inheritDoc} */ public String getScope() { return "local"; } /** * Run keytool to export/import a secret key entry. * * @param session the command session * @param doImport whether to import * @param doExport whether to export * @param alias the alias of the key */ @Descriptor("Export or import a SecretKeyEntry. " + "The Java Keytool does not allow for exporting or importing SecretKeyEntries.") public void keytool(CommandSession session, @Parameter(names = { "-i", "--import" }, presentValue = "true", absentValue = "false") boolean doImport, @Parameter(names = { "-e", "--export" }, presentValue = "true", absentValue = "false") boolean doExport, @Descriptor("key alias") String alias) { if (doImport ^ doExport) { String type = IdentityServer.getInstance().getProperty("openidm.keystore.type", KeyStore.getDefaultType()); String provider = IdentityServer.getInstance().getProperty("openidm.keystore.provider"); String location = IdentityServer.getInstance().getProperty("openidm.keystore.location"); try { KeyStore ks = (provider == null || provider.trim().length() == 0) ? KeyStore.getInstance(type) : KeyStore.getInstance(type, provider); if (location != null) { File configFile = IdentityServer.getFileForInstallPath(location); if (configFile.exists()) { FileInputStream in = null; try { in = new FileInputStream(configFile); if (null != in) { session.getConsole().append("Use KeyStore from: ") .println(configFile.getAbsolutePath()); // TODO Don't use the System in OSGi. char[] passwordArray = System.console().readPassword("Please enter the password: "); char[] passwordCopy = Arrays.copyOf(passwordArray, passwordArray.length); Arrays.fill(passwordArray, ' '); ks.load(in, passwordCopy); if (null != in) { in.close(); in = null; } if (doExport) { KeyStore.Entry key = ks.getEntry(alias, new KeyStore.PasswordProtection(passwordCopy)); if (key instanceof KeyStore.SecretKeyEntry) { KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) key; session.getConsole().append("[OK] ").println(secretKeyEntry); StringBuilder sb = new StringBuilder(secretKeyEntry.getSecretKey().getAlgorithm()); sb.append(":").append( new BigInteger(1, secretKeyEntry.getSecretKey().getEncoded()) .toString(16)); session.getConsole().println(sb); } else { session.getConsole().println( "SecretKeyEntry with this alias is not in KeyStore"); } } else if (doImport) { if (ks.containsAlias(alias)) { session.getConsole().println("KeyStore contains a key with this alias"); } else { session.getConsole().println("Enter the key: "); Scanner scanner = new Scanner(session.getKeyboard()); String[] tokens = scanner.nextLine().split(":"); if (tokens.length == 2) { byte[] encoded = new BigInteger(tokens[1], 16).toByteArray(); javax.crypto.SecretKey mySecretKey = new SecretKeySpec(encoded, tokens[0]); KeyStore.SecretKeyEntry skEntry = new KeyStore.SecretKeyEntry(mySecretKey); ks.setEntry(alias, skEntry, new KeyStore.PasswordProtection(passwordCopy)); FileOutputStream fos = null; try { fos = new FileOutputStream(configFile); ks.store(fos, passwordCopy); } finally { if (fos != null) { fos.close(); } } } else { session.getConsole().println("Invalid key input"); } } } } } catch (IOException ioe) { if (null != in) { in.close(); } } } else { session.getConsole() .append("KeyStore file: ") .append(configFile.getAbsolutePath()).println(" does not exists."); } } } catch (Exception e) { session.getConsole().println(e.getMessage()); } } else { session.getConsole().println("Import or Export have to be exclusively defined."); } } /** * Validate the json configuration files in the configuration folder. * * @param session command session */ @Descriptor("Validates all json configuration files in the configuration (default: /conf) folder.") public void validate(CommandSession session) { File file = IdentityServer.getFileForProjectPath("conf"); session.getConsole().println( "..................................................................."); if (file.isDirectory()) { session.getConsole().println("[Validating] Load JSON configuration files from:"); session.getConsole().append("[Validating] \t").println(file.getAbsolutePath()); FileFilter filter = new FileFilter() { public boolean accept(File f) { return (f.isDirectory()) || (f.getName().endsWith(".json")); } }; ObjectMapper mapper = new ObjectMapper(); File[] files = file.listFiles(filter); for (File subFile : files) { if (subFile.isDirectory()) { continue; } // TODO pretty print try { String json = FileUtil.readFile(subFile); JsonParser parser = mapper.getJsonFactory().createJsonParser(json); while (parser.nextToken() != null) { /* be happy */ } prettyPrint(session.getConsole(), subFile.getName(), null); } catch (Exception e) { prettyPrint(session.getConsole(), subFile.getName(), e); } } } else { session.getConsole().append("[Validating] ").append( "Configuration directory not found at: ").println(file.getAbsolutePath()); } } /** * Encrypt the input string. * * @param session command session * @param isString whether the input is a string */ @Descriptor("Encrypt the input string.") public void encrypt(CommandSession session, @Parameter(names = { "-j", "--json" }, presentValue = "false", absentValue = "true") boolean isString) { session.getConsole().append("Enter the ").append(isString ? "String" : "Json").println(" value"); encrypt(session, isString, loadFromConsole(session)); } /** * Encrypt the input string. * * @param session command session, * @param isString whether the input is a string * @param name the name of the string to encrypt */ @Descriptor("Encrypt the input string.") public void encrypt(CommandSession session, @Parameter(names = { "-j", "--json" }, presentValue = "false", absentValue = "true") boolean isString, @Descriptor("source string to encrypt") String name) { try { CryptoServiceImpl cryptoSvc = (CryptoServiceImpl) CryptoServiceFactory.getInstance(); cryptoSvc.activate(null); JsonValue value = new JsonValue(isString ? name : mapper.readValue(name, Object.class)); String cipher = ServerConstants.SECURITY_CRYPTOGRAPHY_DEFAULT_CIPHER; String alias = IdentityServer.getInstance().getProperty("openidm.config.crypto.alias", "openidm-config-default"); JsonValue secure = cryptoSvc.encrypt(value, cipher, alias); StringWriter wr = new StringWriter(); mapper.writerWithDefaultPrettyPrinter().writeValue(wr, secure.getObject()); session.getConsole().println("-----BEGIN ENCRYPTED VALUE-----"); session.getConsole().println(wr.toString()); session.getConsole().println("------END ENCRYPTED VALUE------"); } catch (JsonCryptoException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * Hashes the input string with a randomly generated salt. * * @param session command session * @param isString whether the input is a string */ @Descriptor("Hash the input string.") public void secureHash(CommandSession session, @Parameter(names = { "-j", "--json" }, presentValue = "false", absentValue = "true") boolean isString, @Parameter(names = { "-a", "--algorithm" }, absentValue = "SHA-256") String algorithm) { session.getConsole().append("Enter the ").append(isString ? "String" : "Json").println(" value"); secureHash(session, isString, algorithm, loadFromConsole(session)); } /** * Hashes the input string with a randomly generated salt. * * @param session command session, * @param isString whether the input is a string * @param stringValue the value of the string to hash */ @Descriptor("Hash the input string.") public void secureHash(CommandSession session, @Parameter(names = { "-j", "--json" }, presentValue = "false", absentValue = "true") boolean isString, @Parameter(names = { "-a", "--algorithm" }, absentValue = "SHA-256") String algorithm, @Descriptor("source string to hash") String stringValue) { try { CryptoServiceImpl cryptoSvc = (CryptoServiceImpl) CryptoServiceFactory.getInstance(); cryptoSvc.activate(null); JsonValue value = new JsonValue(isString ? stringValue : mapper.readValue(stringValue, Object.class)); JsonValue secure = cryptoSvc.hash(value, algorithm); StringWriter wr = new StringWriter(); mapper.writerWithDefaultPrettyPrinter().writeValue(wr, secure.getObject()); session.getConsole().println("-----BEGIN HASHED VALUE-----"); session.getConsole().println(wr.toString()); session.getConsole().println("------END HASHED VALUE------"); } catch (JsonCryptoException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } private void prettyPrint(PrintStream out, String name, Exception reason) { out.append("[Validating] ").append(name).append(" ").append( DOTTED_PLACEHOLDER.substring(Math.min(name.length(), DOTTED_PLACEHOLDER.length()))); if (null == reason) { out.println(" SUCCESS"); } else { out.println(" FAILED"); out.append("\t[").append(reason.getMessage()).println("]"); } } private String loadFromConsole(CommandSession session) { session.getConsole().println(); session.getConsole().println("> Press ctrl-D to finish input"); session.getConsole().println("Start data input:"); String input = null; StringBuilder stringBuilder = new StringBuilder(); Scanner scanner = new Scanner(session.getKeyboard()); while (scanner.hasNext()) { input = scanner.next(); if (null == input) { // control-D pressed break; } else { stringBuilder.append(input); } } session.getConsole().println(); return stringBuilder.toString(); } }