View Javadoc

1   /**
2    * This file Copyright (c) 2003-2011 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.cms.security;
35  
36  import info.magnolia.cms.core.SystemProperty;
37  import info.magnolia.cms.exchange.ActivationManager;
38  import info.magnolia.context.MgnlContext;
39  import info.magnolia.objectfactory.Components;
40  
41  import java.io.File;
42  import java.io.FileInputStream;
43  import java.io.FileNotFoundException;
44  import java.io.FileWriter;
45  import java.io.IOException;
46  import java.security.InvalidKeyException;
47  import java.security.KeyFactory;
48  import java.security.KeyPair;
49  import java.security.KeyPairGenerator;
50  import java.security.NoSuchAlgorithmException;
51  import java.security.NoSuchProviderException;
52  import java.security.PrivateKey;
53  import java.security.PublicKey;
54  import java.security.Security;
55  import java.security.spec.InvalidKeySpecException;
56  import java.security.spec.PKCS8EncodedKeySpec;
57  import java.security.spec.X509EncodedKeySpec;
58  import java.text.SimpleDateFormat;
59  import java.util.Date;
60  import java.util.Properties;
61  
62  import javax.crypto.BadPaddingException;
63  import javax.crypto.Cipher;
64  import javax.crypto.IllegalBlockSizeException;
65  import javax.crypto.NoSuchPaddingException;
66  import javax.jcr.RepositoryException;
67  import javax.jcr.Session;
68  
69  import org.apache.commons.lang.StringUtils;
70  import org.bouncycastle.jce.provider.BouncyCastleProvider;
71  
72  /**
73   * Utility functions required in the context of Security.
74   * 
75   * @version $Id: SecurityUtil.java 53379 2012-01-13 15:24:37Z ochytil $
76   */
77  public class SecurityUtil {
78  
79      private static final String PRIVATE_KEY = "key.private";
80      private static final String PUBLIC_KEY = "key.public";
81      private static final String KEY_LOCATION_PROPERTY = "magnolia.author.key.location";
82  
83      /**
84       * Encryption algorithm used ... if you are ever changing this, keep in mind underlying impl relies on padding!
85       */
86  
87      private static final String ALGORITHM = "RSA";
88  
89      static {
90          Security.addProvider(new BouncyCastleProvider());
91      }
92  
93      /**
94       * Checks if the currently acting user is anonymous.
95       */
96      public static boolean isAnonymous() {
97          User user = MgnlContext.getUser();
98          return (user != null && UserManager.ANONYMOUS_USER.equals(user.getName()));
99      }
100 
101     public static boolean isAuthenticated() {
102         User user = MgnlContext.getUser();
103         return (user != null && !UserManager.ANONYMOUS_USER.equals(user.getName()));
104     }
105 
106     public static String decrypt(String pass) throws SecurityException {
107         return decrypt(pass, getPublicKey());
108     }
109 
110     public static String decrypt(String message, String encodedKey) throws SecurityException {
111         try {
112             if (StringUtils.isBlank(encodedKey)) {
113                 throw new SecurityException("Activation key was not found. Please make sure your instance is correctly configured.");
114             }
115 
116             // decode key
117             byte[] binaryKey = hexToByteArray(encodedKey);
118 
119             // create RSA public key cipher
120             Cipher pkCipher = Cipher.getInstance(ALGORITHM, "BC");
121             try {
122                 // create private key
123                 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(binaryKey);
124                 KeyFactory kf = KeyFactory.getInstance(ALGORITHM, "BC");
125                 PublicKey pk = kf.generatePublic(publicKeySpec);
126                 pkCipher.init(Cipher.DECRYPT_MODE, pk);
127 
128             } catch (InvalidKeySpecException e) {
129                 // decrypting with private key?
130                 PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(binaryKey);
131                 KeyFactory kf = KeyFactory.getInstance(ALGORITHM, "BC");
132                 PrivateKey pk = kf.generatePrivate(privateKeySpec);
133                 pkCipher.init(Cipher.DECRYPT_MODE, pk);
134             }
135 
136             // decrypt
137             String[] chunks = StringUtils.split(message, ";");
138             if (chunks == null) {
139                 throw new SecurityException("The encrypted information is corrupted or incomplete. Please make sure someone is not trying to intercept or modify encrypted message.");
140             }
141             StringBuilder clearText = new StringBuilder();
142             for (String chunk : chunks) {
143                 byte[] byteChunk = hexToByteArray(chunk);
144                 clearText.append(new String(pkCipher.doFinal(byteChunk), "UTF-8"));
145             }
146             return clearText.toString();
147         } catch (NumberFormatException e) {
148             throw new SecurityException("The encrypted information is corrupted or incomplete. Please make sure someone is not trying to intercept or modify encrypted message.", e);
149         } catch (IOException e) {
150             throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
151         } catch (NoSuchAlgorithmException e) {
152             throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
153         } catch (NoSuchPaddingException e) {
154             throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
155         } catch (InvalidKeySpecException e) {
156             throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
157         } catch (InvalidKeyException e) {
158             throw new SecurityException("Failed to read authentication string. Please use Java version with cryptography support.", e);
159         } catch (NoSuchProviderException e) {
160             throw new SecurityException("Failed to find encryption provider. Please use Java version with cryptography support.", e);
161         } catch (IllegalBlockSizeException e) {
162             throw new SecurityException("Failed to decrypt message. It might have been corrupted during transport.", e);
163         } catch (BadPaddingException e) {
164             throw new SecurityException("Failed to decrypt message. It might have been corrupted during transport.", e);
165         }
166 
167     }
168 
169     public static String encrypt(String pass) throws SecurityException {
170         String encodedKey = getPrivateKey();
171         return encrypt(pass, encodedKey);
172     }
173 
174     public static String encrypt(String message, String encodedKey) {
175         try {
176 
177             // read private key
178             if (StringUtils.isBlank(encodedKey)) {
179                 throw new SecurityException("Activation key was not found. Please make sure your instance is correctly configured.");
180             }
181             byte[] binaryKey = hexToByteArray(encodedKey);
182 
183             // create RSA public key cipher
184             Cipher pkCipher = Cipher.getInstance(ALGORITHM, "BC");
185             try {
186                 // create private key
187                 PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(binaryKey);
188                 KeyFactory kf = KeyFactory.getInstance(ALGORITHM, "BC");
189                 PrivateKey pk = kf.generatePrivate(privateKeySpec);
190 
191                 pkCipher.init(Cipher.ENCRYPT_MODE, pk);
192             } catch (InvalidKeySpecException e) {
193                 // encrypting with public key?
194                 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(binaryKey);
195                 KeyFactory kf = KeyFactory.getInstance(ALGORITHM, "BC");
196                 PublicKey pk = kf.generatePublic(publicKeySpec);
197 
198                 pkCipher.init(Cipher.ENCRYPT_MODE, pk);
199             }
200 
201             // encrypt
202             byte[] bytes = message.getBytes("UTF-8");
203             // split bit message in chunks
204             int start = 0;
205             StringBuilder chaos = new StringBuilder();
206             while (start < bytes.length) {
207                 byte[] tmp = new byte[Math.min(bytes.length - start, binaryKey.length / 8)];
208                 System.arraycopy(bytes, start, tmp, 0, tmp.length);
209                 start += tmp.length;
210                 byte[] encrypted = pkCipher.doFinal(tmp);
211                 chaos.append(byteArrayToHex(encrypted));
212                 chaos.append(";");
213             }
214             chaos.setLength(chaos.length() - 1);
215 
216             return chaos.toString();
217 
218         } catch (IOException e) {
219             throw new SecurityException("Failed to create authentication string. Please use Java version with cryptography support.", e);
220         } catch (NoSuchAlgorithmException e) {
221             throw new SecurityException("Failed to create authentication string. Please use Java version with cryptography support.", e);
222         } catch (NoSuchPaddingException e) {
223             throw new SecurityException("Failed to create authentication string. Please use Java version with cryptography support.", e);
224         } catch (InvalidKeySpecException e) {
225             throw new SecurityException("Failed to create authentication string. Please use Java version with cryptography support.", e);
226         } catch (InvalidKeyException e) {
227             throw new SecurityException("Failed to create authentication string. Please use Java version with cryptography support.", e);
228         } catch (NoSuchProviderException e) {
229             throw new SecurityException("Failed to find encryption provider. Please use Java version with cryptography support.", e);
230         } catch (IllegalBlockSizeException e) {
231             throw new SecurityException("Failed to encrypt string. Please use Java version with cryptography support.", e);
232         } catch (BadPaddingException e) {
233             throw new SecurityException("Failed to encrypt string. Please use Java version with cryptography support.", e);
234         }
235     }
236 
237 
238     public static String getPrivateKey() {
239         String path = SystemProperty.getProperty("magnolia.author.key.location");
240         try {
241             Properties defaultProps = new Properties();
242             if (path == null || !new File(path).exists()) {
243                 throw new SecurityException("Private key store doesn't exist");
244             }
245             FileInputStream in = new FileInputStream(path);
246             defaultProps.load(in);
247             in.close();
248             return defaultProps.getProperty(PRIVATE_KEY);
249         } catch (FileNotFoundException e) {
250             throw new SecurityException("Failed to retrieve private key. Please make sure the key is located in " + path, e);
251         } catch (IOException e) {
252             throw new SecurityException("Failed to retrieve private key. Please make sure the key is located in " + path, e);
253         }
254     }
255 
256     public static void updateKeys(MgnlKeyPair keys) {
257         // update filestore only when private key is present
258         if (keys.getPrivateKey() != null) {
259             String path = SystemProperty.getProperty(KEY_LOCATION_PROPERTY);
260             try {
261                 Properties defaultProps = new Properties();
262                 defaultProps.put(PRIVATE_KEY, keys.getPrivateKey());
263                 defaultProps.put(PUBLIC_KEY, keys.getPublicKey());
264                 File keystore = new File(path);
265                 keystore.getParentFile().mkdirs();
266                 FileWriter writer = new FileWriter(keystore);
267                 String date = new SimpleDateFormat("dd.MMM.yyyy hh:mm").format(new Date());
268                 defaultProps.store(writer, "generated " + date + " by " + MgnlContext.getUser().getName());
269                 writer.close();
270             } catch (FileNotFoundException e) {
271                 throw new SecurityException("Failed to store private key. Please make sure the key is located in " + path, e);
272             } catch (IOException e) {
273                 throw new SecurityException("Failed to store private key. Please make sure the key is located in " + path, e);
274             }
275         }
276         try {
277             Session session = MgnlContext.getSystemContext().getJCRSession("config");
278             session.getNode("/server/activation").setProperty("publicKey", keys.getPublicKey());
279             session.save();
280         } catch (RepositoryException e) {
281             throw new SecurityException("Failed to store public key.", e);
282         }
283     }
284 
285     public static String getPublicKey() {
286         ActivationManager aman = Components.getComponentProvider().getComponent(ActivationManager.class);
287         return aman.getPublicKey();
288     }
289 
290     private static final String HEX = "0123456789ABCDEF";
291 
292     public static String byteArrayToHex(byte[] raw) {
293         if (raw == null) {
294             return null;
295         }
296         final StringBuilder hex = new StringBuilder(2 * raw.length);
297         for (final byte b : raw) {
298             hex.append(HEX.charAt((b & 0xF0) >> 4))
299             .append(HEX.charAt((b & 0x0F)));
300         }
301         return hex.toString();
302     }
303 
304     public static byte[] hexToByteArray(String s) {
305         byte[] b = new byte[s.length() / 2];
306         for (int i = 0; i < b.length; i++) {
307             int index = i * 2;
308             int v = Integer.parseInt(s.substring(index, index + 2), 16);
309             b[i] = (byte) v;
310         }
311         return b;
312     }
313 
314     public static MgnlKeyPair generateKeyPair(int keyLength) throws NoSuchAlgorithmException {
315         KeyPairGenerator kgen = KeyPairGenerator.getInstance(ALGORITHM);
316         kgen.initialize(keyLength);
317         KeyPair key = kgen.genKeyPair();
318         return new MgnlKeyPair(byteArrayToHex(key.getPrivate().getEncoded()), byteArrayToHex(key.getPublic().getEncoded()));
319     }
320 
321     /**
322      * Used for removing password parameter from cache key.
323      * @param cacheKey.toString()
324      * @return
325      */
326     public static String stripPasswordFromCacheLog(String log){
327         if(StringUtils.isBlank(log)){
328             return null;
329         }
330         String value = null;
331         value = StringUtils.substringBefore(log, "mgnlUserPSWD");
332         String afterString = StringUtils.substringAfter(log, "mgnlUserPSWD");
333         if(StringUtils.indexOf(afterString, " ") < StringUtils.indexOf(afterString, "}")){
334             value = value + StringUtils.substringAfter(afterString, " ");
335         }else{
336             value = value + "}" + StringUtils.substringAfter(afterString, "}");
337         }
338         return value;
339     }
340 
341     public static String stripPasswordFromUrl(String url){
342         if(StringUtils.isBlank(url)){
343             return null;
344         }
345         String value = null;
346         value = StringUtils.substringBefore(url, "mgnlUserPSWD");
347         value = value + StringUtils.substringAfter(StringUtils.substringAfter(url, "mgnlUserPSWD"), "&");
348         return StringUtils.removeEnd(value, "&");
349     }
350 }