3232import org .springframework .security .crypto .codec .Hex ;
3333import org .springframework .security .crypto .encrypt .TextEncryptor ;
3434import org .springframework .security .rsa .crypto .RsaKeyHolder ;
35+ import org .springframework .security .rsa .crypto .RsaSecretEncryptor ;
3536import org .springframework .util .Base64Utils ;
3637import org .springframework .web .bind .annotation .ExceptionHandler ;
3738import org .springframework .web .bind .annotation .PathVariable ;
4344
4445/**
4546 * @author Dave Syer
47+ * @author Tim Ysewyn
4648 *
4749 */
4850@ RestController
@@ -51,16 +53,16 @@ public class EncryptionController {
5153
5254 private static Log logger = LogFactory .getLog (EncryptionController .class );
5355
54- volatile private TextEncryptorLocator encryptor ;
56+ volatile private TextEncryptorLocator encryptorLocator ;
5557
5658 private EnvironmentPrefixHelper helper = new EnvironmentPrefixHelper ();
5759
5860 private String defaultApplicationName = "application" ;
5961
6062 private String defaultProfile = "default" ;
6163
62- public EncryptionController (TextEncryptorLocator encryptor ) {
63- this .encryptor = encryptor ;
64+ public EncryptionController (TextEncryptorLocator encryptorLocator ) {
65+ this .encryptorLocator = encryptorLocator ;
6466 }
6567
6668 public void setDefaultApplicationName (String defaultApplicationName ) {
@@ -73,82 +75,61 @@ public void setDefaultProfile(String defaultProfile) {
7375
7476 @ RequestMapping (value = "/key" , method = RequestMethod .GET )
7577 public String getPublicKey () {
76- TextEncryptor encryptor = this .encryptor
77- .locate (this .helper .getEncryptorKeys ("application" , "default" , "" ));
78- if (!(encryptor instanceof RsaKeyHolder )) {
79- throw new KeyNotAvailableException ();
80- }
81- return ((RsaKeyHolder ) encryptor ).getPublicKey ();
78+ return getPublicKey (defaultApplicationName , defaultProfile );
8279 }
8380
8481 @ RequestMapping (value = "/key/{name}/{profiles}" , method = RequestMethod .GET )
8582 public String getPublicKey (@ PathVariable String name , @ PathVariable String profiles ) {
86- TextEncryptor encryptor = this .encryptor
87- .locate (this .helper .getEncryptorKeys (name , profiles , "" ));
83+ TextEncryptor encryptor = getEncryptor (name , profiles , "" );
8884 if (!(encryptor instanceof RsaKeyHolder )) {
8985 throw new KeyNotAvailableException ();
9086 }
9187 return ((RsaKeyHolder ) encryptor ).getPublicKey ();
9288 }
9389
94- @ ExceptionHandler (KeyFormatException .class )
95- public ResponseEntity <Map <String , Object >> keyFormat () {
96- Map <String , Object > body = new HashMap <>();
97- body .put ("status" , "BAD_REQUEST" );
98- body .put ("description" , "Key data not in correct format (PEM or jks keystore)" );
99- return new ResponseEntity <>(body , HttpStatus .BAD_REQUEST );
100- }
101-
102- @ ExceptionHandler (KeyNotAvailableException .class )
103- public ResponseEntity <Map <String , Object >> keyUnavailable () {
104- Map <String , Object > body = new HashMap <>();
105- body .put ("status" , "NOT_FOUND" );
106- body .put ("description" , "No public key available" );
107- return new ResponseEntity <>(body , HttpStatus .NOT_FOUND );
108- }
109-
11090 @ RequestMapping (value = "encrypt/status" , method = RequestMethod .GET )
11191 public Map <String , Object > status () {
112- checkEncryptorInstalled ("application" , "default" );
92+ TextEncryptor encryptor = getEncryptor (defaultApplicationName , defaultProfile ,
93+ "" );
94+ validateEncryptionWeakness (encryptor );
11395 return Collections .singletonMap ("status" , "OK" );
11496 }
11597
11698 @ RequestMapping (value = "encrypt" , method = RequestMethod .POST )
11799 public String encrypt (@ RequestBody String data ,
118100 @ RequestHeader ("Content-Type" ) MediaType type ) {
119-
120- return encrypt (this .defaultApplicationName , this .defaultProfile , data , type );
101+ return encrypt (defaultApplicationName , defaultProfile , data , type );
121102 }
122103
123104 @ RequestMapping (value = "/encrypt/{name}/{profiles}" , method = RequestMethod .POST )
124105 public String encrypt (@ PathVariable String name , @ PathVariable String profiles ,
125106 @ RequestBody String data , @ RequestHeader ("Content-Type" ) MediaType type ) {
126- checkEncryptorInstalled (name , profiles );
107+ TextEncryptor encryptor = getEncryptor (name , profiles , "" );
108+ validateEncryptionWeakness (encryptor );
127109 String input = stripFormData (data , type , false );
128- Map <String , String > keys = this . helper .getEncryptorKeys (name , profiles , input );
129- String textToEncrypt = this . helper .stripPrefix (input );
130- String encrypted = this . helper .addPrefix (keys ,
131- this . encryptor .locate (keys ).encrypt (textToEncrypt ));
110+ Map <String , String > keys = helper .getEncryptorKeys (name , profiles , input );
111+ String textToEncrypt = helper .stripPrefix (input );
112+ String encrypted = helper .addPrefix (keys ,
113+ encryptorLocator .locate (keys ).encrypt (textToEncrypt ));
132114 logger .info ("Encrypted data" );
133115 return encrypted ;
134116 }
135117
136118 @ RequestMapping (value = "decrypt" , method = RequestMethod .POST )
137119 public String decrypt (@ RequestBody String data ,
138120 @ RequestHeader ("Content-Type" ) MediaType type ) {
139-
140- return decrypt (this .defaultApplicationName , this .defaultProfile , data , type );
121+ return decrypt (defaultApplicationName , defaultProfile , data , type );
141122 }
142123
143124 @ RequestMapping (value = "/decrypt/{name}/{profiles}" , method = RequestMethod .POST )
144125 public String decrypt (@ PathVariable String name , @ PathVariable String profiles ,
145126 @ RequestBody String data , @ RequestHeader ("Content-Type" ) MediaType type ) {
146- checkEncryptorInstalled (name , profiles );
127+ TextEncryptor encryptor = getEncryptor (name , profiles , "" );
128+ checkDecryptionPossible (encryptor );
129+ validateEncryptionWeakness (encryptor );
147130 try {
148- String input = stripFormData (this .helper .stripPrefix (data ), type , true );
149- Map <String , String > encryptorKeys = this .helper .getEncryptorKeys (name ,
150- profiles , data );
151- TextEncryptor encryptor = this .encryptor .locate (encryptorKeys );
131+ encryptor = getEncryptor (name , profiles , data );
132+ String input = stripFormData (helper .stripPrefix (data ), type , true );
152133 String decrypted = encryptor .decrypt (input );
153134 logger .info ("Decrypted cipher data" );
154135 return decrypted ;
@@ -159,16 +140,31 @@ public String decrypt(@PathVariable String name, @PathVariable String profiles,
159140 }
160141 }
161142
162- private void checkEncryptorInstalled (String name , String profiles ) {
163- if (this .encryptor == null ) {
143+ private TextEncryptor getEncryptor (String name , String profiles , String data ) {
144+ if (encryptorLocator == null ) {
145+ throw new KeyNotInstalledException ();
146+ }
147+ TextEncryptor encryptor = encryptorLocator
148+ .locate (helper .getEncryptorKeys (name , profiles , data ));
149+ if (encryptor == null ) {
164150 throw new KeyNotInstalledException ();
165151 }
166- if (this .encryptor .locate (this .helper .getEncryptorKeys (name , profiles , "" ))
167- .encrypt ("FOO" ).equals ("FOO" )) {
152+ return encryptor ;
153+ }
154+
155+ private void validateEncryptionWeakness (TextEncryptor textEncryptor ) {
156+ if (textEncryptor .encrypt ("FOO" ).equals ("FOO" )) {
168157 throw new EncryptionTooWeakException ();
169158 }
170159 }
171160
161+ private void checkDecryptionPossible (TextEncryptor textEncryptor ) {
162+ if (textEncryptor instanceof RsaSecretEncryptor
163+ && !((RsaSecretEncryptor ) textEncryptor ).canDecrypt ()) {
164+ throw new DecryptionNotSupportedException ();
165+ }
166+ }
167+
172168 private String stripFormData (String data , MediaType type , boolean cipher ) {
173169
174170 if (data .endsWith ("=" ) && !type .equals (MediaType .TEXT_PLAIN )) {
@@ -209,6 +205,30 @@ private String stripFormData(String data, MediaType type, boolean cipher) {
209205
210206 }
211207
208+ @ ExceptionHandler (KeyFormatException .class )
209+ public ResponseEntity <Map <String , Object >> keyFormat () {
210+ Map <String , Object > body = new HashMap <>();
211+ body .put ("status" , "BAD_REQUEST" );
212+ body .put ("description" , "Key data not in correct format (PEM or jks keystore)" );
213+ return new ResponseEntity <>(body , HttpStatus .BAD_REQUEST );
214+ }
215+
216+ @ ExceptionHandler (KeyNotAvailableException .class )
217+ public ResponseEntity <Map <String , Object >> keyUnavailable () {
218+ Map <String , Object > body = new HashMap <>();
219+ body .put ("status" , "NOT_FOUND" );
220+ body .put ("description" , "No public key available" );
221+ return new ResponseEntity <>(body , HttpStatus .NOT_FOUND );
222+ }
223+
224+ @ ExceptionHandler (DecryptionNotSupportedException .class )
225+ public ResponseEntity <Map <String , Object >> decryptionDisabled () {
226+ Map <String , Object > body = new HashMap <>();
227+ body .put ("status" , "BAD_REQUEST" );
228+ body .put ("description" , "Server-side decryption is not supported" );
229+ return new ResponseEntity <>(body , HttpStatus .BAD_REQUEST );
230+ }
231+
212232 @ ExceptionHandler (KeyNotInstalledException .class )
213233 public ResponseEntity <Map <String , Object >> notInstalled () {
214234 Map <String , Object > body = new HashMap <>();
@@ -254,3 +274,8 @@ class EncryptionTooWeakException extends RuntimeException {
254274class InvalidCipherException extends RuntimeException {
255275
256276}
277+
278+ @ SuppressWarnings ("serial" )
279+ class DecryptionNotSupportedException extends RuntimeException {
280+
281+ }
0 commit comments