blob: 4eb26e0d57ea80b34bdf11bb06e04360dbc4374f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* 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;
}
}