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>
|
</dependencyManagement>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-rsa</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-cli</artifactId>
|
<artifactId>spring-boot-cli</artifactId>
|
||||||
<version>${spring-boot.version}</version>
|
<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>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
@@ -15,21 +15,23 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.cloud.cli.command;
|
package org.springframework.cloud.cli.command;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import org.springframework.boot.cli.command.Command;
|
import org.springframework.boot.cli.command.Command;
|
||||||
import org.springframework.boot.cli.command.CommandFactory;
|
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
|
* @author Dave Syer
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class EncryptCommand implements CommandFactory {
|
public class CloudCommandFactory implements CommandFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Command> getCommands() {
|
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