| /******************************************************************************* |
| * Copyright (c) 2008, 2017 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.equinox.internal.security.storage; |
| |
| import java.security.*; |
| import java.security.spec.InvalidKeySpecException; |
| import java.util.*; |
| import javax.crypto.*; |
| import javax.crypto.spec.PBEKeySpec; |
| import javax.crypto.spec.PBEParameterSpec; |
| import org.eclipse.core.runtime.jobs.ILock; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.core.runtime.preferences.ConfigurationScope; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences; |
| import org.eclipse.equinox.internal.security.auth.AuthPlugin; |
| import org.eclipse.equinox.internal.security.auth.nls.SecAuthMessages; |
| import org.eclipse.equinox.internal.security.storage.friends.*; |
| import org.eclipse.equinox.security.storage.StorageException; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * Note that algorithm detection skips aliases: |
| * Alg.Alias.Cipher.ABC |
| * only a few aliases are useful and it will be harder to separate human-readable |
| * aliases from internal ones. |
| * |
| */ |
| public class JavaEncryption { |
| |
| private final static String SECRET_KEY_FACTORY = "SecretKeyFactory."; //$NON-NLS-1$ |
| private final static String CIPHER = "Cipher."; //$NON-NLS-1$ |
| |
| private final static String sampleText = "sample text for roundtrip testing"; //$NON-NLS-1$ |
| private final static PasswordExt samplePassword = new PasswordExt(new PBEKeySpec("password1".toCharArray()), "abc"); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| static private ILock lock = Job.getJobManager().newLock(); |
| |
| static private final int SALT_ITERATIONS = 10; |
| |
| private String keyFactoryAlgorithm = null; |
| private String cipherAlgorithm = null; |
| |
| private boolean initialized = false; |
| |
| private HashMap availableCiphers; |
| |
| public JavaEncryption() { |
| // placeholder |
| } |
| |
| public String getKeyFactoryAlgorithm() { |
| return keyFactoryAlgorithm; |
| } |
| |
| public String getCipherAlgorithm() { |
| return cipherAlgorithm; |
| } |
| |
| public void setAlgorithms(String cipherAlgorithm, String keyFactoryAlgorithm) { |
| try { |
| lock.acquire(); // avoid conflict with init() |
| this.cipherAlgorithm = cipherAlgorithm; |
| this.keyFactoryAlgorithm = keyFactoryAlgorithm; |
| } finally { |
| lock.release(); |
| } |
| } |
| |
| private void init() throws StorageException { |
| if (initialized) |
| return; |
| initialized = true; |
| |
| try { |
| lock.acquire(); // avoid multiple simultaneous initializations |
| IUICallbacks callback = CallbacksProvider.getDefault().getCallback(); |
| if (callback == null) |
| internalInitialize(); |
| else { |
| callback.execute(new IStorageTask() { |
| public void execute() throws StorageException { |
| internalInitialize(); |
| } |
| }); |
| } |
| } finally { |
| lock.release(); |
| } |
| } |
| |
| protected void internalInitialize() throws StorageException { |
| if (cipherAlgorithm != null && keyFactoryAlgorithm != null) { |
| if (roundtrip(cipherAlgorithm, keyFactoryAlgorithm)) |
| return; |
| // this is a bad situation - JVM cipher no longer available. Both log and throw an exception |
| String msg = NLS.bind(SecAuthMessages.noAlgorithm, cipherAlgorithm); |
| StorageException e = new StorageException(StorageException.INTERNAL_ERROR, msg); |
| AuthPlugin.getDefault().logError(msg, e); |
| throw e; |
| } |
| if (cipherAlgorithm == null || keyFactoryAlgorithm == null) { |
| IEclipsePreferences eclipseNode = ConfigurationScope.INSTANCE.getNode(AuthPlugin.PI_AUTH); |
| cipherAlgorithm = eclipseNode.get(IStorageConstants.CIPHER_KEY, IStorageConstants.DEFAULT_CIPHER); |
| keyFactoryAlgorithm = eclipseNode.get(IStorageConstants.KEY_FACTORY_KEY, IStorageConstants.DEFAULT_KEY_FACTORY); |
| } |
| if (roundtrip(cipherAlgorithm, keyFactoryAlgorithm)) |
| return; |
| String unavailableCipher = cipherAlgorithm; |
| |
| detect(); |
| if (availableCiphers.size() == 0) |
| throw new StorageException(StorageException.INTERNAL_ERROR, SecAuthMessages.noAlgorithms); |
| |
| // use first available |
| cipherAlgorithm = (String) availableCiphers.keySet().iterator().next(); |
| keyFactoryAlgorithm = (String) availableCiphers.get(cipherAlgorithm); |
| |
| String msg = NLS.bind(SecAuthMessages.usingAlgorithm, unavailableCipher, cipherAlgorithm); |
| AuthPlugin.getDefault().logMessage(msg); |
| } |
| |
| public CryptoData encrypt(PasswordExt passwordExt, byte[] clearText) throws StorageException { |
| init(); |
| return internalEncrypt(passwordExt, clearText); |
| } |
| |
| private CryptoData internalEncrypt(PasswordExt passwordExt, byte[] clearText) throws StorageException { |
| try { |
| SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(keyFactoryAlgorithm); |
| SecretKey key = keyFactory.generateSecret(passwordExt.getPassword()); |
| |
| byte[] salt = new byte[8]; |
| SecureRandom random = new SecureRandom(); |
| random.nextBytes(salt); |
| PBEParameterSpec entropy = new PBEParameterSpec(salt, SALT_ITERATIONS); |
| |
| Cipher c = Cipher.getInstance(cipherAlgorithm); |
| c.init(Cipher.ENCRYPT_MODE, key, entropy); |
| |
| byte[] result = c.doFinal(clearText); |
| return new CryptoData(passwordExt.getModuleID(), salt, result); |
| } catch (InvalidKeyException e) { |
| handle(e, StorageException.ENCRYPTION_ERROR); |
| return null; |
| } catch (InvalidAlgorithmParameterException e) { |
| handle(e, StorageException.ENCRYPTION_ERROR); |
| return null; |
| } catch (IllegalBlockSizeException e) { |
| handle(e, StorageException.ENCRYPTION_ERROR); |
| return null; |
| } catch (BadPaddingException e) { |
| handle(e, StorageException.ENCRYPTION_ERROR); |
| return null; |
| } catch (InvalidKeySpecException e) { |
| handle(e, StorageException.INTERNAL_ERROR); |
| return null; |
| } catch (NoSuchPaddingException e) { |
| handle(e, StorageException.INTERNAL_ERROR); |
| return null; |
| } catch (NoSuchAlgorithmException e) { |
| handle(e, StorageException.INTERNAL_ERROR); |
| return null; |
| } |
| } |
| |
| public byte[] decrypt(PasswordExt passwordExt, CryptoData encryptedData) throws StorageException, IllegalStateException, IllegalBlockSizeException, BadPaddingException { |
| init(); |
| return internalDecrypt(passwordExt, encryptedData); |
| } |
| |
| private byte[] internalDecrypt(PasswordExt passwordExt, CryptoData encryptedData) throws StorageException, IllegalStateException, IllegalBlockSizeException, BadPaddingException { |
| try { |
| SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(keyFactoryAlgorithm); |
| SecretKey key = keyFactory.generateSecret(passwordExt.getPassword()); |
| |
| PBEParameterSpec entropy = new PBEParameterSpec(encryptedData.getSalt(), SALT_ITERATIONS); |
| |
| Cipher c = Cipher.getInstance(cipherAlgorithm); |
| c.init(Cipher.DECRYPT_MODE, key, entropy); |
| |
| byte[] result = c.doFinal(encryptedData.getData()); |
| return result; |
| } catch (InvalidAlgorithmParameterException e) { |
| handle(e, StorageException.INTERNAL_ERROR); |
| return null; |
| } catch (InvalidKeyException e) { |
| handle(e, StorageException.INTERNAL_ERROR); |
| return null; |
| } catch (InvalidKeySpecException e) { |
| handle(e, StorageException.INTERNAL_ERROR); |
| return null; |
| } catch (NoSuchPaddingException e) { |
| handle(e, StorageException.INTERNAL_ERROR); |
| return null; |
| } catch (NoSuchAlgorithmException e) { |
| handle(e, StorageException.INTERNAL_ERROR); |
| return null; |
| } |
| } |
| |
| private void handle(Exception e, int internalCode) throws StorageException { |
| if (AuthPlugin.DEBUG_LOGIN_FRAMEWORK) |
| e.printStackTrace(); |
| StorageException exception = new StorageException(internalCode, e); |
| throw exception; |
| } |
| |
| ///////////////////////////////////////////////////////////////////////////////////// |
| // Algorithm detection |
| |
| /** |
| * Result: Map: |
| * <String>cipher -> <String>keyFactory |
| */ |
| public HashMap detect() { |
| IUICallbacks callback = CallbacksProvider.getDefault().getCallback(); |
| if (callback == null) |
| return internalDetect(); |
| |
| IStorageTask task = new IStorageTask() { |
| public void execute() { |
| internalDetect(); |
| } |
| }; |
| try { |
| callback.execute(task); |
| } catch (StorageException e) { // should not happen in this path |
| AuthPlugin.getDefault().logError(e.getMessage(), e); |
| } |
| return availableCiphers; |
| } |
| |
| public HashMap internalDetect() { |
| Set ciphers = findProviders(CIPHER); |
| Set keyFactories = findProviders(SECRET_KEY_FACTORY); |
| availableCiphers = new HashMap(ciphers.size()); |
| |
| for (Iterator i = ciphers.iterator(); i.hasNext();) { |
| String cipher = (String) i.next(); |
| // check if there is a key factory with the same name |
| if (keyFactories.contains(cipher)) { |
| if (roundtrip(cipher, cipher)) { |
| availableCiphers.put(cipher, cipher); |
| continue; |
| } |
| } |
| for (Iterator j = keyFactories.iterator(); j.hasNext();) { |
| String keyFactory = (String) j.next(); |
| if (roundtrip(cipher, keyFactory)) { |
| availableCiphers.put(cipher, keyFactory); |
| continue; |
| } |
| } |
| } |
| return availableCiphers; |
| } |
| |
| private Set findProviders(String prefix) { |
| Provider[] providers = Security.getProviders(); |
| Set algorithms = new HashSet(); |
| int prefixLength = prefix.length(); |
| for (int i = 0; i < providers.length; i++) { |
| for (Iterator j = providers[i].entrySet().iterator(); j.hasNext();) { |
| Map.Entry entry = (Map.Entry) j.next(); |
| Object key = entry.getKey(); |
| if (key == null) |
| continue; |
| if (!(key instanceof String)) |
| continue; |
| String value = (String) key; |
| if (value.indexOf(' ') != -1) // skips properties like "[Cipher.ABC SupportedPaddings]" |
| continue; |
| if (value.startsWith(prefix)) { |
| String keyFactory = value.substring(prefixLength); |
| algorithms.add(keyFactory); |
| } |
| } |
| } |
| return algorithms; |
| } |
| |
| private boolean roundtrip(String testCipher, String testKeyFactory) { |
| boolean storeInitState = initialized; |
| String storedCipherAlgorithm = cipherAlgorithm; |
| String storedKeyAlgorithm = keyFactoryAlgorithm; |
| initialized = true; |
| try { |
| cipherAlgorithm = testCipher; |
| keyFactoryAlgorithm = testKeyFactory; |
| CryptoData encrypted = internalEncrypt(samplePassword, StorageUtils.getBytes(sampleText)); |
| byte[] roundtripBytes = internalDecrypt(samplePassword, encrypted); |
| String result = StorageUtils.getString(roundtripBytes); |
| return sampleText.equals(result); |
| } catch (Exception e) { |
| // internal implementation throws both checked and unchecked |
| // exceptions (without much documentation to go on), so have to use catch-all |
| return false; |
| } finally { // reset back |
| cipherAlgorithm = storedCipherAlgorithm; |
| keyFactoryAlgorithm = storedKeyAlgorithm; |
| initialized = storeInitState; |
| } |
| } |
| |
| } |