1 /******************************************************************************* 2 * Copyright (c) 2008, 2017 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.equinox.internal.security.win32; 15 16 import java.io.IOException; 17 import java.security.SecureRandom; 18 19 import javax.crypto.spec.PBEKeySpec; 20 21 import org.eclipse.equinox.internal.security.auth.AuthPlugin; 22 import org.eclipse.equinox.internal.security.auth.nls.SecAuthMessages; 23 import org.eclipse.equinox.internal.security.storage.Base64; 24 import org.eclipse.equinox.internal.security.win32.nls.WinCryptoMessages; 25 import org.eclipse.equinox.security.storage.ISecurePreferences; 26 import org.eclipse.equinox.security.storage.StorageException; 27 import org.eclipse.equinox.security.storage.provider.IPreferencesContainer; 28 import org.eclipse.equinox.security.storage.provider.PasswordProvider; 29 30 /** 31 * Provides interface with native Windows data protection API. This provider 32 * auto-generates separate passwords for each secure preferences tree. 33 */ 34 public class WinCrypto extends PasswordProvider { 35 windecrypt(byte[] encryptedText)36 native public byte[] windecrypt(byte[] encryptedText); 37 winencrypt(byte[] clearText)38 native public byte[] winencrypt(byte[] clearText); 39 40 static { 41 System.loadLibrary("jnicrypt64"); 42 } 43 44 private final static String WIN_PROVIDER_NODE = "/org.eclipse.equinox.secure.storage/windows64"; 45 private final static String PASSWORD_KEY = "encryptedPassword"; 46 47 /** 48 * The length of the randomly generated password in bytes 49 */ 50 private final static int PASSWORD_LENGTH = 250; 51 52 @Override getPassword(IPreferencesContainer container, int passwordType)53 public PBEKeySpec getPassword(IPreferencesContainer container, int passwordType) { 54 byte[] encryptedPassword; 55 if ((passwordType & CREATE_NEW_PASSWORD) == 0) 56 encryptedPassword = getEncryptedPassword(container); 57 else 58 encryptedPassword = null; 59 60 if (encryptedPassword != null) { 61 byte[] decryptedPassword = windecrypt(encryptedPassword); 62 if (decryptedPassword != null) { 63 String password = new String(decryptedPassword); 64 return new PBEKeySpec(password.toCharArray()); 65 } else { 66 StorageException e = new StorageException(StorageException.ENCRYPTION_ERROR, WinCryptoMessages.decryptPasswordFailed); 67 AuthPlugin.getDefault().logError(WinCryptoMessages.decryptPasswordFailed, e); 68 return null; 69 } 70 } 71 72 // add info message in the log 73 AuthPlugin.getDefault().logMessage(WinCryptoMessages.newPasswordGenerated); 74 75 byte[] rawPassword = new byte[PASSWORD_LENGTH]; 76 SecureRandom random = new SecureRandom(); 77 random.setSeed(System.currentTimeMillis()); 78 random.nextBytes(rawPassword); 79 String password = Base64.encode(rawPassword); 80 if (savePassword(password, container)) 81 return new PBEKeySpec(password.toCharArray()); 82 else 83 return null; 84 } 85 getEncryptedPassword(IPreferencesContainer container)86 private byte[] getEncryptedPassword(IPreferencesContainer container) { 87 ISecurePreferences node = container.getPreferences().node(WIN_PROVIDER_NODE); 88 String passwordHint; 89 try { 90 passwordHint = node.get(PASSWORD_KEY, null); 91 } catch (StorageException e) { // should never happen in this scenario 92 AuthPlugin.getDefault().logError(WinCryptoMessages.decryptPasswordFailed, e); 93 return null; 94 } 95 if (passwordHint == null) 96 return null; 97 return Base64.decode(passwordHint); 98 } 99 savePassword(String password, IPreferencesContainer container)100 private boolean savePassword(String password, IPreferencesContainer container){ 101 byte[] data = winencrypt(password.getBytes()); 102 if (data == null) { // this is bad. Something wrong with OS or JNI. 103 StorageException e = new StorageException(StorageException.ENCRYPTION_ERROR, WinCryptoMessages.encryptPasswordFailed); 104 AuthPlugin.getDefault().logError(WinCryptoMessages.encryptPasswordFailed, e); 105 return false; 106 } 107 String encodedEncryptyedPassword = Base64.encode(data); 108 ISecurePreferences node = container.getPreferences().node(WIN_PROVIDER_NODE); 109 try { 110 node.put(PASSWORD_KEY, encodedEncryptyedPassword, false); // note we don't recursively try to encrypt 111 } catch (StorageException e) { // should never happen in this scenario 112 AuthPlugin.getDefault().logError(SecAuthMessages.errorOnSave, e); 113 return false; 114 } 115 try { 116 node.flush(); // save right away 117 } catch (IOException e) { 118 AuthPlugin.getDefault().logError(SecAuthMessages.errorOnSave, e); 119 return false; 120 } 121 return true; 122 } 123 124 @Override retryOnError(Exception e, IPreferencesContainer container)125 public boolean retryOnError(Exception e, IPreferencesContainer container) { 126 // It would be rather dangerous to allow this password to be changed 127 // as it would permanently trash all entries in the secure storage. 128 // Rather applications using get...() should handle exceptions and offer to overwrite 129 // data on an entry-by-entry scale. 130 return false; 131 } 132 133 } 134