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 Copyrighted [year] [name of copyright owner]".
013 *
014 * Copyright 2011-2016 ForgeRock AS.
015 */
016
017package org.forgerock.json.crypto.cli;
018
019import java.io.File;
020import java.io.FileNotFoundException;
021import java.io.IOException;
022import java.security.Key;
023import java.security.KeyStore;
024
025import org.apache.commons.cli.CommandLine;
026import org.apache.commons.cli.CommandLineParser;
027import org.apache.commons.cli.HelpFormatter;
028import org.apache.commons.cli.Options;
029import org.apache.commons.cli.ParseException;
030import org.apache.commons.cli.PosixParser;
031import org.forgerock.json.JsonValue;
032import org.forgerock.json.crypto.JsonEncryptFunction;
033import org.forgerock.json.crypto.JsonCryptoException;
034import org.forgerock.json.crypto.JsonDecryptFunction;
035import org.forgerock.json.crypto.simple.SimpleDecryptor;
036import org.forgerock.json.crypto.simple.SimpleEncryptor;
037import org.forgerock.json.crypto.simple.SimpleKeyStoreSelector;
038import org.forgerock.security.keystore.KeyStoreBuilder;
039import org.forgerock.security.keystore.KeyStoreType;
040import org.forgerock.util.Utils;
041
042import com.fasterxml.jackson.databind.ObjectMapper;
043
044/**
045 * Command-line interface to encrypt/decrypt.
046 */
047public class Main {
048
049    private static final ObjectMapper MAPPER = new ObjectMapper();
050    private static final Options OPTIONS; // Command line options
051
052    private static final String PROPERTIES_ALIAS_OPTION = "alias";
053    private static final String PROPERTIES_CIPHER_OPTION = "cipher";
054    private static final String DEFAULT_CIPHER = "AES/CBC/PKCS5Padding";
055    private static final String PROPERTIES_SRCJSON_OPTION = "srcjson";
056    private static final String PROPERTIES_DESTJSON_OPTION = "destjson";
057    private static final String PROPERTIES_KEYPASS_OPTION = "keypass";
058    private static final String PROPERTIES_KEYSTORE_OPTION = "keystore";
059    private static final String PROPERTIES_STOREPASS_OPTION = "storepass";
060    private static final String PROPERTIES_STORETYPE_OPTION = "storetype";
061    private static final String PROPERTIES_PROVIDERNAME_OPTION = "providername";
062    private static final String PROPERTIES_PROVIDERCLASS_OPTION = "providerclass";
063    private static final String PROPERTIES_PROVIDERARG_OPTION = "providerarg";
064    private static final String PROPERTIES_PROVIDERPATH_OPTION = "providerpath";
065    private static final String PROPERTIES_ENCRYPT_COMMAND = "encrypt";
066    private static final String PROPERTIES_DECRYPT_COMMAND = "decrypt";
067    private static final String PROPERTIES_HELP_COMMAND = "help";
068
069    private CommandLine cmd = null; // Command Line arguments
070
071    static {
072        OPTIONS = new Options();
073        OPTIONS.addOption(PROPERTIES_ENCRYPT_COMMAND, false,
074                "Encrypt input file");
075        OPTIONS.addOption(PROPERTIES_DECRYPT_COMMAND, false,
076                "Decrypt input file");
077        OPTIONS.addOption("h", PROPERTIES_HELP_COMMAND, false,
078                "Display help");
079
080
081        //Required encryption options
082        OPTIONS.addOption(PROPERTIES_ALIAS_OPTION, true,
083                "Cryptography key alias.");
084        OPTIONS.addOption(PROPERTIES_CIPHER_OPTION, true,
085                "Cipher algorithm. " + DEFAULT_CIPHER + " by default");
086        //Required input options
087        OPTIONS.addOption(PROPERTIES_SRCJSON_OPTION, true,
088                "Input JSON File");
089        //Optional output options
090        OPTIONS.addOption(PROPERTIES_DESTJSON_OPTION, true,
091                "Output JSON File");
092        //Required keystore options
093        OPTIONS.addOption(PROPERTIES_KEYSTORE_OPTION, true,
094                "KeyStore File");
095        OPTIONS.addOption(PROPERTIES_STOREPASS_OPTION, true,
096                "KeyStore password.");
097        OPTIONS.addOption(PROPERTIES_STORETYPE_OPTION, true,
098                "KeyStore type. Default: " + KeyStore.getDefaultType());
099        OPTIONS.addOption(PROPERTIES_KEYPASS_OPTION, true,
100                "Key password");
101        OPTIONS.addOption(PROPERTIES_PROVIDERNAME_OPTION, true,
102                "KeyStore provider");
103        OPTIONS.addOption(PROPERTIES_PROVIDERCLASS_OPTION, true,
104                "KeyStore provider class");
105        OPTIONS.addOption(PROPERTIES_PROVIDERARG_OPTION, true,
106                "KeyStore provider options");
107        OPTIONS.addOption(PROPERTIES_PROVIDERPATH_OPTION, true,
108                "KeyStore provider path");
109    }
110
111    /**
112     * Entry point.
113     * @param args CLI Args.
114     * @throws Exception On error.
115     */
116    public static void main(String[] args) throws Exception {
117        Main cliProg = new Main();
118        cliProg.loadArgs(args);
119        cliProg.exec();
120    }
121
122    /**
123     * Execute the CLI on the class instance.
124     * @throws Exception On error.
125     */
126    public void exec() throws Exception {
127        if (cmd.hasOption(PROPERTIES_ENCRYPT_COMMAND)) {
128            Key key = getSimpleKeySelector(cmd.getOptionValue(PROPERTIES_KEYSTORE_OPTION),
129                    cmd.getOptionValue(PROPERTIES_STORETYPE_OPTION, KeyStore.getDefaultType()),
130                    cmd.getOptionValue(PROPERTIES_STOREPASS_OPTION),
131                    cmd.getOptionValue(PROPERTIES_PROVIDERNAME_OPTION))
132                            .select(cmd.getOptionValue(PROPERTIES_ALIAS_OPTION));
133            if (key == null) {
134                throw new JsonCryptoException("key not found: " + cmd.getOptionValue(PROPERTIES_ALIAS_OPTION));
135            }
136            JsonEncryptFunction encrypt = new JsonEncryptFunction(new SimpleEncryptor(
137                    cmd.getOptionValue(PROPERTIES_CIPHER_OPTION, DEFAULT_CIPHER), key,
138                    cmd.getOptionValue(PROPERTIES_ALIAS_OPTION)));
139            JsonValue value = getSourceValue(cmd.getOptionValue(PROPERTIES_SRCJSON_OPTION), true);
140            setDestinationValue(cmd.getOptionValue(PROPERTIES_DESTJSON_OPTION), value.as(encrypt));
141        } else if (cmd.hasOption(PROPERTIES_DECRYPT_COMMAND)) {
142            JsonDecryptFunction decrypt = new JsonDecryptFunction(new SimpleDecryptor(
143                    getSimpleKeySelector(cmd.getOptionValue(PROPERTIES_KEYSTORE_OPTION),
144                            cmd.getOptionValue(PROPERTIES_STORETYPE_OPTION, KeyStore.getDefaultType()),
145                            cmd.getOptionValue(PROPERTIES_STOREPASS_OPTION),
146                            cmd.getOptionValue(PROPERTIES_PROVIDERNAME_OPTION))));
147            JsonValue value = getSourceValue(cmd.getOptionValue(PROPERTIES_SRCJSON_OPTION), true);
148            setDestinationValue(cmd.getOptionValue(PROPERTIES_DESTJSON_OPTION), value.as(decrypt));
149        } else {
150            usage();
151        }
152    }
153
154    private SimpleKeyStoreSelector getSimpleKeySelector(String keystore, String type, String password, String provider)
155            throws Exception {
156        final KeyStore ks = new KeyStoreBuilder()
157                .withKeyStoreFile(keystore)
158                .withPassword(password)
159                .withProvider(provider)
160                .withKeyStoreType(Utils.asEnum(type, KeyStoreType.class))
161                .build();
162        return new SimpleKeyStoreSelector(ks, password);
163    }
164
165    private JsonValue getSourceValue(String source, boolean file) throws IOException {
166        JsonValue src = null;
167        if (file) {
168            File srcFile = new File(source);
169            if (srcFile.exists()) {
170                src = new JsonValue(MAPPER.readValue(srcFile, Object.class));
171            } else {
172                throw new FileNotFoundException("JsonSource file not found at: " + srcFile.getAbsolutePath());
173            }
174        } else {
175            src = new JsonValue(MAPPER.readValue(source, Object.class));
176        }
177        return src;
178    }
179
180    private void setDestinationValue(String destination, JsonValue value) throws IOException {
181        if (null == destination) {
182            MAPPER.writeValue(System.out, value.getObject());
183        } else {
184            File dest = new File(destination);
185            dest.getParentFile().mkdirs();
186            MAPPER.writeValue(dest, value.getObject());
187        }
188    }
189
190    /**
191     * Validate and set command line arguments. Exit after printing usage if anything is
192     * astray.
193     *
194     * @param args String[] args as featured in public static void main()
195     */
196    private void loadArgs(String[] args) {
197        CommandLineParser parser = new PosixParser();
198        try {
199            cmd = parser.parse(OPTIONS, args);
200        } catch (ParseException e) {
201            System.err.println("Error parsing arguments");
202            e.printStackTrace();
203            System.exit(1);
204        }
205
206        if (cmd.hasOption('h')) {
207            usage();
208            System.exit(0);
209        }
210
211        // Check for mandatory args
212        if (cmd.hasOption(PROPERTIES_HELP_COMMAND)) {
213            usage();
214            System.exit(0);
215        }
216    }
217
218    private static void usage() {
219        HelpFormatter formatter = new HelpFormatter();
220        formatter.printHelp("java -jar json-crypto-1.0.0-command-line.jar", OPTIONS);
221    }
222}