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