Add encrypt/decrypt commands (use -k to specify key)
The key format can be plaintext (symmetric key, not recommended but quick and dirty), or PEM encoded key data, or (for encryption only) a base64 encoded public key (like in ~/.ssh/id_rsa or like you get from the Spring Cloud configserver /key endpoint).
This commit is contained in:
@@ -38,15 +38,14 @@
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-rsa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-cli</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix-eureka-server</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -15,21 +15,23 @@
|
||||
*/
|
||||
package org.springframework.cloud.cli.command;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.boot.cli.command.Command;
|
||||
import org.springframework.boot.cli.command.CommandFactory;
|
||||
import org.springframework.cloud.cli.command.encrypt.DecryptCommand;
|
||||
import org.springframework.cloud.cli.command.encrypt.EncryptCommand;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class EncryptCommand implements CommandFactory {
|
||||
public class CloudCommandFactory implements CommandFactory {
|
||||
|
||||
@Override
|
||||
public Collection<Command> getCommands() {
|
||||
return Collections.emptySet();
|
||||
return Arrays.<Command>asList(new EncryptCommand(), new DecryptCommand());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.cli.command.encrypt;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import org.springframework.boot.cli.command.options.OptionHandler;
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.security.crypto.encrypt.TextEncryptor;
|
||||
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
|
||||
import org.springframework.security.rsa.crypto.RsaSecretEncryptor;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
class BaseEncryptOptionHandler extends OptionHandler {
|
||||
|
||||
private OptionSpec<String> keyOption;
|
||||
|
||||
private OptionSpec<String> aliasOption;
|
||||
|
||||
private OptionSpec<String> passwordOption;
|
||||
|
||||
private Charset charset;
|
||||
|
||||
{
|
||||
charset = Charset.forName("UTF-8");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void options() {
|
||||
this.keyOption = option(
|
||||
asList("key", "k"),
|
||||
"Specify key (symmetric secret, or pem-encoded key). If the value starts with @ it is interpreted as a file location.")
|
||||
.withRequiredArg();
|
||||
this.passwordOption = option("password",
|
||||
"A password for the keyfile (assuming the --key option is a KetStore file).")
|
||||
.withRequiredArg();
|
||||
this.aliasOption = option("alias",
|
||||
"An alias for the the key in a keyfile (assuming the --key option is a KetStore file).")
|
||||
.withRequiredArg();
|
||||
doOptions();
|
||||
}
|
||||
|
||||
protected void doOptions() {
|
||||
}
|
||||
|
||||
protected TextEncryptor createEncryptor(OptionSet options) {
|
||||
String value = keyOption.value(options);
|
||||
if (options.has(passwordOption)) { // it's a keystore
|
||||
String password = options.valueOf(passwordOption);
|
||||
String alias = options.valueOf(aliasOption);
|
||||
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new FileSystemResource(
|
||||
value), password.toCharArray());
|
||||
RsaSecretEncryptor encryptor = new RsaSecretEncryptor(
|
||||
factory.getKeyPair(alias));
|
||||
return encryptor;
|
||||
}
|
||||
boolean verbose = Boolean.getBoolean("debug");
|
||||
if (value.startsWith("@")) {
|
||||
value = readFile(value.substring(1));
|
||||
}
|
||||
try {
|
||||
value = readFile(value);
|
||||
if (verbose) {
|
||||
int len = Math.min(100, Math.max(value.length(), value.indexOf("\n")));
|
||||
Log.info("File contents:\n" + value.substring(0, len) + "...");
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
// not a file
|
||||
}
|
||||
return new EncryptorFactory(verbose).create(value.trim());
|
||||
}
|
||||
|
||||
private String readFile(String filename) {
|
||||
try {
|
||||
return StreamUtils.copyToString(new FileInputStream(new File(filename)),
|
||||
charset);
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.cli.command.encrypt;
|
||||
|
||||
import joptsimple.OptionSet;
|
||||
|
||||
import org.springframework.boot.cli.command.OptionParsingCommand;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.security.crypto.encrypt.TextEncryptor;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class DecryptCommand extends OptionParsingCommand {
|
||||
|
||||
public DecryptCommand() {
|
||||
super("decrypt", "Decrypt a string previsouly encrypted with the same key (or key pair)",
|
||||
new DecryptOptionHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return "[options] <text>";
|
||||
}
|
||||
|
||||
private static class DecryptOptionHandler extends BaseEncryptOptionHandler {
|
||||
|
||||
@Override
|
||||
protected synchronized ExitStatus run(OptionSet options) throws Exception {
|
||||
TextEncryptor encryptor = createEncryptor(options);
|
||||
String text = StringUtils.collectionToDelimitedString(
|
||||
options.nonOptionArguments(), " ");
|
||||
if (text.startsWith("{cipher}")) {
|
||||
text = text.substring("{cipher}".length());
|
||||
}
|
||||
System.out.println(encryptor.decrypt(text));
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.cli.command.encrypt;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import org.springframework.boot.cli.command.OptionParsingCommand;
|
||||
import org.springframework.boot.cli.command.status.ExitStatus;
|
||||
import org.springframework.security.crypto.encrypt.TextEncryptor;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class EncryptCommand extends OptionParsingCommand {
|
||||
|
||||
public EncryptCommand() {
|
||||
super("encrypt", "Encrypt a string so, for isntance, it can be added to source control",
|
||||
new EncryptOptionHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsageHelp() {
|
||||
return "[options] <text>";
|
||||
}
|
||||
|
||||
private static class EncryptOptionHandler extends BaseEncryptOptionHandler {
|
||||
|
||||
private OptionSpec<String> propertyOption;
|
||||
|
||||
@Override
|
||||
protected void doOptions() {
|
||||
this.propertyOption = option(
|
||||
asList("property", "p"),
|
||||
"A name for the encrypted value. Output will be in a form that can be pasted in to a properties file.")
|
||||
.withRequiredArg();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized ExitStatus run(OptionSet options) throws Exception {
|
||||
TextEncryptor encryptor = createEncryptor(options);
|
||||
String text = StringUtils.collectionToDelimitedString(
|
||||
options.nonOptionArguments(), " ");
|
||||
System.out.println(formatCipher(options, encryptor.encrypt(text)));
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
|
||||
protected String formatCipher(OptionSet options, String output) {
|
||||
if (options.has(propertyOption)) {
|
||||
output = options.valueOf(propertyOption).replace(":", "\\:")
|
||||
.replace("=", "\\=").replace(" ", "\\ ")
|
||||
+ "={cipher}" + output;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.cli.command.encrypt;
|
||||
|
||||
import org.springframework.boot.cli.util.Log;
|
||||
import org.springframework.security.crypto.encrypt.Encryptors;
|
||||
import org.springframework.security.crypto.encrypt.TextEncryptor;
|
||||
import org.springframework.security.rsa.crypto.ExtendedKeyHelper;
|
||||
import org.springframework.security.rsa.crypto.RsaSecretEncryptor;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class EncryptorFactory {
|
||||
|
||||
// TODO: expose as config property
|
||||
private static final String SALT = "deadbeef";
|
||||
|
||||
private final boolean verbose;
|
||||
|
||||
public EncryptorFactory() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public EncryptorFactory(boolean verbose) {
|
||||
this.verbose = verbose;
|
||||
}
|
||||
|
||||
public TextEncryptor create(String data) {
|
||||
|
||||
TextEncryptor encryptor = null;
|
||||
try {
|
||||
encryptor = new RsaSecretEncryptor(data);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
if (verbose) {
|
||||
Log.info("Could not create RSA Encryptor (" + e.getMessage() + ")");
|
||||
}
|
||||
}
|
||||
if (encryptor == null) {
|
||||
if (verbose) {
|
||||
Log.info("Trying public key");
|
||||
}
|
||||
try {
|
||||
encryptor = new RsaSecretEncryptor(ExtendedKeyHelper.parsePublicKey(data));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
if (verbose) {
|
||||
Log.info("Could not create public key RSA Encryptor ("
|
||||
+ e.getMessage() + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (encryptor == null) {
|
||||
if (verbose) {
|
||||
Log.info("Trying symmetric key");
|
||||
}
|
||||
encryptor = Encryptors.text(data, SALT);
|
||||
}
|
||||
if (encryptor == null) {
|
||||
if (verbose) {
|
||||
Log.error("Could not create any Encryptor");
|
||||
}
|
||||
throw new KeyFormatException();
|
||||
}
|
||||
|
||||
return encryptor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.cloud.cli.command.encrypt;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class KeyFormatException extends RuntimeException {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.rsa.crypto;
|
||||
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ExtendedKeyHelper extends RsaKeyHelper {
|
||||
public static RSAPublicKey parsePublicKey(String key) {
|
||||
return RsaKeyHelper.parsePublicKey(key);
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
org.springframework.cloud.cli.command.EncryptCommand
|
||||
org.springframework.cloud.cli.command.CloudCommandFactory
|
||||
|
||||
Reference in New Issue
Block a user