Merge branch '2.1.x'
This commit is contained in:
@@ -32,6 +32,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.security.crypto.encrypt.TextEncryptor;
|
||||
import org.springframework.security.rsa.crypto.RsaKeyHolder;
|
||||
import org.springframework.security.rsa.crypto.RsaSecretEncryptor;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@@ -43,6 +44,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Tim Ysewyn
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@@ -51,7 +53,7 @@ public class EncryptionController {
|
||||
|
||||
private static Log logger = LogFactory.getLog(EncryptionController.class);
|
||||
|
||||
volatile private TextEncryptorLocator encryptor;
|
||||
volatile private TextEncryptorLocator encryptorLocator;
|
||||
|
||||
private EnvironmentPrefixHelper helper = new EnvironmentPrefixHelper();
|
||||
|
||||
@@ -59,8 +61,8 @@ public class EncryptionController {
|
||||
|
||||
private String defaultProfile = "default";
|
||||
|
||||
public EncryptionController(TextEncryptorLocator encryptor) {
|
||||
this.encryptor = encryptor;
|
||||
public EncryptionController(TextEncryptorLocator encryptorLocator) {
|
||||
this.encryptorLocator = encryptorLocator;
|
||||
}
|
||||
|
||||
public void setDefaultApplicationName(String defaultApplicationName) {
|
||||
@@ -73,62 +75,42 @@ public class EncryptionController {
|
||||
|
||||
@RequestMapping(value = "/key", method = RequestMethod.GET)
|
||||
public String getPublicKey() {
|
||||
TextEncryptor encryptor = this.encryptor
|
||||
.locate(this.helper.getEncryptorKeys("application", "default", ""));
|
||||
if (!(encryptor instanceof RsaKeyHolder)) {
|
||||
throw new KeyNotAvailableException();
|
||||
}
|
||||
return ((RsaKeyHolder) encryptor).getPublicKey();
|
||||
return getPublicKey(defaultApplicationName, defaultProfile);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/key/{name}/{profiles}", method = RequestMethod.GET)
|
||||
public String getPublicKey(@PathVariable String name, @PathVariable String profiles) {
|
||||
TextEncryptor encryptor = this.encryptor
|
||||
.locate(this.helper.getEncryptorKeys(name, profiles, ""));
|
||||
TextEncryptor encryptor = getEncryptor(name, profiles, "");
|
||||
if (!(encryptor instanceof RsaKeyHolder)) {
|
||||
throw new KeyNotAvailableException();
|
||||
}
|
||||
return ((RsaKeyHolder) encryptor).getPublicKey();
|
||||
}
|
||||
|
||||
@ExceptionHandler(KeyFormatException.class)
|
||||
public ResponseEntity<Map<String, Object>> keyFormat() {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("status", "BAD_REQUEST");
|
||||
body.put("description", "Key data not in correct format (PEM or jks keystore)");
|
||||
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ExceptionHandler(KeyNotAvailableException.class)
|
||||
public ResponseEntity<Map<String, Object>> keyUnavailable() {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("status", "NOT_FOUND");
|
||||
body.put("description", "No public key available");
|
||||
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "encrypt/status", method = RequestMethod.GET)
|
||||
public Map<String, Object> status() {
|
||||
checkEncryptorInstalled("application", "default");
|
||||
TextEncryptor encryptor = getEncryptor(defaultApplicationName, defaultProfile,
|
||||
"");
|
||||
validateEncryptionWeakness(encryptor);
|
||||
return Collections.singletonMap("status", "OK");
|
||||
}
|
||||
|
||||
@RequestMapping(value = "encrypt", method = RequestMethod.POST)
|
||||
public String encrypt(@RequestBody String data,
|
||||
@RequestHeader("Content-Type") MediaType type) {
|
||||
|
||||
return encrypt(this.defaultApplicationName, this.defaultProfile, data, type);
|
||||
return encrypt(defaultApplicationName, defaultProfile, data, type);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/encrypt/{name}/{profiles}", method = RequestMethod.POST)
|
||||
public String encrypt(@PathVariable String name, @PathVariable String profiles,
|
||||
@RequestBody String data, @RequestHeader("Content-Type") MediaType type) {
|
||||
checkEncryptorInstalled(name, profiles);
|
||||
TextEncryptor encryptor = getEncryptor(name, profiles, "");
|
||||
validateEncryptionWeakness(encryptor);
|
||||
String input = stripFormData(data, type, false);
|
||||
Map<String, String> keys = this.helper.getEncryptorKeys(name, profiles, input);
|
||||
String textToEncrypt = this.helper.stripPrefix(input);
|
||||
String encrypted = this.helper.addPrefix(keys,
|
||||
this.encryptor.locate(keys).encrypt(textToEncrypt));
|
||||
Map<String, String> keys = helper.getEncryptorKeys(name, profiles, input);
|
||||
String textToEncrypt = helper.stripPrefix(input);
|
||||
String encrypted = helper.addPrefix(keys,
|
||||
encryptorLocator.locate(keys).encrypt(textToEncrypt));
|
||||
logger.info("Encrypted data");
|
||||
return encrypted;
|
||||
}
|
||||
@@ -136,19 +118,18 @@ public class EncryptionController {
|
||||
@RequestMapping(value = "decrypt", method = RequestMethod.POST)
|
||||
public String decrypt(@RequestBody String data,
|
||||
@RequestHeader("Content-Type") MediaType type) {
|
||||
|
||||
return decrypt(this.defaultApplicationName, this.defaultProfile, data, type);
|
||||
return decrypt(defaultApplicationName, defaultProfile, data, type);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/decrypt/{name}/{profiles}", method = RequestMethod.POST)
|
||||
public String decrypt(@PathVariable String name, @PathVariable String profiles,
|
||||
@RequestBody String data, @RequestHeader("Content-Type") MediaType type) {
|
||||
checkEncryptorInstalled(name, profiles);
|
||||
TextEncryptor encryptor = getEncryptor(name, profiles, "");
|
||||
checkDecryptionPossible(encryptor);
|
||||
validateEncryptionWeakness(encryptor);
|
||||
try {
|
||||
String input = stripFormData(this.helper.stripPrefix(data), type, true);
|
||||
Map<String, String> encryptorKeys = this.helper.getEncryptorKeys(name,
|
||||
profiles, data);
|
||||
TextEncryptor encryptor = this.encryptor.locate(encryptorKeys);
|
||||
encryptor = getEncryptor(name, profiles, data);
|
||||
String input = stripFormData(helper.stripPrefix(data), type, true);
|
||||
String decrypted = encryptor.decrypt(input);
|
||||
logger.info("Decrypted cipher data");
|
||||
return decrypted;
|
||||
@@ -159,16 +140,31 @@ public class EncryptionController {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkEncryptorInstalled(String name, String profiles) {
|
||||
if (this.encryptor == null) {
|
||||
private TextEncryptor getEncryptor(String name, String profiles, String data) {
|
||||
if (encryptorLocator == null) {
|
||||
throw new KeyNotInstalledException();
|
||||
}
|
||||
if (this.encryptor.locate(this.helper.getEncryptorKeys(name, profiles, ""))
|
||||
.encrypt("FOO").equals("FOO")) {
|
||||
TextEncryptor encryptor = encryptorLocator
|
||||
.locate(helper.getEncryptorKeys(name, profiles, data));
|
||||
if (encryptor == null) {
|
||||
throw new KeyNotInstalledException();
|
||||
}
|
||||
return encryptor;
|
||||
}
|
||||
|
||||
private void validateEncryptionWeakness(TextEncryptor textEncryptor) {
|
||||
if (textEncryptor.encrypt("FOO").equals("FOO")) {
|
||||
throw new EncryptionTooWeakException();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDecryptionPossible(TextEncryptor textEncryptor) {
|
||||
if (textEncryptor instanceof RsaSecretEncryptor
|
||||
&& !((RsaSecretEncryptor) textEncryptor).canDecrypt()) {
|
||||
throw new DecryptionNotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
private String stripFormData(String data, MediaType type, boolean cipher) {
|
||||
|
||||
if (data.endsWith("=") && !type.equals(MediaType.TEXT_PLAIN)) {
|
||||
@@ -209,6 +205,30 @@ public class EncryptionController {
|
||||
|
||||
}
|
||||
|
||||
@ExceptionHandler(KeyFormatException.class)
|
||||
public ResponseEntity<Map<String, Object>> keyFormat() {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("status", "BAD_REQUEST");
|
||||
body.put("description", "Key data not in correct format (PEM or jks keystore)");
|
||||
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ExceptionHandler(KeyNotAvailableException.class)
|
||||
public ResponseEntity<Map<String, Object>> keyUnavailable() {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("status", "NOT_FOUND");
|
||||
body.put("description", "No public key available");
|
||||
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
@ExceptionHandler(DecryptionNotSupportedException.class)
|
||||
public ResponseEntity<Map<String, Object>> decryptionDisabled() {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("status", "BAD_REQUEST");
|
||||
body.put("description", "Server-side decryption is not supported");
|
||||
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
@ExceptionHandler(KeyNotInstalledException.class)
|
||||
public ResponseEntity<Map<String, Object>> notInstalled() {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
@@ -254,3 +274,8 @@ class EncryptionTooWeakException extends RuntimeException {
|
||||
class InvalidCipherException extends RuntimeException {
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
class DecryptionNotSupportedException extends RuntimeException {
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.config.server.encryption;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -23,7 +24,11 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.cloud.config.server.ConfigServerApplication;
|
||||
import org.springframework.cloud.config.server.test.ConfigServerTestUtils;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
@@ -93,4 +98,46 @@ public class EncryptionIntegrationTests {
|
||||
|
||||
}
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = { ConfigServerApplication.class }, properties = {
|
||||
"spring.cloud.bootstrap.name:keystore-bootstrap",
|
||||
"spring.cloud.config.server.encrypt.enabled=false",
|
||||
"encrypt.keyStore.alias=myencryptionkey" }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles({ "test", "git" })
|
||||
@DirtiesContext
|
||||
public static class KeystoreConfigurationEncryptionOnlyIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate testRestTemplate;
|
||||
|
||||
@BeforeClass
|
||||
public static void setupTest() throws Exception {
|
||||
ConfigServerTestUtils.prepareLocalRepo("./", "target/repos", "encrypt-repo",
|
||||
"target/config");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldOnlySupportEncryption() {
|
||||
ResponseEntity<String> entity = this.testRestTemplate
|
||||
.getForEntity("/keystore-bootstrap/encrypt", String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(entity.getBody()).contains(
|
||||
"{cipher}{key:mytestkey}AQCohs2V6P8/UiG6a4TF/CZTCBdt5Q7wvNvcyf6vs2ByK2ZYSM77Nu0sOAduxUpMbVwJ/syecmkIXR+hU3EfT2uqPieA7/v5n33ppqIQ9JAt5JggdYIGe+wX25zU3DTXOOJdAAMzNX+zjOVyCh0QtmJf/kFslg6NqQq0E+kSg3zBi3AnkKj5BLnLIxkjxzKA4mnDXpSm7ekLZZP2iQSYSW/82AC7UOLLzTqwInMI3tJLW1e9Ne+LDsjmSxA+nkK9zhidtXPwb/SPaNF74cJCEf9mgzzKYwJlwqChLzJt8UQ1jHwRc8B6FufmizUHSp27nxdtVB4HMqh3nNsMCy137Ces58T09ZS/y/cYNRxcFbp78MHFHUqAgbC0B/p5t6h4XbQ=");
|
||||
|
||||
HttpEntity<String> encryptionRequest = new HttpEntity<>("valueToBeEncrypted");
|
||||
entity = this.testRestTemplate.postForEntity("/encrypt", encryptionRequest,
|
||||
String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
|
||||
HttpHeaders decryptionRequestHeaders = new HttpHeaders();
|
||||
decryptionRequestHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
HttpEntity<String> decryptionRequest = new HttpEntity<>(entity.getBody(),
|
||||
decryptionRequestHeaders);
|
||||
entity = this.testRestTemplate.postForEntity("/decrypt", decryptionRequest,
|
||||
String.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user