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.cms.security.auth.ACL;
40 import info.magnolia.cms.security.auth.PrincipalCollectionImpl;
41 import info.magnolia.context.MgnlContext;
42 import info.magnolia.objectfactory.Components;
43 import info.magnolia.repository.RepositoryConstants;
44
45 import java.io.File;
46 import java.io.FileInputStream;
47 import java.io.FileNotFoundException;
48 import java.io.FileWriter;
49 import java.io.IOException;
50 import java.io.InputStream;
51 import java.io.OutputStream;
52 import java.security.DigestInputStream;
53 import java.security.DigestOutputStream;
54 import java.security.InvalidKeyException;
55 import java.security.KeyFactory;
56 import java.security.KeyPair;
57 import java.security.KeyPairGenerator;
58 import java.security.MessageDigest;
59 import java.security.NoSuchAlgorithmException;
60 import java.security.Principal;
61 import java.security.PrivateKey;
62 import java.security.PublicKey;
63 import java.security.spec.InvalidKeySpecException;
64 import java.security.spec.PKCS8EncodedKeySpec;
65 import java.security.spec.X509EncodedKeySpec;
66 import java.text.SimpleDateFormat;
67 import java.util.ArrayList;
68 import java.util.Collection;
69 import java.util.Date;
70 import java.util.HashSet;
71 import java.util.List;
72 import java.util.Properties;
73
74 import javax.crypto.BadPaddingException;
75 import javax.crypto.Cipher;
76 import javax.crypto.IllegalBlockSizeException;
77 import javax.crypto.NoSuchPaddingException;
78 import javax.jcr.RepositoryException;
79 import javax.jcr.Session;
80 import javax.security.auth.Subject;
81
82 import org.apache.commons.lang.StringUtils;
83 import org.bouncycastle.jce.provider.BouncyCastleProvider;
84 import org.mindrot.jbcrypt.BCrypt;
85 import org.slf4j.Logger;
86 import org.slf4j.LoggerFactory;
87
88
89
90
91
92
93 public class SecurityUtil {
94
95 private static final String PRIVATE_KEY = "key.private";
96 private static final String PUBLIC_KEY = "key.public";
97 private static final String KEY_LOCATION_PROPERTY = "magnolia.author.key.location";
98
99 public static final String SHA1 = "SHA-1";
100 public static final String MD5 = "MD5";
101
102
103
104
105
106 public static final String SHA256 = "SHA-256";
107 public static final String SHA384 = "SHA-384";
108 public static final String SHA512 = "SHA-512";
109
110
111
112
113
114 private static final String ALGORITHM = "RSA";
115
116 private static Logger log = LoggerFactory.getLogger(SecurityUtil.class);
117
118 private static final BouncyCastleProvider BC_PROVIDER = new BouncyCastleProvider();
119
120
121
122
123 public static boolean isAnonymous() {
124 User user = MgnlContext.getUser();
125 return user != null && UserManager.ANONYMOUS_USER.equals(user.getName());
126 }
127
128 public static boolean isAuthenticated() {
129 User user = MgnlContext.getUser();
130 return user != null && !UserManager.ANONYMOUS_USER.equals(user.getName());
131 }
132
133 public static String decrypt(String pass) throws SecurityException {
134 return decrypt(pass, getPublicKey());
135 }
136
137 public static String decrypt(String message, String encodedKey) throws SecurityException {
138 try {
139 if (StringUtils.isBlank(encodedKey)) {
140 throw new SecurityException("Activation key was not found. Please make sure your instance is correctly configured.");
141 }
142
143
144 byte[] binaryKey = hexToByteArray(encodedKey);
145
146
147 Cipher pkCipher = Cipher.getInstance(ALGORITHM, BC_PROVIDER);
148 try {
149
150 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(binaryKey);
151 KeyFactory kf = KeyFactory.getInstance(ALGORITHM, BC_PROVIDER);
152 PublicKey pk = kf.generatePublic(publicKeySpec);
153 pkCipher.init(Cipher.DECRYPT_MODE, pk);
154
155 } catch (InvalidKeySpecException e) {
156
157 PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(binaryKey);
158 KeyFactory kf = KeyFactory.getInstance(ALGORITHM, BC_PROVIDER);
159 PrivateKey pk = kf.generatePrivate(privateKeySpec);
160 pkCipher.init(Cipher.DECRYPT_MODE, pk);
161 }
162
163
164 String[] chunks = StringUtils.split(message, ";");
165 if (chunks == null) {
166 throw new SecurityException("The encrypted information is corrupted or incomplete. Please make sure someone is not trying to intercept or modify encrypted message.");
167 }
168 StringBuilder clearText = new StringBuilder();
169 for (String chunk : chunks) {
170 byte[] byteChunk = hexToByteArray(chunk);
171 clearText.append(new String(pkCipher.doFinal(byteChunk), "UTF-8"));
172 }
173 return clearText.toString();
174 } catch (NumberFormatException e) {
175 throw new SecurityException("The encrypted information is corrupted or incomplete. Please make sure someone is not trying to intercept or modify encrypted message.", e);
176 } catch (IOException e) {
177 throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
178 } catch (NoSuchAlgorithmException e) {
179 throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
180 } catch (NoSuchPaddingException e) {
181 throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
182 } catch (InvalidKeySpecException e) {
183 throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
184 } catch (InvalidKeyException e) {
185 throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
186 } catch (IllegalBlockSizeException e) {
187 throw new SecurityException("Failed to decrypt message. It might have been corrupted during transport.", e);
188 } catch (BadPaddingException e) {
189 throw new SecurityException("Failed to decrypt message. It might have been corrupted during transport.", e);
190 }
191
192 }
193
194 public static String encrypt(String pass) throws SecurityException {
195 String encodedKey = getPrivateKey();
196 return encrypt(pass, encodedKey);
197 }
198
199 public static String encrypt(String message, String encodedKey) {
200 try {
201
202
203 if (StringUtils.isBlank(encodedKey)) {
204 throw new SecurityException("Activation key was not found. Please make sure your instance is correctly configured.");
205 }
206 byte[] binaryKey = hexToByteArray(encodedKey);
207
208
209 Cipher pkCipher = Cipher.getInstance(ALGORITHM, BC_PROVIDER);
210 try {
211
212 PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(binaryKey);
213 KeyFactory kf = KeyFactory.getInstance(ALGORITHM, BC_PROVIDER);
214 PrivateKey pk = kf.generatePrivate(privateKeySpec);
215
216 pkCipher.init(Cipher.ENCRYPT_MODE, pk);
217 } catch (InvalidKeySpecException e) {
218
219 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(binaryKey);
220 KeyFactory kf = KeyFactory.getInstance(ALGORITHM, BC_PROVIDER);
221 PublicKey pk = kf.generatePublic(publicKeySpec);
222
223 pkCipher.init(Cipher.ENCRYPT_MODE, pk);
224 }
225
226
227 byte[] bytes = message.getBytes("UTF-8");
228
229 int start = 0;
230 StringBuilder chaos = new StringBuilder();
231 while (start < bytes.length) {
232 byte[] tmp = new byte[Math.min(bytes.length - start, binaryKey.length / 8)];
233 System.arraycopy(bytes, start, tmp, 0, tmp.length);
234 start += tmp.length;
235 byte[] encrypted = pkCipher.doFinal(tmp);
236 chaos.append(byteArrayToHex(encrypted));
237 chaos.append(";");
238 }
239 chaos.setLength(chaos.length() - 1);
240
241 return chaos.toString();
242
243 } catch (IOException e) {
244 throw new SecurityException("Failed to create authentication string. Please use Java version with cryptography support.", e);
245 } catch (NoSuchAlgorithmException e) {
246 throw new SecurityException("Failed to create authentication string. Please use Java version with cryptography support.", e);
247 } catch (NoSuchPaddingException e) {
248 throw new SecurityException("Failed to create authentication string. Please use Java version with cryptography support.", e);
249 } catch (InvalidKeySpecException e) {
250 throw new SecurityException("Failed to create authentication string. Please use Java version with cryptography support.", e);
251 } catch (InvalidKeyException e) {
252 throw new SecurityException("Failed to create authentication string. 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
491 public static Subject createSubjectAndPopulate(User user) {
492
493 RoleManager roleManager = Components.getComponent(SecuritySupport.class).getRoleManager();
494
495 List<Principal> acls = new ArrayList<Principal>();
496 for (String role : user.getAllRoles()) {
497 acls.addAll(roleManager.getACLs(role).values());
498 }
499
500 PrincipalCollectionImpl principalCollection = new PrincipalCollectionImpl();
501 mergePrincipals(principalCollection, acls);
502
503 Subject subject = new Subject();
504 subject.getPrincipals().add(user);
505 subject.getPrincipals().add(principalCollection);
506 return subject;
507 }
508
509 private static void mergePrincipals(PrincipalCollectionImpl principalCollection, List<Principal> acls) {
510 for (Principal principal : acls) {
511 ACL princ = (ACL) principal;
512 if (principalCollection.contains(princ.getName())) {
513 ACL oldACL = (ACL) principalCollection.get(princ.getName());
514 Collection<Permission> permissions = new HashSet<Permission>(oldACL.getList());
515 permissions.addAll(princ.getList());
516 principalCollection.remove(oldACL);
517 princ = new ACLImpl(princ.getName(), new ArrayList<Permission>(permissions));
518 }
519 principalCollection.add(princ);
520 }
521 }
522 }