| /******************************************************************************* |
| * 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.win32; |
| |
| import java.io.IOException; |
| import java.security.SecureRandom; |
| |
| import javax.crypto.spec.PBEKeySpec; |
| |
| import org.eclipse.equinox.internal.security.auth.AuthPlugin; |
| import org.eclipse.equinox.internal.security.auth.nls.SecAuthMessages; |
| import org.eclipse.equinox.internal.security.storage.Base64; |
| import org.eclipse.equinox.internal.security.win32.nls.WinCryptoMessages; |
| import org.eclipse.equinox.security.storage.ISecurePreferences; |
| import org.eclipse.equinox.security.storage.StorageException; |
| import org.eclipse.equinox.security.storage.provider.IPreferencesContainer; |
| import org.eclipse.equinox.security.storage.provider.PasswordProvider; |
| |
| /** |
| * Provides interface with native Windows data protection API. This provider |
| * auto-generates separate passwords for each secure preferences tree. |
| */ |
| public class WinCrypto extends PasswordProvider { |
| |
| native public byte[] windecrypt(byte[] encryptedText); |
| |
| native public byte[] winencrypt(byte[] clearText); |
| |
| static { |
| System.loadLibrary("jnicrypt"); |
| } |
| |
| private final static String WIN_PROVIDER_NODE = "/org.eclipse.equinox.secure.storage/windows"; |
| private final static String PASSWORD_KEY = "encryptedPassword"; |
| |
| /** |
| * The length of the randomly generated password in bytes |
| */ |
| private final static int PASSWORD_LENGTH = 250; |
| |
| @Override |
| public PBEKeySpec getPassword(IPreferencesContainer container, int passwordType) { |
| byte[] encryptedPassword; |
| if ((passwordType & CREATE_NEW_PASSWORD) == 0) |
| encryptedPassword = getEncryptedPassword(container); |
| else |
| encryptedPassword = null; |
| |
| if (encryptedPassword != null) { |
| byte[] decryptedPassword = windecrypt(encryptedPassword); |
| if (decryptedPassword != null) { |
| String password = new String(decryptedPassword); |
| return new PBEKeySpec(password.toCharArray()); |
| } else { |
| StorageException e = new StorageException(StorageException.ENCRYPTION_ERROR, WinCryptoMessages.decryptPasswordFailed); |
| AuthPlugin.getDefault().logError(WinCryptoMessages.decryptPasswordFailed, e); |
| return null; |
| } |
| } |
| |
| // add info message in the log |
| AuthPlugin.getDefault().logMessage(WinCryptoMessages.newPasswordGenerated); |
| |
| byte[] rawPassword = new byte[PASSWORD_LENGTH]; |
| SecureRandom random = new SecureRandom(); |
| random.setSeed(System.currentTimeMillis()); |
| random.nextBytes(rawPassword); |
| String password = Base64.encode(rawPassword); |
| if (savePassword(password, container)) |
| return new PBEKeySpec(password.toCharArray()); |
| else |
| return null; |
| } |
| |
| private byte[] getEncryptedPassword(IPreferencesContainer container) { |
| ISecurePreferences node = container.getPreferences().node(WIN_PROVIDER_NODE); |
| String passwordHint; |
| try { |
| passwordHint = node.get(PASSWORD_KEY, null); |
| } catch (StorageException e) { // should never happen in this scenario |
| AuthPlugin.getDefault().logError(WinCryptoMessages.decryptPasswordFailed, e); |
| return null; |
| } |
| if (passwordHint == null) |
| return null; |
| return Base64.decode(passwordHint); |
| } |
| |
| private boolean savePassword(String password, IPreferencesContainer container){ |
| byte[] data = winencrypt(password.getBytes()); |
| if (data == null) { // this is bad. Something wrong with OS or JNI. |
| StorageException e = new StorageException(StorageException.ENCRYPTION_ERROR, WinCryptoMessages.encryptPasswordFailed); |
| AuthPlugin.getDefault().logError(WinCryptoMessages.encryptPasswordFailed, e); |
| return false; |
| } |
| String encodedEncryptyedPassword = Base64.encode(data); |
| ISecurePreferences node = container.getPreferences().node(WIN_PROVIDER_NODE); |
| try { |
| node.put(PASSWORD_KEY, encodedEncryptyedPassword, false); // note we don't recursively try to encrypt |
| } catch (StorageException e) { // should never happen in this scenario |
| AuthPlugin.getDefault().logError(SecAuthMessages.errorOnSave, e); |
| return false; |
| } |
| try { |
| node.flush(); // save right away |
| } catch (IOException e) { |
| AuthPlugin.getDefault().logError(SecAuthMessages.errorOnSave, e); |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean retryOnError(Exception e, IPreferencesContainer container) { |
| // It would be rather dangerous to allow this password to be changed |
| // as it would permanently trash all entries in the secure storage. |
| // Rather applications using get...() should handle exceptions and offer to overwrite |
| // data on an entry-by-entry scale. |
| return false; |
| } |
| |
| } |