1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.cms.security;
35
36 import info.magnolia.cms.core.Path;
37 import info.magnolia.cms.core.SystemProperty;
38 import info.magnolia.cms.exchange.ActivationManager;
39 import info.magnolia.context.MgnlContext;
40 import info.magnolia.objectfactory.Components;
41 import info.magnolia.repository.RepositoryConstants;
42
43 import java.io.File;
44 import java.io.FileInputStream;
45 import java.io.FileNotFoundException;
46 import java.io.FileWriter;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.io.OutputStream;
50 import java.security.DigestInputStream;
51 import java.security.DigestOutputStream;
52 import java.security.InvalidKeyException;
53 import java.security.KeyFactory;
54 import java.security.KeyPair;
55 import java.security.KeyPairGenerator;
56 import java.security.MessageDigest;
57 import java.security.NoSuchAlgorithmException;
58 import java.security.NoSuchProviderException;
59 import java.security.PrivateKey;
60 import java.security.PublicKey;
61 import java.security.Security;
62 import java.security.spec.InvalidKeySpecException;
63 import java.security.spec.PKCS8EncodedKeySpec;
64 import java.security.spec.X509EncodedKeySpec;
65 import java.text.SimpleDateFormat;
66 import java.util.Date;
67 import java.util.Properties;
68
69 import javax.crypto.BadPaddingException;
70 import javax.crypto.Cipher;
71 import javax.crypto.IllegalBlockSizeException;
72 import javax.crypto.NoSuchPaddingException;
73 import javax.jcr.RepositoryException;
74 import javax.jcr.Session;
75
76 import org.apache.commons.lang.StringUtils;
77 import org.bouncycastle.jce.provider.BouncyCastleProvider;
78 import org.mindrot.jbcrypt.BCrypt;
79 import org.slf4j.Logger;
80 import org.slf4j.LoggerFactory;
81
82
83
84
85
86
87 public class SecurityUtil {
88
89 private static final String PRIVATE_KEY = "key.private";
90 private static final String PUBLIC_KEY = "key.public";
91 private static final String KEY_LOCATION_PROPERTY = "magnolia.author.key.location";
92
93 public static final String SHA1 = "SHA-1";
94 public static final String MD5 = "MD5";
95
96
97
98
99
100 public static final String SHA256 = "SHA-256";
101 public static final String SHA384 = "SHA-384";
102 public static final String SHA512 = "SHA-512";
103
104
105
106
107
108 private static final String ALGORITHM = "RSA";
109
110 private static Logger log = LoggerFactory.getLogger(SecurityUtil.class);
111
112 static {
113 Security.addProvider(new BouncyCastleProvider());
114 }
115
116
117
118
119 public static boolean isAnonymous() {
120 User user = MgnlContext.getUser();
121 return user != null && UserManager.ANONYMOUS_USER.equals(user.getName());
122 }
123
124 public static boolean isAuthenticated() {
125 User user = MgnlContext.getUser();
126 return user != null && !UserManager.ANONYMOUS_USER.equals(user.getName());
127 }
128
129 public static String decrypt(String pass) throws SecurityException {
130 return decrypt(pass, getPublicKey());
131 }
132
133 public static String decrypt(String message, String encodedKey) throws SecurityException {
134 try {
135 if (StringUtils.isBlank(encodedKey)) {
136 throw new SecurityException("Activation key was not found. Please make sure your instance is correctly configured.");
137 }
138
139
140 byte[] binaryKey = hexToByteArray(encodedKey);
141
142
143 Cipher pkCipher = Cipher.getInstance(ALGORITHM, "BC");
144 try {
145
146 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(binaryKey);
147 KeyFactory kf = KeyFactory.getInstance(ALGORITHM, "BC");
148 PublicKey pk = kf.generatePublic(publicKeySpec);
149 pkCipher.init(Cipher.DECRYPT_MODE, pk);
150
151 } catch (InvalidKeySpecException e) {
152
153 PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(binaryKey);
154 KeyFactory kf = KeyFactory.getInstance(ALGORITHM, "BC");
155 PrivateKey pk = kf.generatePrivate(privateKeySpec);
156 pkCipher.init(Cipher.DECRYPT_MODE, pk);
157 }
158
159
160 String[] chunks = StringUtils.split(message, ";");
161 if (chunks == null) {
162 throw new SecurityException("The encrypted information is corrupted or incomplete. Please make sure someone is not trying to intercept or modify encrypted message.");
163 }
164 StringBuilder clearText = new StringBuilder();
165 for (String chunk : chunks) {
166 byte[] byteChunk = hexToByteArray(chunk);
167 clearText.append(new String(pkCipher.doFinal(byteChunk), "UTF-8"));
168 }
169 return clearText.toString();
170 } catch (NumberFormatException e) {
171 throw new SecurityException("The encrypted information is corrupted or incomplete. Please make sure someone is not trying to intercept or modify encrypted message.", e);
172 } catch (IOException e) {
173 throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
174 } catch (NoSuchAlgorithmException e) {
175 throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
176 } catch (NoSuchPaddingException e) {
177 throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
178 } catch (InvalidKeySpecException e) {
179 throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
180 } catch (InvalidKeyException e) {
181 throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
182 } catch (NoSuchProviderException e) {
183 throw new SecurityException("Failed to find encryption provider. Please use Java version with cryptography support.", e);
184 } catch (IllegalBlockSizeException e) {
185 throw new SecurityException("Failed to decrypt message. It might have been corrupted during transport.", e);
186 } catch (BadPaddingException e) {
187 throw new SecurityException("Failed to decrypt message. It might have been corrupted during transport.", e);
188 }
189
190 }
191
192 public static String encrypt(String pass) throws SecurityException {
193 String encodedKey = getPrivateKey();
194 return encrypt(pass, encodedKey);
195 }
196
197 public static String encrypt(String message, String encodedKey) {
198 try {
199
200
201 if (StringUtils.isBlank(encodedKey)) {
202 throw new SecurityException("Activation key was not found. Please make sure your instance is correctly configured.");
203 }
204 byte[] binaryKey = hexToByteArray(encodedKey);
205
206
207 Cipher pkCipher = Cipher.getInstance(ALGORITHM, "BC");
208 try {
209
210 PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(binaryKey);
211 KeyFactory kf = KeyFactory.getInstance(ALGORITHM, "BC");
212 PrivateKey pk = kf.generatePrivate(privateKeySpec);
213
214 pkCipher.init(Cipher.ENCRYPT_MODE, pk);
215 } catch (InvalidKeySpecException e) {
216
217 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(binaryKey);
218 KeyFactory kf = KeyFactory.getInstance(ALGORITHM, "BC");
219 PublicKey pk = kf.generatePublic(publicKeySpec);
220
221 pkCipher.init(Cipher.ENCRYPT_MODE, pk);
222 }
223
224
225 byte[] bytes = message.getBytes("UTF-8");
226
227 int start = 0;
228 StringBuilder chaos = new StringBuilder();
229 while (start < bytes.length) {
230 byte[] tmp = new byte[Math.min(bytes.length - start, binaryKey.length / 8)];
231 System.arraycopy(bytes, start, tmp, 0, tmp.length);
232 start += tmp.length;
233 byte[] encrypted = pkCipher.doFinal(tmp);
234 chaos.append(byteArrayToHex(encrypted));
235 chaos.append(";");
236 }
237 chaos.setLength(chaos.length() - 1);
238
239 return chaos.toString();
240
241 } catch (IOException e) {
242 throw new SecurityException("Failed to create authentication string. Please use Java version with cryptography support.", e);
243 } catch (NoSuchAlgorithmException e) {
244 throw new SecurityException("Failed to create authentication string. Please use Java version with cryptography support.", e);
245 } catch (NoSuchPaddingException e) {
246 throw new SecurityException("Failed to create authentication string. Please use Java version with cryptography support.", e);
247 } catch (InvalidKeySpecException e) {
248 throw new SecurityException("Failed to create authentication string. Please use Java version with cryptography support.", e);
249 } catch (InvalidKeyException e) {
250 throw new SecurityException("Failed to create authentication string. Please use Java version with cryptography support.", e);
251 } catch (NoSuchProviderException e) {
252 throw new SecurityException("Failed to find encryption provider. Please use Java version with cryptography support.", e);
253 } catch (IllegalBlockSizeException e) {
254 throw new SecurityException("Failed to encrypt string. Please use Java version with cryptography support.", e);
255 } catch (BadPaddingException e) {
256 throw new SecurityException("Failed to encrypt string. Please use Java version with cryptography support.", e);
257 }
258 }
259
260 public static String getPrivateKey() {
261 String path = SystemProperty.getProperty(KEY_LOCATION_PROPERTY);
262 checkPrivateKeyStoreExistence(path);
263 try {
264 Properties defaultProps = new Properties();
265 FileInputStream in = new FileInputStream(path);
266 defaultProps.load(in);
267 in.close();
268 return defaultProps.getProperty(PRIVATE_KEY);
269 } catch (FileNotFoundException e) {
270 throw new SecurityException("Failed to retrieve private key. Please make sure the key is located in " + path, e);
271 } catch (IOException e) {
272 throw new SecurityException("Failed to retrieve private key. Please make sure the key is located in " + path, e);
273 }
274 }
275
276 public static void updateKeys(MgnlKeyPair keys) {
277
278 if (keys.getPrivateKey() != null) {
279 String path = SystemProperty.getProperty(KEY_LOCATION_PROPERTY);
280 if (path == null) {
281 final String errorMsg = KEY_LOCATION_PROPERTY + " is not specified. Please set the location of the key store file in your 'magnolia.properties'.";
282 log.error(errorMsg);
283 throw new IllegalStateException(errorMsg);
284 }
285 try {
286 Properties defaultProps = new Properties();
287 defaultProps.put(PRIVATE_KEY, keys.getPrivateKey());
288 defaultProps.put(PUBLIC_KEY, keys.getPublicKey());
289 File keystore = new File(path);
290 File parentFile = keystore.getParentFile();
291 if (parentFile != null) {
292 parentFile.mkdirs();
293 }
294 FileWriter writer = new FileWriter(keystore);
295 String date = new SimpleDateFormat("dd.MMM.yyyy hh:mm").format(new Date());
296 defaultProps.store(writer, "generated " + date + " by " + MgnlContext.getUser().getName());
297 writer.close();
298 } catch (FileNotFoundException e) {
299 throw new SecurityException("Failed to store private key. Please make sure the key is located in " + path, e);
300 } catch (IOException e) {
301 throw new SecurityException("Failed to store private key. Please make sure the key is located in " + path, e);
302 }
303 }
304 try {
305 Session session = MgnlContext.getSystemContext().getJCRSession(RepositoryConstants.CONFIG);
306 session.getNode("/server/activation").setProperty("publicKey", keys.getPublicKey());
307 session.save();
308 } catch (RepositoryException e) {
309 throw new SecurityException("Failed to store public key.", e);
310 }
311 }
312
313 public static String getPublicKey() {
314 ActivationManager aman = Components.getComponentProvider().getComponent(ActivationManager.class);
315 return aman.getPublicKey();
316 }
317
318 private static final String HEX = "0123456789ABCDEF";
319
320 public static String byteArrayToHex(byte[] raw) {
321 if (raw == null) {
322 return null;
323 }
324 final StringBuilder hex = new StringBuilder(2 * raw.length);
325 for (final byte b : raw) {
326 hex.append(HEX.charAt((b & 0xF0) >> 4))
327 .append(HEX.charAt(b & 0x0F));
328 }
329 return hex.toString();
330 }
331
332 public static byte[] hexToByteArray(String s) {
333 byte[] b = new byte[s.length() / 2];
334 for (int i = 0; i < b.length; i++) {
335 int index = i * 2;
336 int v = Integer.parseInt(s.substring(index, index + 2), 16);
337 b[i] = (byte) v;
338 }
339 return b;
340 }
341
342 public static MgnlKeyPair generateKeyPair(int keyLength) throws NoSuchAlgorithmException {
343 KeyPairGenerator kgen = KeyPairGenerator.getInstance(ALGORITHM);
344 kgen.initialize(keyLength);
345 KeyPair key = kgen.genKeyPair();
346 return new MgnlKeyPair(byteArrayToHex(key.getPrivate().getEncoded()), byteArrayToHex(key.getPublic().getEncoded()));
347 }
348
349
350
351
352
353
354
355 public static String stripPasswordFromCacheLog(String log) {
356 String value = stripParameterFromCacheLog(log, "mgnlUserPSWD");
357 value = stripParameterFromCacheLog(value, "passwordConfirmation");
358 value = stripParameterFromCacheLog(value, "password");
359 return value;
360 }
361
362 public static String stripPasswordFromUrl(String url) {
363 if (StringUtils.isBlank(url)) {
364 return null;
365 }
366 String value = null;
367 value = StringUtils.substringBefore(url, "mgnlUserPSWD");
368 value = value + StringUtils.substringAfter(StringUtils.substringAfter(url, "mgnlUserPSWD"), "&");
369 return StringUtils.removeEnd(value, "&");
370 }
371
372 public static String stripParameterFromCacheLog(String log, String parameter) {
373 if (StringUtils.isBlank(log)) {
374 return null;
375 } else if (!StringUtils.contains(log, parameter)) {
376 return log;
377 }
378 String value = null;
379 value = StringUtils.substringBefore(log, parameter);
380 String afterString = StringUtils.substringAfter(log, parameter);
381 if (StringUtils.indexOf(afterString, " ") < StringUtils.indexOf(afterString, "}")) {
382 value = value + StringUtils.substringAfter(afterString, " ");
383 } else {
384 value = value + "}" + StringUtils.substringAfter(afterString, "}");
385 }
386 return value;
387 }
388
389 private static void checkPrivateKeyStoreExistence(final String path) throws SecurityException {
390 if (StringUtils.isBlank(path)) {
391 throw new SecurityException("Private key store path is either null or empty. Please, check [" + KEY_LOCATION_PROPERTY + "] value in magnolia.properties");
392 }
393 String absPath = Path.getAbsoluteFileSystemPath(path);
394 File keypair = new File(absPath);
395 if (!keypair.exists()) {
396 throw new SecurityException("Private key store doesn't exist at [" + keypair.getAbsolutePath() + "]. Please, ensure that [" + KEY_LOCATION_PROPERTY + "] actually points to the correct location");
397 }
398 }
399
400 public static String getBCrypt(String text) {
401
402
403 String hashed = BCrypt.hashpw(text, BCrypt.gensalt(12));
404 return hashed;
405 }
406
407 public static boolean matchBCrypted(String candidate, String hash) {
408
409
410 return BCrypt.checkpw(candidate, hash);
411 }
412
413
414
415
416 public static String getDigest(String data, String algorithm) throws NoSuchAlgorithmException {
417 MessageDigest md = MessageDigest.getInstance(algorithm);
418 md.reset();
419 return new String(md.digest(data.getBytes()));
420 }
421
422
423
424
425 public static byte[] getDigest(byte[] data, String algorithm) throws NoSuchAlgorithmException {
426 MessageDigest md = MessageDigest.getInstance(algorithm);
427 md.reset();
428 return md.digest(data);
429 }
430
431 public static DigestInputStream getDigestInputStream(InputStream stream) {
432 MessageDigest md;
433 try {
434 md = MessageDigest.getInstance(SecurityUtil.MD5);
435 md.reset();
436 return new DigestInputStream(stream, md);
437 } catch (NoSuchAlgorithmException e) {
438 throw new SecurityException("Couldn't digest with " + SecurityUtil.MD5 + " algorithm!");
439 }
440 }
441
442 public static DigestOutputStream getDigestOutputStream(OutputStream stream) {
443 MessageDigest md;
444 try {
445 md = MessageDigest.getInstance(SecurityUtil.MD5);
446 md.reset();
447 return new DigestOutputStream(stream, md);
448 } catch (NoSuchAlgorithmException e) {
449 throw new SecurityException("Couldn't digest with " + SecurityUtil.MD5 + " algorithm!");
450 }
451 }
452
453
454
455
456 public static String getSHA1Hex(byte[] data) {
457 try {
458 return byteArrayToHex(getDigest(data, SecurityUtil.SHA1));
459 } catch (NoSuchAlgorithmException e) {
460 throw new SecurityException("Couldn't digest with " + SecurityUtil.SHA1 + " algorithm!");
461 }
462 }
463
464 public static String getSHA1Hex(String data) {
465 return getSHA1Hex(data.getBytes());
466 }
467
468
469
470
471 public static String getMD5Hex(byte[] data) {
472 try {
473 return byteArrayToHex(getDigest(data, SecurityUtil.MD5));
474 } catch (NoSuchAlgorithmException e) {
475 throw new SecurityException("Couldn't digest with " + SecurityUtil.MD5 + " algorithm!");
476 }
477 }
478
479 public static String getMD5Hex(String data) {
480 return getMD5Hex(data.getBytes());
481 }
482
483 public static String getMD5Hex(DigestInputStream stream) {
484 return byteArrayToHex(stream.getMessageDigest().digest());
485 }
486
487 public static String getMD5Hex(DigestOutputStream stream) {
488 return byteArrayToHex(stream.getMessageDigest().digest());
489 }
490 }