001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.secrets;
019
020import io.stallion.boot.CommandOptionsBase;
021import io.stallion.services.Log;
022import io.stallion.settings.childSections.SecretsSettings;
023import io.stallion.utils.Prompter;
024import net.east301.keyring.BackendNotSupportedException;
025import net.east301.keyring.Keyring;
026import net.east301.keyring.PasswordRetrievalException;
027import net.east301.keyring.PasswordSaveException;
028import net.east301.keyring.util.LockException;
029import org.apache.commons.io.FileUtils;
030
031import java.io.File;
032import java.io.IOException;
033import java.text.MessageFormat;
034
035import static io.stallion.utils.Literals.*;
036
037
038public class SecretsCommandLineManager {
039
040    private String password = "";
041    private SecretsVault vault;
042    private String targetFolder = "";
043    private boolean keyringAccesible = false;
044    private boolean inKeyChain = false;
045    private String keyringServiceName = "";
046    private static final String keyringAccountName = "stallion";
047
048    private static final String lock = "            .-\"\"-.\n" +
049            "           / .--. \\\n" +
050            "          / /    \\ \\\n" +
051            "          | |    | |\n" +
052            "          | |.-\"\"-.|\n" +
053            "         ///`.::::.`\\\n" +
054            "        ||| ::/  \\:: ;\n" +
055            "        ||; ::\\__/:: ;\n" +
056            "         \\\\\\ '::::' /\n" +
057            "          `=':-..-'`";
058    public SecretsVault loadVault(String appPath, SecretsSettings secretsSettings) {
059        return loadVault(appPath, secretsSettings, "");
060    }
061
062    public SecretsVault loadVault(String appPath, SecretsSettings secretsSettings, String password) {
063
064        targetFolder = appPath;
065        keyringServiceName = "Stallion Secrets Key: " + targetFolder;
066
067        if (secretsSettings == null) {
068            Log.info("secretsSettings is null");
069        } else {
070            Log.info("Secrets passphrase file is {0}", secretsSettings.getPassPhraseFile());
071        }
072
073        if (empty(password)) {
074            password = findPasswordFromEnv();
075        }
076        if (empty(password)) {
077            password = findPasswordFromFile(secretsSettings);
078        }
079
080
081        if (empty(password)) {
082            password = findPasswordInKeyring();
083        }
084
085        if (empty(password)) {
086            password = new Prompter("What is your encryption password? If you are creating a secrets file for the first time," +
087                    "generate a new random string of at least 16 alpha-numeric characters, save it somewhere safe " +
088                    "(like your password manager), and then paste it in here. This password is not recoverable. If you lost it, " +
089                    "your secrets file will be locked forever.\nEnter your encryption password: ") {
090                @Override
091                public boolean validate(String line) {
092                    if (line.length() < 15) {
093                        this.lastErrorMessage = "Your password must be at least 20 characters.";
094                        return false;
095                    }
096                    return true;
097                }
098            }.prompt();
099        }
100
101
102        vault = new SecretsVault(targetFolder, password);
103
104        promptStorePassword(password);
105        return vault;
106    }
107
108    public void start(CommandOptionsBase options) {
109        System.out.print("\n\n" + lock + "\n\n");
110        System.out.println("Welcome to the Stallion Secrets Manager.\n\n You can view your secrets, create new secrets, change secrets, delete secrets");
111
112        targetFolder = options.getTargetPath();
113        loadVault(options.getTargetPath(), null);
114
115        while(true) {
116            String line = new Prompter("What do you want to do? Options: new/edit/delete/list/quit ")
117                    .setChoices("new", "edit", "delete", "list", "quit")
118                    .prompt();
119            switch (line) {
120                case "quit":
121                    return;
122                case "new":
123                    newSecret();
124                    break;
125                case "delete":
126                    deleteSecrets();
127                    break;
128                case "list":
129                    listSecrets();
130                    break;
131                case "edit":
132                    editSecret();
133                    break;
134                default:
135                    continue;
136            }
137        }
138
139    }
140
141    public String findPasswordFromEnv() {
142        return System.getenv("STALLION_SECRETS_PASSPHRASE");
143    }
144
145    public String findPasswordFromFile(SecretsSettings secretsSettings) {
146        if (secretsSettings == null) {
147            return "";
148        }
149        String path = or(secretsSettings.getPassPhraseFile(), "/usr/local/etc/stallion-secrets-passphrase");
150        Log.info("Looking for secrets pass phrase in file {0}", path);
151        File passPhraseFile = new File(path);
152
153        if (!passPhraseFile.exists()) {
154            return "";
155        }
156        try {
157            return FileUtils.readFileToString(passPhraseFile, UTF8).trim();
158        } catch (IOException e) {
159            throw new RuntimeException(e);
160        }
161    }
162
163
164    public String findPasswordInKeyring() {
165
166        Keyring keyring;
167        try {
168            keyring = Keyring.create();
169        } catch (BackendNotSupportedException ex) {
170            Log.info("Backend not supported for storing passphrase in Keyring");
171            return "";
172        }
173
174        keyringAccesible = true;
175
176
177        // some backend directory handles a file to store password to disks.
178        // in this case, we must set path to password store file by Keyring.setKeyStorePath
179        // before using Keyring.getPassword and Keyring.getPassword.
180        if (keyring.isKeyStorePathRequired()) {
181            try {
182                File keyStoreFile = File.createTempFile("keystore", ".keystore");
183                keyring.setKeyStorePath(keyStoreFile.getPath());
184            } catch (IOException ex) {
185                throw new RuntimeException(ex);
186            }
187        }
188
189        //
190        // Retrieve password from password store
191        //
192
193        // Password can be retrieved by using Keyring.getPassword method.
194        // PasswordRetrievalException is thrown when some error happened while getting password.
195        // LockException is thrown when keyring backend failed to lock password store file.
196        try {
197            String password = keyring.getPassword(keyringServiceName, keyringAccountName);
198            if (!empty(password)) {
199                inKeyChain = true;
200            }
201            return password;
202        } catch (LockException ex) {
203            throw new RuntimeException(ex);
204        } catch (PasswordRetrievalException ex) {
205            Log.info("Could not retrieve passphrase from the keychain");
206            return "";
207        }
208    }
209
210    public void promptStorePassword(String password) {
211        if (!keyringAccesible) {
212            return;
213        }
214        if (inKeyChain) {
215            return;
216        }
217        boolean shouldStore = new Prompter("Store this passphrase in your local keychain? (Y/n) ").yesNo();
218        if (!shouldStore) {
219            Log.info("Not should store!");
220            return;
221        }
222
223
224        Keyring keyring;
225        try {
226            keyring = Keyring.create();
227        } catch (BackendNotSupportedException ex) {
228            Log.info("Backend not supported for storing passphrase in Keyring");
229            return;
230        }
231
232        // some backend directory handles a file to store password to disks.
233        // in this case, we must set path to password store file by Keyring.setKeyStorePath
234        // before using Keyring.getPassword and Keyring.getPassword.
235        if (keyring.isKeyStorePathRequired()) {
236            try {
237                File keyStoreFile = File.createTempFile("keystore", ".keystore");
238                keyring.setKeyStorePath(keyStoreFile.getPath());
239            } catch (IOException ex) {
240                throw new RuntimeException(ex);
241            }
242        }
243
244        // Password can be stored to password store by using Keyring.setPassword method.
245        // PasswordSaveException is thrown when some error happened while saving password.
246        // LockException is thrown when keyring backend failed to lock password store file.
247        try {
248            keyring.setPassword(keyringServiceName, keyringAccountName, password);
249        } catch (LockException ex) {
250            throw new RuntimeException(ex);
251        } catch (PasswordSaveException ex) {
252            throw new RuntimeException(ex);
253        }
254
255
256    }
257
258    public void newSecret() {
259        String name = new Prompter("Secret name (You will use this name in your settings file to retrieve the secret): ")
260                .prompt();
261        String value = new Prompter("Secret value: ")
262                .prompt();
263        vault.add(name, value);
264        vault.save();
265        System.out.println("Secret added.");
266
267    }
268
269    public void editSecret() {
270        String name = new Prompter("Secret name: ")
271                .setChoices(vault.getSecretNames())
272                .prompt();
273        String value = new Prompter("Secret value: ")
274                .prompt();
275        vault.update(name, value);
276        vault.save();
277        System.out.println("Secret updated.");
278
279    }
280    public void listSecrets() {
281        MessageFormat format = new MessageFormat("{0}: {1}");
282        System.out.print("\n");
283        if (vault.getSecretNames().size() == 0) {
284            System.out.println("No secrets defined.");
285        }
286        for(String name: vault.getSecretNames()) {
287            System.out.println(format.format("{0}: {1}", name, vault.getSecret(name)));
288        }
289        System.out.print("\n");
290    }
291    public void deleteSecrets() {
292        String name = new Prompter("Secret name: ")
293                .setChoices(vault.getSecretNames())
294                .prompt();
295        vault.getSecrets().remove(name);
296        vault.save();
297        System.out.println("Secret deleted.");
298    }
299
300
301}