001/*
002 *  Copyright 2018 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.messagingconnector;
017
018import java.io.BufferedReader;
019import java.io.File;
020import java.io.FileReader;
021import java.io.FileWriter;
022import java.io.IOException;
023import java.io.UnsupportedEncodingException;
024import java.security.InvalidKeyException;
025import java.security.Key;
026import java.security.MessageDigest;
027import java.security.NoSuchAlgorithmException;
028import java.time.ZonedDateTime;
029import java.util.Base64;
030
031import javax.crypto.Cipher;
032import javax.crypto.NoSuchPaddingException;
033import javax.crypto.spec.SecretKeySpec;
034
035import org.apache.avalon.framework.component.Component;
036import org.apache.commons.codec.digest.Sha2Crypt;
037import org.apache.commons.lang3.StringUtils;
038
039import org.ametys.runtime.plugin.component.AbstractLogEnabled;
040import org.ametys.runtime.util.AmetysHomeHelper;
041
042/**
043 *  Helper to encrypt/decrypt some text
044 */
045public class CryptoHelper extends AbstractLogEnabled implements Component
046{
047    /** Avalon Role */
048    public static final String ROLE = CryptoHelper.class.getName();
049    
050    private static final String CHARSET = "UTF-8";
051    private static final String CIPHER_ALGORITHM = "AES";
052    private static final String KEY_ALGORITHM = "AES";
053    private static final String PASS_HASH_ALGORITHM = "SHA-256";
054
055    private String _cryptoKey;
056    
057    /**
058     * Decrypt a string (base64) using the selected key
059     * @param encryptedValue the encrypted value
060     * @param key The key used to decrypt value
061     * @return the decrypted String
062     */
063    public String decrypt(String encryptedValue, String key)
064    {
065        if (encryptedValue == null)
066        {
067            return null;
068        }
069        try
070        {
071            Cipher cipher = _buildCipher(key, Cipher.DECRYPT_MODE);
072            byte[] encryptedData = Base64.getDecoder().decode(encryptedValue);
073            byte[] data = cipher.doFinal(encryptedData);
074            return new String(data, CHARSET);
075        }
076        catch (Exception e)
077        {
078            throw new RuntimeException(e);
079        }
080    }
081
082    /**
083     * Decrypt a string (base64) using the generated key
084     * @param encryptedValue the encrypted value
085     * @return the decrypted String
086     */
087    public String decrypt(String encryptedValue)
088    {
089        String key = getCryptoKey();
090        return decrypt(encryptedValue, key);
091    }
092
093    /**
094     * Encrypt (base64) a string using the selected key
095     * @param data input data
096     * @param key the key used to encrypt
097     * @return the base64 value of the encrypted data
098     */
099    public String encrypt(String data, String key)
100    {
101        if (data == null)
102        {
103            return null;
104        }
105        try
106        {
107            Cipher cipher = _buildCipher(key, Cipher.ENCRYPT_MODE);
108            byte[] dataToSend = data.getBytes(CHARSET);
109            byte[] encryptedData = cipher.doFinal(dataToSend);
110            return Base64.getEncoder().encodeToString(encryptedData);
111
112        }
113        catch (Exception e)
114        {
115            throw new RuntimeException(e);
116        }
117    }
118
119    /**
120     * Encrypt (base64) a string using the generated key
121     * @param data input data
122     * @return the base64 value of the encrypted data
123     */
124    public String encrypt(String data)
125    {
126        String key = getCryptoKey();
127        return encrypt(data, key);
128    }
129
130    /**
131     * Get the generated crypto key
132     * @return the generated crypto key
133     */
134    public String getCryptoKey()
135    {
136        if (_cryptoKey == null)
137        {
138            File cryptoFile = new File(AmetysHomeHelper.getAmetysHomeConfig(), "messaging-connector.key");
139            if (cryptoFile.exists())
140            {
141                if (cryptoFile.canRead())
142                {
143                    try (BufferedReader reader = new BufferedReader(new FileReader(cryptoFile)))
144                    {
145                        String line = reader.readLine();
146                        if (!StringUtils.isEmpty(line))
147                        {
148                            _cryptoKey = line.trim();
149                        }
150                        else
151                        {
152                            _cryptoKey = _writeKeyInFile(cryptoFile);
153                        }
154                    }
155                    catch (IOException e)
156                    {
157                        getLogger().error("Unable to read the crypto key for messaging connector from file {}", cryptoFile.getAbsolutePath(), e);
158                    }
159                }
160                else
161                {
162                    getLogger().error("Unable to read the crypto key for messaging connector from file {}", cryptoFile.getAbsolutePath());
163                }
164            }
165            else
166            {
167                try
168                {
169                    _cryptoKey = _writeKeyInFile(cryptoFile);
170                }
171                catch (IOException e)
172                {
173                    getLogger().error("Unable to write the crypto key for messaging connector in file {}", cryptoFile.getAbsolutePath(), e);
174                }
175            }
176        }
177        return _cryptoKey;
178    }
179
180    /**
181     * Will create a new random key based on a sha256 hash of the current time, and write it to a file
182     * @param file file where to write
183     * @return the generated password
184     * @throws IOException if something went wrong when writting the file
185     */
186    private String _writeKeyInFile(File file) throws IOException
187    {
188        try (FileWriter fw = new FileWriter(file))
189        {
190            String basePassword = ZonedDateTime.now().toString();
191            String key = Sha2Crypt.sha256Crypt(basePassword.getBytes());
192            fw.write(key);
193            return key;
194        }
195    }
196
197    private Cipher _buildCipher(String password, int mode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException
198    {
199        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
200        Key key = _buildKey(password);
201        cipher.init(mode, key);
202        return cipher;
203    }
204
205    private Key _buildKey(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException
206    {
207        MessageDigest digester = MessageDigest.getInstance(PASS_HASH_ALGORITHM);
208        digester.update(String.valueOf(password).getBytes(CHARSET));
209        byte[] key = digester.digest();
210        return new SecretKeySpec(key, KEY_ALGORITHM);
211    }
212
213}