# HG changeset patch # User Jesse Glick # Date 1259961763 18000 Issue #173413: create common API for managing user passwords in a secure keyring. diff --git a/core.ui/src/org/netbeans/core/ui/options/general/GeneralOptionsModel.java b/core.ui/src/org/netbeans/core/ui/options/general/GeneralOptionsModel.java --- a/core.ui/src/org/netbeans/core/ui/options/general/GeneralOptionsModel.java +++ b/core.ui/src/org/netbeans/core/ui/options/general/GeneralOptionsModel.java @@ -187,7 +187,6 @@ return ProxySettings.getAuthenticationUsername (); } - //TODO: not used yet - store valu just in case if modified void setAuthenticationUsername (String username) { getProxyPreferences ().put (ProxySettings.PROXY_AUTHENTICATION_USERNAME, username); } @@ -196,9 +195,8 @@ return ProxySettings.getAuthenticationPassword (); } - //TODO: not used yet - store valu just in case if modified void setAuthenticationPassword(char [] password) { - getProxyPreferences().put(ProxySettings.PROXY_AUTHENTICATION_PASSWORD, new String(password)); + ProxySettings.setAuthenticationPassword(password); } // private helper methods .................................................. diff --git a/hudson/nbproject/project.xml b/hudson/nbproject/project.xml --- a/hudson/nbproject/project.xml +++ b/hudson/nbproject/project.xml @@ -51,6 +51,14 @@ + org.netbeans.modules.keyring + + + + 1.0 + + + org.netbeans.modules.options.api diff --git a/hudson/src/org/netbeans/modules/hudson/ui/Bundle.properties b/hudson/src/org/netbeans/modules/hudson/ui/Bundle.properties --- a/hudson/src/org/netbeans/modules/hudson/ui/Bundle.properties +++ b/hudson/src/org/netbeans/modules/hudson/ui/Bundle.properties @@ -41,3 +41,6 @@ FormLogin.userLabel.text=&Username: FormLogin.passLabel.text=&Password: FormLogin.log_in=Log in to Hudson +# {0} - server location +# {1} - user name +FormLogin.password_description=Password for {1} on {0} diff --git a/hudson/src/org/netbeans/modules/hudson/ui/FormLogin.java b/hudson/src/org/netbeans/modules/hudson/ui/FormLogin.java --- a/hudson/src/org/netbeans/modules/hudson/ui/FormLogin.java +++ b/hudson/src/org/netbeans/modules/hudson/ui/FormLogin.java @@ -42,6 +42,7 @@ import java.net.URL; import java.util.prefs.Preferences; import javax.swing.JPanel; +import org.netbeans.api.keyring.Keyring; import org.netbeans.modules.hudson.impl.HudsonManagerImpl; import org.netbeans.modules.hudson.spi.PasswordAuthorizer; import org.openide.DialogDescriptor; @@ -73,6 +74,10 @@ String username = loginPrefs().get(server, null); if (username != null) { panel.userField.setText(username); + char[] savedPassword = Keyring.read(server); + if (savedPassword != null) { + panel.passField.setText(new String(savedPassword)); + } } panel.locationField.setText(home.toString()); DialogDescriptor dd = new DialogDescriptor(panel, NbBundle.getMessage(FormLogin.class, "FormLogin.log_in")); @@ -83,6 +88,7 @@ loginPrefs().put(server, username); String password = new String(panel.passField.getPassword()); panel.passField.setText(""); + Keyring.save(server, password.toCharArray(), NbBundle.getMessage(FormLogin.class, "FormLogin.password_description", home, username)); return new String[] {username, password}; } } diff --git a/kenai.ui/nbproject/project.xml b/kenai.ui/nbproject/project.xml --- a/kenai.ui/nbproject/project.xml +++ b/kenai.ui/nbproject/project.xml @@ -94,6 +94,14 @@ + org.netbeans.modules.keyring + + + + 1.0 + + + org.netbeans.modules.mercurial diff --git a/kenai.ui/src/org/netbeans/modules/kenai/ui/LoginPanel.form b/kenai.ui/src/org/netbeans/modules/kenai/ui/LoginPanel.form --- a/kenai.ui/src/org/netbeans/modules/kenai/ui/LoginPanel.form +++ b/kenai.ui/src/org/netbeans/modules/kenai/ui/LoginPanel.form @@ -34,7 +34,7 @@ - + @@ -66,7 +66,7 @@ - + @@ -164,6 +164,7 @@ + diff --git a/kenai.ui/src/org/netbeans/modules/kenai/ui/LoginPanel.java b/kenai.ui/src/org/netbeans/modules/kenai/ui/LoginPanel.java --- a/kenai.ui/src/org/netbeans/modules/kenai/ui/LoginPanel.java +++ b/kenai.ui/src/org/netbeans/modules/kenai/ui/LoginPanel.java @@ -200,6 +200,7 @@ lblPassword.setLabelFor(password); org.openide.awt.Mnemonics.setLocalizedText(lblPassword, org.openide.util.NbBundle.getMessage(LoginPanel.class, "LoginPanel.lblPassword.text")); // NOI18N + chkRememberMe.setSelected(true); org.openide.awt.Mnemonics.setLocalizedText(chkRememberMe, org.openide.util.NbBundle.getMessage(LoginPanel.class, "LoginPanel.chkRememberMe.text")); // NOI18N chkRememberMe.setToolTipText(org.openide.util.NbBundle.getMessage(LoginPanel.class, "LoginPanel.chkRememberMe.toolTipText")); // NOI18N chkRememberMe.addActionListener(new java.awt.event.ActionListener() { @@ -237,7 +238,7 @@ .add(layout.createSequentialGroup() .add(lblKenaiLogoLeft) .add(0, 0, 0) - .add(lblKenaiLogoCenter, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 196, Short.MAX_VALUE) + .add(lblKenaiLogoCenter, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 202, Short.MAX_VALUE) .add(0, 0, 0) .add(lblKenaiLogoRight)) .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup() @@ -262,7 +263,7 @@ .addContainerGap()) .add(layout.createSequentialGroup() .addContainerGap() - .add(progressBar, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 418, Short.MAX_VALUE) + .add(progressBar, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 424, Short.MAX_VALUE) .addContainerGap()) ); layout.setVerticalGroup( diff --git a/kenai.ui/src/org/netbeans/modules/kenai/ui/spi/Bundle.properties b/kenai.ui/src/org/netbeans/modules/kenai/ui/spi/Bundle.properties new file mode 100644 --- /dev/null +++ b/kenai.ui/src/org/netbeans/modules/kenai/ui/spi/Bundle.properties @@ -0,0 +1,39 @@ +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# +# The contents of this file are subject to the terms of either the GNU +# General Public License Version 2 only ("GPL") or the Common +# Development and Distribution License("CDDL") (collectively, the +# "License"). You may not use this file except in compliance with the +# License. You can obtain a copy of the License at +# http://www.netbeans.org/cddl-gplv2.html +# or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the +# specific language governing permissions and limitations under the +# License. When distributing the software, include this License Header +# Notice in each file and include the License file at +# nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this +# particular file as subject to the "Classpath" exception as provided +# by Sun in the GPL Version 2 section of the License file that +# accompanied this code. If applicable, add the following below the +# License Header, with the fields enclosed by brackets [] replaced by +# your own identifying information: +# "Portions Copyrighted [year] [name of copyright owner]" +# +# If you wish your version of this file to be governed by only the CDDL +# or only the GPL Version 2, indicate your decision by adding +# "[Contributor] elects to include this software in this distribution +# under the [CDDL or GPL Version 2] license." If you do not indicate a +# single choice of license, a recipient has the option to distribute +# your version of this file under either the CDDL, the GPL Version 2 or +# to extend the choice of license to its licensees as provided above. +# However, if you add GPL Version 2 code and therefore, elected the GPL +# Version 2 license, then the option applies only if the new code is +# made subject to such option by the copyright holder. +# +# Contributor(s): +# +# Portions Copyrighted 2009 Sun Microsystems, Inc. + +# {0} - kenai.com or similar +UIUtils.password_keyring_description=Password for {0} diff --git a/kenai.ui/src/org/netbeans/modules/kenai/ui/spi/Scrambler.java b/kenai.ui/src/org/netbeans/modules/kenai/ui/spi/Scrambler.java --- a/kenai.ui/src/org/netbeans/modules/kenai/ui/spi/Scrambler.java +++ b/kenai.ui/src/org/netbeans/modules/kenai/ui/spi/Scrambler.java @@ -45,17 +45,9 @@ import java.io.ByteArrayOutputStream; -/** - * Scrambles text (the password) using the standard scheme described in the - * CVS protocol version 1.10. This encoding is trivial and should not be - * used for security, but rather as a mechanism for avoiding inadvertant - * compromise. - * @author Robert Greig, Tomas Stupka - */ +@Deprecated class Scrambler { - private static final char [] characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); // NOI18N - /** * The mapping array */ @@ -313,21 +305,6 @@ return instance; } - /** - * Scramble text, turning it into a String of scrambled data - * @return a String of scrambled data - */ - public String scramble(String text) { - StringBuffer buf = new StringBuffer("A"); //NOI18N - - if (text != null) { - for (int i = 0; i < text.length(); ++i) { - buf.append(scramble(text.charAt(i))); - } - } - return new String(encode(buf.toString().getBytes())); - } - public String descramble(String scrambledText) { StringBuffer buf = new StringBuffer(); if (scrambledText != null) { @@ -348,51 +325,6 @@ private byte[] decode(String str) { return decode64(str); } - - private byte[] encode(byte[] encode) { - return encode64(encode).getBytes(); - } - - private static String encode64(byte [] data) { - return encode64(data, false); - } - - private static String encode64(byte [] data, boolean useNewlines) { - int length = data.length; - StringBuffer sb = new StringBuffer(data.length * 3 / 2); - - int end = length - 3; - int i = 0; - int lineCount = 0; - - while (i <= end) { - int d = ((((int) data[i]) & 0xFF) << 16) | ((((int) data[i + 1]) & 0xFF) << 8) | (((int) data[i + 2]) & 0xFF); - sb.append(characters[(d >> 18) & 0x3F]); - sb.append(characters[(d >> 12) & 0x3F]); - sb.append(characters[(d >> 6) & 0x3F]); - sb.append(characters[d & 0x3F]); - i += 3; - - if (useNewlines && lineCount++ >= 14) { - lineCount = 0; - sb.append(System.getProperty("line.separator")); - } - } - - if (i == length - 2) { - int d = ((((int) data[i]) & 0xFF) << 16) | ((((int) data[i + 1]) & 0xFF) << 8); - sb.append(characters[(d >> 18) & 0x3F]); - sb.append(characters[(d >> 12) & 0x3F]); - sb.append(characters[(d >> 6) & 0x3F]); - sb.append("="); // NOI18N - } else if (i == length - 1) { - int d = (((int) data[i]) & 0xFF) << 16; - sb.append(characters[(d >> 18) & 0x3F]); - sb.append(characters[(d >> 12) & 0x3F]); - sb.append("=="); // NOI18N - } - return sb.toString(); - } private static byte [] decode64(String s) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); diff --git a/kenai.ui/src/org/netbeans/modules/kenai/ui/spi/UIUtils.java b/kenai.ui/src/org/netbeans/modules/kenai/ui/spi/UIUtils.java --- a/kenai.ui/src/org/netbeans/modules/kenai/ui/spi/UIUtils.java +++ b/kenai.ui/src/org/netbeans/modules/kenai/ui/spi/UIUtils.java @@ -57,6 +57,7 @@ import javax.swing.JLabel; import javax.swing.JRootPane; import javax.swing.SwingUtilities; +import org.netbeans.api.keyring.Keyring; import org.netbeans.modules.kenai.api.Kenai; import org.netbeans.modules.kenai.api.KenaiException; import org.netbeans.modules.kenai.api.KenaiUser; @@ -125,11 +126,15 @@ if (uname==null) { return false; } - String password=preferences.get(getPrefName(KENAI_PASSWORD_PREF), null); // NOI18N PresenceIndicator.getDefault().init(); try { KenaiConnection.getDefault(); - Kenai.getDefault().login(uname, Scrambler.getInstance().descramble(password).toCharArray(), force?true:Boolean.parseBoolean(preferences.get(getPrefName(ONLINE_STATUS_PREF), String.valueOf(Utilities.isChatSupported())))); + char[] password = loadPassword(preferences); + if (password == null) { + return false; + } + Kenai.getDefault().login(uname, password, + force ? true : Boolean.parseBoolean(preferences.get(getPrefName(ONLINE_STATUS_PREF), String.valueOf(Utilities.isChatSupported())))); } catch (KenaiException ex) { return false; } @@ -137,6 +142,24 @@ } /** + * Loads password from the keyring. For settings compatibility, + * can also interpret and upgrade old insecure storage. + */ + @SuppressWarnings("deprecation") + private static char[] loadPassword(Preferences preferences) { + String passwordPref = getPrefName(KENAI_PASSWORD_PREF); + String scrambledPassword = preferences.get(passwordPref, null); // NOI18N + char[] newPassword = Keyring.read(passwordPref); + if (scrambledPassword != null) { + preferences.remove(passwordPref); + if (newPassword == null) { + return Scrambler.getInstance().descramble(scrambledPassword).toCharArray(); + } + } + return newPassword; + } + + /** * Invokes login dialog * @return true, if user was succesfully logged in */ @@ -186,13 +209,16 @@ } } }); + String passwordPref = getPrefName(KENAI_PASSWORD_PREF); if (loginPanel.isStorePassword()) { preferences.put(getPrefName(KENAI_USERNAME_PREF), loginPanel.getUsername()); // NOI18N - preferences.put(getPrefName(KENAI_PASSWORD_PREF), Scrambler.getInstance().scramble(new String(loginPanel.getPassword()))); // NOI18N + Keyring.save(passwordPref, loginPanel.getPassword(), + NbBundle.getMessage(UIUtils.class, "UIUtils.password_keyring_description", Kenai.getDefault().getUrl().getHost())); } else { preferences.remove(getPrefName(KENAI_USERNAME_PREF)); // NOI18N - preferences.remove(getPrefName(KENAI_PASSWORD_PREF)); // NOI18N + Keyring.delete(passwordPref); } + preferences.remove(passwordPref); } else { loginPanel.putClientProperty("cancel", "true"); // NOI18N JDialog parent = (JDialog) loginPanel.getRootPane().getParent(); @@ -205,10 +231,12 @@ Dialog d = DialogDisplayer.getDefault().createDialog(login); String uname=preferences.get(getPrefName(KENAI_USERNAME_PREF), null); // NOI18N - String password=preferences.get(getPrefName(KENAI_PASSWORD_PREF), null); // NOI18N - if (uname!=null && password!=null) { + if (uname != null) { loginPanel.setUsername(uname); - loginPanel.setPassword(Scrambler.getInstance().descramble(password).toCharArray()); + char[] password = loadPassword(preferences); + if (password != null) { + loginPanel.setPassword(password); + } } d.pack(); d.setResizable(false); diff --git a/kenai.ui/test/unit/src/org/netbeans/modules/kenai/ui/spi/UIUtilsTest.java b/kenai.ui/test/unit/src/org/netbeans/modules/kenai/ui/spi/UIUtilsTest.java deleted file mode 100644 --- a/kenai.ui/test/unit/src/org/netbeans/modules/kenai/ui/spi/UIUtilsTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2009 Sun Microsystems, Inc. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common - * Development and Distribution License("CDDL") (collectively, the - * "License"). You may not use this file except in compliance with the - * License. You can obtain a copy of the License at - * http://www.netbeans.org/cddl-gplv2.html - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the - * specific language governing permissions and limitations under the - * License. When distributing the software, include this License Header - * Notice in each file and include the License file at - * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this - * particular file as subject to the "Classpath" exception as provided - * by Sun in the GPL Version 2 section of the License file that - * accompanied this code. If applicable, add the following below the - * License Header, with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * If you wish your version of this file to be governed by only the CDDL - * or only the GPL Version 2, indicate your decision by adding - * "[Contributor] elects to include this software in this distribution - * under the [CDDL or GPL Version 2] license." If you do not indicate a - * single choice of license, a recipient has the option to distribute - * your version of this file under either the CDDL, the GPL Version 2 or - * to extend the choice of license to its licensees as provided above. - * However, if you add GPL Version 2 code and therefore, elected the GPL - * Version 2 license, then the option applies only if the new code is - * made subject to such option by the copyright holder. - * - * Contributor(s): - * - * Portions Copyrighted 2009 Sun Microsystems, Inc. - */ - -package org.netbeans.modules.kenai.ui.spi; - -import java.util.prefs.Preferences; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.openide.util.NbPreferences; -import static org.junit.Assert.*; - -/** - * - * @author Jan Becicka - */ -public class UIUtilsTest { - - public UIUtilsTest() { - } - - @BeforeClass - public static void setUpClass() throws Exception { - } - - @AfterClass - public static void tearDownClass() throws Exception { - } - - /** - * Test of showLogin method, of class UIUtils. - */ - @Test - public void testEncodeDecode() { - String testpass = "pswd"; - String scram = Scrambler.getInstance().scramble(testpass); - Preferences preferences=NbPreferences.forModule(UIUtils.class); - preferences.put("kenai.test.password", scram); - String newp=preferences.get("kenai.test.password", null); - String r = Scrambler.getInstance().descramble(newp); - assertEquals(testpass, r); - // TODO review the generated test code and remove the default call to fail. - } -} diff --git a/keyring/build.xml b/keyring/build.xml new file mode 100644 --- /dev/null +++ b/keyring/build.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/keyring/manifest.mf b/keyring/manifest.mf new file mode 100644 --- /dev/null +++ b/keyring/manifest.mf @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +OpenIDE-Module: org.netbeans.modules.keyring +OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/keyring/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/keyring/nbproject/project.properties b/keyring/nbproject/project.properties new file mode 100644 --- /dev/null +++ b/keyring/nbproject/project.properties @@ -0,0 +1,4 @@ +is.autoload=true +javac.source=1.5 +javac.compilerargs=-Xlint -Xlint:-serial +tryme.args=-J-Dnetbeans.full.hack=true -J-Dorg.netbeans.modules.keyring.level=0 diff --git a/keyring/nbproject/project.xml b/keyring/nbproject/project.xml new file mode 100644 --- /dev/null +++ b/keyring/nbproject/project.xml @@ -0,0 +1,71 @@ + + + org.netbeans.modules.apisupport.project + + + org.netbeans.modules.keyring + + + org.jdesktop.layout + + + + 1 + 1.9 + + + + org.netbeans.libs.jna + + + + 1 + 1.4 + + + + org.openide.awt + + + + 7.16 + + + + org.openide.dialogs + + + + 7.13 + + + + org.openide.util + + + + 7.29 + + + + + + unit + + org.netbeans.libs.junit4 + + + + org.netbeans.modules.nbjunit + + + + + + + org.netbeans.api.keyring + org.netbeans.spi.keyring + + + + diff --git a/keyring/src/org/netbeans/api/keyring/Keyring.java b/keyring/src/org/netbeans/api/keyring/Keyring.java new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/api/keyring/Keyring.java @@ -0,0 +1,125 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.api.keyring; + +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.netbeans.spi.keyring.KeyringProvider; +import org.openide.util.Lookup; +import org.openide.util.Parameters; + +/** + * Client class for working with stored keys (such as passwords). + *

The key identifier should be unique for the whole application, + * so qualify it with any prefixes as needed. + *

Avoid calling methods on this class from the event dispatch thread, + * as some provider implementations may need to block while displaying a dialog + * (e.g. prompting for a master password to access the keyring). + */ +public class Keyring { + + private Keyring() {} + + private static KeyringProvider PROVIDER; + private static KeyringProvider provider() { + if (PROVIDER == null) { + for (KeyringProvider p : Lookup.getDefault().lookupAll(KeyringProvider.class)) { + if (p.enabled()) { + PROVIDER = p; + break; + } + } + if (PROVIDER == null) { + PROVIDER = new DummyKeyringProvider(); + } + Logger.getLogger("org.netbeans.modules.keyring").log(Level.FINE, "Using provider: {0}", PROVIDER); + } + return PROVIDER; + } + + /** + * Reads a key from the ring. + * @param key the identifier of the key + * @return its value if found (you may null out its elements), else null if not present + */ + public static synchronized char[] read(String key) { + Parameters.notNull("key", key); + return provider().read(key); + } + + /** + * Saves a key to the ring. + * If it could not be saved, does nothing. + * If the key already existed, overwrites the password. + * @param key a key identifier + * @param password the password or other sensitive information associated with the key + * (its contents will be nulled out by end of call) + * @param description a user-visible description of the key (may be null) + */ + public static synchronized void save(String key, char[] password, String description) { + Parameters.notNull("key", key); + Parameters.notNull("password", password); + provider().save(key, password, description); + Arrays.fill(password, (char) 0); + } + + /** + * Deletes a key from the ring. + * If the key was not in the ring to begin with, does nothing. + * @param key a key identifier + */ + public static synchronized void delete(String key) { + Parameters.notNull("key", key); + provider().delete(key); + } + + private static class DummyKeyringProvider implements KeyringProvider { + public boolean enabled() { + return true; + } + public char[] read(String key) { + return null; + } + public void save(String key, char[] password, String description) {} + public void delete(String key) {} + } + +} diff --git a/keyring/src/org/netbeans/modules/keyring/Bundle.properties b/keyring/src/org/netbeans/modules/keyring/Bundle.properties new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/modules/keyring/Bundle.properties @@ -0,0 +1,1 @@ +OpenIDE-Module-Name=Keyring API diff --git a/keyring/src/org/netbeans/modules/keyring/Utils.java b/keyring/src/org/netbeans/modules/keyring/Utils.java new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/modules/keyring/Utils.java @@ -0,0 +1,69 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring; + +import java.util.prefs.Preferences; + +public class Utils { + + private Utils() {} + + public static byte[] chars2Bytes(char[] chars) { + byte[] bytes = new byte[chars.length * 2]; + for (int i = 0; i < chars.length; i++) { + bytes[i * 2] = (byte) (chars[i] / 256); + bytes[i * 2 + 1] = (byte) (chars[i] % 256); + } + return bytes; + } + + public static char[] bytes2Chars(byte[] bytes) { + char[] result = new char[bytes.length / 2]; + for (int i = 0; i < result.length; i++) { + result[i] = (char) (((int) bytes[i * 2]) * 256 + (int) bytes[i * 2 + 1]); + } + return result; + } + + public static void goMinusR(Preferences p) { + // XXX try to set $userdir/config/${p.absolutePath()}.properties to -rw------- + } + +} diff --git a/keyring/src/org/netbeans/modules/keyring/fallback/Bundle.properties b/keyring/src/org/netbeans/modules/keyring/fallback/Bundle.properties new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/modules/keyring/fallback/Bundle.properties @@ -0,0 +1,4 @@ +MasterPasswordPanel.masterPasswordLabel.text=Master &Password: +MasterPasswordPanel.setNewBox.text=&Change... +MasterPasswordPanel.newLabel1.text=&New Master Password: +MasterPasswordPanel.newLabel2.text=&Retype: diff --git a/keyring/src/org/netbeans/modules/keyring/fallback/FallbackProvider.java b/keyring/src/org/netbeans/modules/keyring/fallback/FallbackProvider.java new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/modules/keyring/fallback/FallbackProvider.java @@ -0,0 +1,173 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring.fallback; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.prefs.Preferences; +import org.netbeans.modules.keyring.Utils; +import org.netbeans.modules.keyring.spi.EncryptionProvider; +import org.netbeans.spi.keyring.KeyringProvider; +import org.openide.util.Lookup; +import org.openide.util.NbPreferences; +import org.openide.util.lookup.ServiceProvider; + +/** + * Platform-independent keyring provider using a master password and the user directory. + */ +@ServiceProvider(service=KeyringProvider.class, position=1000) +public class FallbackProvider implements KeyringProvider, Callable { + + private static final Logger LOG = Logger.getLogger(FallbackProvider.class.getName()); + private static final String DESCRIPTION = ".description"; + private static final String SAMPLE_KEY = "__sample__"; + + private EncryptionProvider encryption; + + public boolean enabled() { + for (EncryptionProvider p : Lookup.getDefault().lookupAll(EncryptionProvider.class)) { + if (p.enabled()) { + encryption = p; + LOG.log(Level.FINE, "Using provider: {0}", p); + Preferences prefs = prefs(); + Utils.goMinusR(prefs); + p.encryptionChangingCallback(this); + if (!testSampleKey(prefs)) { + continue; + } + return true; + } + } + LOG.fine("No provider"); + return false; + } + + private boolean testSampleKey(Preferences prefs) { + byte[] ciphertext = prefs().getByteArray(SAMPLE_KEY, null); + if (ciphertext == null) { + save(SAMPLE_KEY, (SAMPLE_KEY + UUID.randomUUID()).toCharArray(), "Sample value ensuring that decryption is working."); // XXX I18N + LOG.fine("saved sample key"); + return true; + } else { + while (true) { + try { + if (new String(encryption.decrypt(ciphertext)).startsWith(SAMPLE_KEY)) { + LOG.fine("succeeded in decrypting sample key"); + return true; + } else { + LOG.fine("wrong result decrypting sample key"); + } + } catch (Exception x) { + LOG.log(Level.FINE, "failed to decrypt sample key", x); + } + if (!encryption.decryptionFailed()) { + LOG.fine("sample key decryption failed and are not retrying"); + return false; + } + LOG.fine("will retry decryption of sample key"); + } + } + } + + private Preferences prefs() { + return NbPreferences.forModule(FallbackProvider.class).node(encryption.id()); + } + + public char[] read(String key) { + byte[] ciphertext = prefs().getByteArray(key, null); + if (ciphertext == null) { + return null; + } + try { + return encryption.decrypt(ciphertext); + } catch (Exception x) { + LOG.log(Level.FINE, "failed to decrypt password for " + key, x); + } + return null; + } + + public void save(String key, char[] password, String description) { + Preferences prefs = prefs(); + try { + prefs.putByteArray(key, encryption.encrypt(password)); + } catch (Exception x) { + LOG.log(Level.FINE, "failed to encrypt password for " + key, x); + return; + } + if (description != null) { + // Preferences interface gives no access to *.properties comments, so: + prefs.put(key + DESCRIPTION, description); + } + } + + public void delete(String key) { + Preferences prefs = prefs(); + prefs.remove(key); + prefs.remove(key + DESCRIPTION); + } + + public Void call() throws Exception { // encryption changing + LOG.fine("encryption changing"); + Map saved = new HashMap(); + Preferences prefs = prefs(); + for (String k : prefs.keys()) { + if (k.endsWith(DESCRIPTION)) { + continue; + } + byte[] ciphertext = prefs.getByteArray(k, null); + if (ciphertext == null) { + continue; + } + saved.put(k, encryption.decrypt(ciphertext)); + } + LOG.log(Level.FINE, "reencrypting keys: {0}", saved.keySet()); + encryption.encryptionChanged(); + for (Map.Entry entry : saved.entrySet()) { + prefs.putByteArray(entry.getKey(), encryption.encrypt(entry.getValue())); + } + LOG.fine("encryption changing finished"); + return null; + } + +} diff --git a/keyring/src/org/netbeans/modules/keyring/fallback/MasterPasswordEncryption.java b/keyring/src/org/netbeans/modules/keyring/fallback/MasterPasswordEncryption.java new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/modules/keyring/fallback/MasterPasswordEncryption.java @@ -0,0 +1,217 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring.fallback; + +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.KeySpec; +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.prefs.Preferences; +import javax.crypto.Cipher; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.PBEParameterSpec; +import org.netbeans.modules.keyring.Utils; +import org.netbeans.modules.keyring.spi.EncryptionProvider; +import org.openide.util.NbPreferences; +import org.openide.util.lookup.ServiceProvider; + +/** + * Encrypts data using a master password which the user must enter for each NetBeans session. + */ +@ServiceProvider(service=EncryptionProvider.class, position=1000) +public class MasterPasswordEncryption implements EncryptionProvider { + + private static final Logger LOG = Logger.getLogger(MasterPasswordEncryption.class.getName()); + private static final String ENCRYPTION_ALGORITHM = "PBEWithSHA1AndDESede"; // NOI18N + private static SecretKeyFactory KEY_FACTORY; + private static AlgorithmParameterSpec PARAM_SPEC; + + private Cipher encrypt, decrypt; + private boolean unlocked; + private Callable encryptionChanging; + private char[] newMasterPassword; + + public boolean enabled() { + try { + KEY_FACTORY = SecretKeyFactory.getInstance(ENCRYPTION_ALGORITHM); + encrypt = Cipher.getInstance(ENCRYPTION_ALGORITHM); + decrypt = Cipher.getInstance(ENCRYPTION_ALGORITHM); + Preferences prefs = NbPreferences.forModule(MasterPasswordEncryption.class); + Utils.goMinusR(prefs); + String saltKey = "salt"; // NOI18N + byte[] salt = prefs.getByteArray(saltKey, null); + if (salt == null) { + salt = UUID.randomUUID().toString().getBytes(); + prefs.putByteArray(saltKey, salt); + } + PARAM_SPEC = new PBEParameterSpec(salt, 20); + return true; + } catch (Exception x) { + LOG.log(Level.INFO, "Cannot initialize security using " + ENCRYPTION_ALGORITHM, x); + return false; + } + } + + public String id() { + return "general"; // NOI18N + } + + public byte[] encrypt(char[] cleartext) throws Exception { + if (!unlockIfNecessary()) { + throw new Exception("cannot unlock"); + } + try { + return doEncrypt(cleartext); + } catch (Exception x) { + unlocked = false; // reset + throw x; + } + } + + public char[] decrypt(byte[] ciphertext) throws Exception { + AtomicBoolean callEncryptionChanging = new AtomicBoolean(); + if (!unlockIfNecessary(callEncryptionChanging)) { + throw new Exception("cannot unlock"); + } + try { + return doDecrypt(ciphertext); + } catch (Exception x) { + unlocked = false; // reset + throw x; + } finally { + if (callEncryptionChanging.get()) { + try { + encryptionChanging.call(); + } catch (Exception x) { + LOG.log(Level.FINE, "failed to change encryption", x); + } + } + } + } + + private boolean unlockIfNecessary() { + AtomicBoolean callEncryptionChanging = new AtomicBoolean(); + boolean result = unlockIfNecessary(callEncryptionChanging); + if (callEncryptionChanging.get()) { + try { + encryptionChanging.call(); + } catch (Exception x) { + LOG.log(Level.FINE, "failed to change encryption", x); + } + } + return result; + } + private boolean unlockIfNecessary(AtomicBoolean callEncryptionChanging) { + if (unlocked) { + return true; + } + char[][] passwords = new MasterPasswordPanel().display(); + if (passwords == null) { + LOG.fine("cancelled master password dialog"); + return false; + } + try { + unlock(passwords[0]); + Arrays.fill(passwords[0], '\0'); + if (passwords.length == 2) { + newMasterPassword = passwords[1]; + LOG.fine("will set new master password"); + callEncryptionChanging.set(true); + } + return true; + } catch (Exception x) { + LOG.log(Level.FINE, "failed to initialize ciphers", x); + return false; + } + } + + void unlock(char[] masterPassword) throws Exception { + LOG.fine("switching to new master password"); + KeySpec keySpec = new PBEKeySpec(masterPassword); + Key key = KEY_FACTORY.generateSecret(keySpec); + encrypt.init(Cipher.ENCRYPT_MODE, key, PARAM_SPEC); + decrypt.init(Cipher.DECRYPT_MODE, key, PARAM_SPEC); + unlocked = true; + } + + byte[] doEncrypt(char[] cleartext) throws Exception { + assert unlocked; + byte[] cleartextB = Utils.chars2Bytes(cleartext); + byte[] result = encrypt.doFinal(cleartextB); + Arrays.fill(cleartextB, (byte) 0); + return result; + } + + char[] doDecrypt(byte[] ciphertext) throws Exception { + assert unlocked; + byte[] result = decrypt.doFinal(ciphertext); + char[] cleartext = Utils.bytes2Chars(result); + Arrays.fill(result, (byte) 0); + return cleartext; + } + + public boolean decryptionFailed() { + unlocked = false; + return unlockIfNecessary(); + } + + public void encryptionChangingCallback(Callable callback) { + encryptionChanging = callback; + } + + public void encryptionChanged() { + assert newMasterPassword != null; + LOG.fine("encryption changed"); + try { + unlock(newMasterPassword); + } catch (Exception x) { + LOG.log(Level.FINE, "failed to initialize ciphers", x); + } + Arrays.fill(newMasterPassword, '\0'); + newMasterPassword = null; + } + +} diff --git a/keyring/src/org/netbeans/modules/keyring/fallback/MasterPasswordPanel.form b/keyring/src/org/netbeans/modules/keyring/fallback/MasterPasswordPanel.form new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/modules/keyring/fallback/MasterPasswordPanel.form @@ -0,0 +1,119 @@ + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/keyring/src/org/netbeans/modules/keyring/fallback/MasterPasswordPanel.java b/keyring/src/org/netbeans/modules/keyring/fallback/MasterPasswordPanel.java new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/modules/keyring/fallback/MasterPasswordPanel.java @@ -0,0 +1,202 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring.fallback; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Arrays; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.openide.DialogDisplayer; +import org.openide.NotificationLineSupport; +import org.openide.NotifyDescriptor; + +class MasterPasswordPanel extends JPanel { + + public MasterPasswordPanel() { + initComponents(); + } + + /** + * Shows this dialog. + * @return master password, and if selected, new master password; or null if cancelled + */ + public char[][] display() { + // XXX I18N + final JButton ok = new JButton("OK"); + ok.setDefaultCapable(true); + NotifyDescriptor d = new NotifyDescriptor(this, "Enter Master Password", NotifyDescriptor.OK_CANCEL_OPTION, NotifyDescriptor.PLAIN_MESSAGE, + new Object[] {ok, NotifyDescriptor.CANCEL_OPTION}, ok); + final NotificationLineSupport notification = d.createNotificationLineSupport(); + final Runnable update = new Runnable() { + public void run() { + if (masterPasswordField.getPassword().length == 0) { + notification.setInformationMessage("Enter password"); + ok.setEnabled(false); + return; + } + boolean changing = setNewBox.isSelected(); + newLabel1.setEnabled(changing); + newField1.setEnabled(changing); + newLabel2.setEnabled(changing); + newField2.setEnabled(changing); + if (changing) { + if (newField1.getPassword().length == 0) { + notification.setInformationMessage("Enter new password"); + ok.setEnabled(false); + return; + } + if (!Arrays.equals(newField1.getPassword(), newField2.getPassword())) { + notification.setInformationMessage("New passwords do not match"); + ok.setEnabled(false); + return; + } + } + notification.clearMessages(); + ok.setEnabled(true); + } + }; + DocumentListener listener = new DocumentListener() { + public void insertUpdate(DocumentEvent e) { + update.run(); + } + public void removeUpdate(DocumentEvent e) { + update.run(); + } + public void changedUpdate(DocumentEvent e) {} + }; + update.run(); + masterPasswordField.getDocument().addDocumentListener(listener); + newField1.getDocument().addDocumentListener(listener); + newField2.getDocument().addDocumentListener(listener); + setNewBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + update.run(); + } + }); + if (DialogDisplayer.getDefault().notify(d) != ok) { + return null; + } + char[] masterPassword = masterPasswordField.getPassword(); + return setNewBox.isSelected() ? new char[][] {masterPassword, newField1.getPassword()} : new char[][] {masterPassword}; + } + + // //GEN-BEGIN:initComponents + private void initComponents() { + + masterPasswordLabel = new javax.swing.JLabel(); + masterPasswordField = new javax.swing.JPasswordField(); + setNewBox = new javax.swing.JCheckBox(); + newLabel1 = new javax.swing.JLabel(); + newField1 = new javax.swing.JPasswordField(); + newLabel2 = new javax.swing.JLabel(); + newField2 = new javax.swing.JPasswordField(); + + masterPasswordLabel.setLabelFor(masterPasswordField); + org.openide.awt.Mnemonics.setLocalizedText(masterPasswordLabel, org.openide.util.NbBundle.getMessage(MasterPasswordPanel.class, "MasterPasswordPanel.masterPasswordLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(setNewBox, org.openide.util.NbBundle.getMessage(MasterPasswordPanel.class, "MasterPasswordPanel.setNewBox.text")); // NOI18N + + newLabel1.setLabelFor(newField1); + org.openide.awt.Mnemonics.setLocalizedText(newLabel1, org.openide.util.NbBundle.getMessage(MasterPasswordPanel.class, "MasterPasswordPanel.newLabel1.text")); // NOI18N + newLabel1.setEnabled(false); + + newField1.setEnabled(false); + + newLabel2.setLabelFor(newField2); + org.openide.awt.Mnemonics.setLocalizedText(newLabel2, org.openide.util.NbBundle.getMessage(MasterPasswordPanel.class, "MasterPasswordPanel.newLabel2.text")); // NOI18N + newLabel2.setEnabled(false); + + newField2.setEnabled(false); + + org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .addContainerGap() + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(newLabel1) + .add(newLabel2) + .add(masterPasswordLabel)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(masterPasswordField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 224, Short.MAX_VALUE) + .add(newField2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 224, Short.MAX_VALUE) + .add(newField1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 224, Short.MAX_VALUE))) + .add(setNewBox)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .addContainerGap() + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(masterPasswordLabel) + .add(masterPasswordField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .add(18, 18, 18) + .add(setNewBox) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(newLabel1) + .add(newField1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(newLabel2) + .add(newField2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPasswordField masterPasswordField; + private javax.swing.JLabel masterPasswordLabel; + private javax.swing.JPasswordField newField1; + private javax.swing.JPasswordField newField2; + private javax.swing.JLabel newLabel1; + private javax.swing.JLabel newLabel2; + private javax.swing.JCheckBox setNewBox; + // End of variables declaration//GEN-END:variables + +} diff --git a/keyring/src/org/netbeans/modules/keyring/gnome/GnomeKeyringLibrary.java b/keyring/src/org/netbeans/modules/keyring/gnome/GnomeKeyringLibrary.java new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/modules/keyring/gnome/GnomeKeyringLibrary.java @@ -0,0 +1,80 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring.gnome; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Structure; + +/** + * @see gnome-keyring API Reference + */ +public interface GnomeKeyringLibrary extends Library { + + GnomeKeyringLibrary LIBRARY = (GnomeKeyringLibrary) Native.loadLibrary("gnome-keyring", GnomeKeyringLibrary.class); + + boolean gnome_keyring_is_available(); + + int gnome_keyring_store_password_sync(GnomeKeyringPasswordSchema schema, + String keyring, + String display_name, + String password, + String... attrs); + + int gnome_keyring_find_password_sync(GnomeKeyringPasswordSchema schema, + String[] password, + String... attrs); + + int gnome_keyring_delete_password_sync(GnomeKeyringPasswordSchema schema, + String... attrs); + + void g_set_application_name(String name); + + class GnomeKeyringPasswordSchema extends Structure { + public int item_type; + public GnomeKeyringPasswordSchemaAttribute[] attributes = new GnomeKeyringPasswordSchemaAttribute[32]; + } + + class GnomeKeyringPasswordSchemaAttribute extends Structure { + public String name; + public int type; + } + +} diff --git a/keyring/src/org/netbeans/modules/keyring/gnome/GnomeProvider.java b/keyring/src/org/netbeans/modules/keyring/gnome/GnomeProvider.java new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/modules/keyring/gnome/GnomeProvider.java @@ -0,0 +1,131 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring.gnome; + +import java.text.MessageFormat; +import java.util.MissingResourceException; +import java.util.logging.Level; +import java.util.logging.Logger; +import static org.netbeans.modules.keyring.gnome.GnomeKeyringLibrary.*; +import org.netbeans.spi.keyring.KeyringProvider; +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service=KeyringProvider.class, position=100) +public class GnomeProvider implements KeyringProvider { + + private static final Logger LOG = Logger.getLogger(GnomeProvider.class.getName()); + private static final String KEY = "key"; // NOI18N + + private static GnomeKeyringPasswordSchema SCHEMA; + + public boolean enabled() { + if (Boolean.getBoolean("netbeans.keyring.no.native")) { + LOG.fine("native keyring integration disabled"); + return false; + } + if (System.getenv("GNOME_KEYRING_PID") == null) { // NOI18N + // XXX is this going to be set on all Gnome platforms? + LOG.fine("GNOME_KEYRING_PID not set"); + return false; + } + String appName; + try { + appName = MessageFormat.format( + NbBundle.getBundle("org.netbeans.core.windows.view.ui.Bundle").getString("CTL_MainWindow_Title_No_Project"), + System.getProperty("netbeans.buildnumber")); + } catch (MissingResourceException x) { + appName = "NetBeans"; // NOI18N + } + try { + // Need to do this somewhere, or we get warnings on console. + // Also used by confirmation dialogs to give the app access to the login keyring. + LIBRARY.g_set_application_name(appName); + if (!LIBRARY.gnome_keyring_is_available()) { + return false; + } + SCHEMA = new GnomeKeyringPasswordSchema(); + SCHEMA.item_type = 0; // GNOME_KEYRING_ITEM_GENERIC_SECRET + SCHEMA.attributes[0] = new GnomeKeyringPasswordSchemaAttribute(); + SCHEMA.attributes[0].name = KEY; + SCHEMA.attributes[0].type = 0; // GNOME_KEYRING_ATTRIBUTE_TYPE_STRING + SCHEMA.attributes[1] = null; + return true; + } catch (Throwable t) { + LOG.log(Level.FINE, null, t); + return false; + } + } + + public char[] read(String key) { + // XXX try to use the char[] directly; not sure how to do this with JNA + String[] password = {null}; + error(GnomeKeyringLibrary.LIBRARY.gnome_keyring_find_password_sync(SCHEMA, password, KEY, key)); + return password[0] != null ? password[0].toCharArray() : null; + } + + public void save(String key, char[] password, String description) { + error(GnomeKeyringLibrary.LIBRARY.gnome_keyring_store_password_sync( + SCHEMA, null, description != null ? description : key, new String(password), KEY, key)); + } + + public void delete(String key) { + error(GnomeKeyringLibrary.LIBRARY.gnome_keyring_delete_password_sync(SCHEMA, KEY, key)); + } + + private static String[] ERRORS = { + "OK", // NOI18N + "DENIED", // NOI18N + "NO_KEYRING_DAEMON", // NOI18N + "ALREADY_UNLOCKED", // NOI18N + "NO_SUCH_KEYRING", // NOI18N + "BAD_ARGUMENTS", // NOI18N + "IO_ERROR", // NOI18N + "CANCELLED", // NOI18N + "KEYRING_ALREADY_EXISTS", // NOI18N + "NO_MATCH", // NOI18N + }; + private static void error(int code) { + if (code != 0 && code != 9) { + LOG.warning("gnome-keyring error: " + ERRORS[code]); + } + } + +} diff --git a/keyring/src/org/netbeans/modules/keyring/mac/MacProvider.java b/keyring/src/org/netbeans/modules/keyring/mac/MacProvider.java new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/modules/keyring/mac/MacProvider.java @@ -0,0 +1,119 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring.mac; + +import com.sun.jna.Pointer; +import java.io.UnsupportedEncodingException; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.netbeans.spi.keyring.KeyringProvider; +import org.openide.util.Utilities; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service=KeyringProvider.class, position=200) +public class MacProvider implements KeyringProvider { + + private static final Logger LOG = Logger.getLogger(MacProvider.class.getName()); + + public boolean enabled() { + if (Boolean.getBoolean("netbeans.keyring.no.native")) { + LOG.fine("native keyring integration disabled"); + return false; + } + return Utilities.isMac(); + } + + public char[] read(String key) { + try { + byte[] serviceName = key.getBytes("UTF-8"); + byte[] accountName = "NetBeans".getBytes("UTF-8"); + int[] dataLength = new int[1]; + Pointer[] data = new Pointer[1]; + error("find", SecurityLibrary.LIBRARY.SecKeychainFindGenericPassword(null, serviceName.length, serviceName, + accountName.length, accountName, dataLength, data, null)); + if (data[0] == null) { + return null; + } + byte[] value = data[0].getByteArray(0, dataLength[0]); // XXX ought to call SecKeychainItemFreeContent + return new String(value, "UTF-8").toCharArray(); + } catch (UnsupportedEncodingException x) { + LOG.log(Level.WARNING, null, x); + return null; + } + } + + public void save(String key, char[] password, String description) { + delete(key); // XXX supposed to use SecKeychainItemModifyContent instead, but this seems like too much work + try { + byte[] serviceName = key.getBytes("UTF-8"); + byte[] accountName = "NetBeans".getBytes("UTF-8"); + // Keychain Access seems to expect UTF-8, so do not use Utils.chars2Bytes: + byte[] data = new String(password).getBytes("UTF-8"); + error("save", SecurityLibrary.LIBRARY.SecKeychainAddGenericPassword(null, serviceName.length, serviceName, + accountName.length, accountName, data.length, data, null)); + } catch (UnsupportedEncodingException x) { + LOG.log(Level.WARNING, null, x); + } + // XXX use description somehow... better to use SecItemAdd with kSecAttrDescription + } + + public void delete(String key) { + try { + byte[] serviceName = key.getBytes("UTF-8"); + byte[] accountName = "NetBeans".getBytes("UTF-8"); + Pointer[] itemRef = new Pointer[1]; + error("find (for delete)", SecurityLibrary.LIBRARY.SecKeychainFindGenericPassword(null, serviceName.length, serviceName, + accountName.length, accountName, null, null, itemRef)); + if (itemRef[0] != null) { + error("delete", SecurityLibrary.LIBRARY.SecKeychainItemDelete(itemRef[0])); + } + } catch (UnsupportedEncodingException x) { + LOG.log(Level.WARNING, null, x); + } + } + + private static void error(String msg, int code) { + if (code != 0 && code != /* errSecItemNotFound, always returned from find it seems */-25300) { + // XXX translate, but SecCopyErrorMessageString returns weird CFStringRef + LOG.warning(msg + ": " + code); + } + } + +} diff --git a/keyring/src/org/netbeans/modules/keyring/mac/SecurityLibrary.java b/keyring/src/org/netbeans/modules/keyring/mac/SecurityLibrary.java new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/modules/keyring/mac/SecurityLibrary.java @@ -0,0 +1,79 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring.mac; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Pointer; + +/** + * @see Security Framework Reference + */ +public interface SecurityLibrary extends Library { + + SecurityLibrary LIBRARY = (SecurityLibrary) Native.loadLibrary("Security", SecurityLibrary.class); + + int SecKeychainAddGenericPassword( + Pointer keychain, + int serviceNameLength, + byte[] serviceName, + int accountNameLength, + byte[] accountName, + int passwordLength, + byte[] passwordData, + Pointer itemRef + ); + + int SecKeychainFindGenericPassword( + Pointer keychainOrArray, + int serviceNameLength, + byte[] serviceName, + int accountNameLength, + byte[] accountName, + int[] passwordLength, + Pointer[] passwordData, + Pointer[] itemRef + ); + + int SecKeychainItemDelete( + Pointer itemRef + ); + +} diff --git a/keyring/src/org/netbeans/modules/keyring/spi/EncryptionProvider.java b/keyring/src/org/netbeans/modules/keyring/spi/EncryptionProvider.java new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/modules/keyring/spi/EncryptionProvider.java @@ -0,0 +1,116 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring.spi; + +import java.util.concurrent.Callable; +import org.netbeans.spi.keyring.KeyringProvider; + +/** + * A weaker version of {@link KeyringProvider} which can only encrypt passwords securely. + * Rather than managing the complete storage of the keyring, a NetBeans-specific keyring + * is used, but this provider can encrypt the sensitive contents. + * The encryption is assumed to be symmetric but the encryption key should be secured. + * If the NetBeans {@link KeyringProvider} is used, the first encryption provider to be found + * in global lookup which claims to be enabled will be used; + * a standard implementation exists (at position 1000) which uses a simple master password. + */ +public interface EncryptionProvider { + + /** + * Check whether this provider can be used in the current JVM session. + * If integrating a native library, this should attempt to load it. + * This method will be called at most once per JVM session, + * prior to any other methods in this interface being called. + * @return true if this provider should be used, false if not + */ + boolean enabled(); + + /** + * Define a unique ID for this encryption provider, so that if the same userdir + * is reused on machines of different architecture the encrypted passwords will not conflict. + * @return an arbitrary ID specific to the algorithm + */ + String id(); + + /** + * Encrypt a password or other sensitive data so that only the current user can decrypt it. + * @param cleartext some data (may be nulled out after this call) + * @return encrypted data + * @throws Exception if anything goes wrong + */ + byte[] encrypt(char[] cleartext) throws Exception; + + /** + * Decrypt a password or other sensitive data. + * @param ciphertext encrypted data + * @return cleartext (may be nulled out after this call) + * @throws Exception if anything goes wrong + */ + char[] decrypt(byte[] ciphertext) throws Exception; + + /** + * Called if {@link #decrypt} produced incorrect results on a sample key. + * The provider can react by prompting again for a master password, for example. + *

Implementations which do not support dynamic changes to the encryption + * key or method should return false from this method. + * @return true if an attempt was made to correct the encryption, false if nothing has changed + */ + boolean decryptionFailed(); + + /** + * Offers a callback in case the encryption needs to change. + * For example, this may be employed if the user asks to change a master password. + * During the callback, the provider will be asked to decrypt existing secrets + * using the old encryption key; then {@link #encryptionChanged} + * will be called; finally the secrets will be reencrypted using the new encryption key. + *

Implementations which do not support dynamic changes to the encryption + * key or method may ignore this method. + * @param callback a callback which the provider may store and later call + */ + void encryptionChangingCallback(Callable callback); + + /** + * See {@link #encryptionChangingCallback} for description. + *

Implementations which do not support dynamic changes to the encryption + * key or method may ignore this method. + */ + void encryptionChanged(); + +} diff --git a/keyring/src/org/netbeans/modules/keyring/win32/Win32Protect.java b/keyring/src/org/netbeans/modules/keyring/win32/Win32Protect.java new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/modules/keyring/win32/Win32Protect.java @@ -0,0 +1,165 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring.win32; + +import com.sun.jna.Memory; +import com.sun.jna.Native; +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import com.sun.jna.WString; +import com.sun.jna.win32.StdCallLibrary; +import java.util.Arrays; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.netbeans.modules.keyring.Utils; +import org.netbeans.modules.keyring.spi.EncryptionProvider; +import org.openide.util.lookup.ServiceProvider; + +/** + * Data protection utility for Microsoft Windows. + * XXX org.tmatesoft.svn.core.internal.util.jna.SVNWinCrypt is a possibly more robust implementation + * (though it seems to set CRYPTPROTECT_UI_FORBIDDEN which we do not necessarily want). + */ +@ServiceProvider(service=EncryptionProvider.class, position=100) +public class Win32Protect implements EncryptionProvider { + + private static final Logger LOG = Logger.getLogger(Win32Protect.class.getName()); + + public boolean enabled() { + if (Boolean.getBoolean("netbeans.keyring.no.native")) { + LOG.fine("native keyring integration disabled"); + return false; + } + try { + if (CryptLib.INSTANCE == null) { + LOG.fine("loadLibrary -> null"); + return false; + } + return true; + } catch (Throwable t) { + LOG.log(Level.FINE, null, t); + return false; + } + } + + public String id() { + return "win32"; // NOI18N + } + + public byte[] encrypt(char[] cleartext) throws Exception { + byte[] cleartextB = Utils.chars2Bytes(cleartext); + CryptIntegerBlob input = new CryptIntegerBlob(); + input.store(cleartextB); + Arrays.fill(cleartextB, (byte) 0); + CryptIntegerBlob output = new CryptIntegerBlob(); + if (!CryptLib.INSTANCE.CryptProtectData(input, null, null, null, null, 0, output)) { + throw new Exception("CryptProtectData failed"); + } + input.zero(); + return output.load(); + } + + public char[] decrypt(byte[] ciphertext) throws Exception { + CryptIntegerBlob input = new CryptIntegerBlob(); + input.store(ciphertext); + CryptIntegerBlob output = new CryptIntegerBlob(); + if (!CryptLib.INSTANCE.CryptUnprotectData(input, null, null, null, null, 0, output)) { + throw new Exception("CryptUnprotectData failed"); + } + byte[] result = output.load(); + // XXX gives CCE because not a Memory: output.zero(); + char[] cleartext = Utils.bytes2Chars(result); + Arrays.fill(result, (byte) 0); + return cleartext; + } + + public boolean decryptionFailed() { + return false; // not much to do about it + } + + public void encryptionChangingCallback(Callable callback) {} + + public void encryptionChanged() { + assert false; + } + + public interface CryptLib extends StdCallLibrary { + CryptLib INSTANCE = (CryptLib) Native.loadLibrary("Crypt32", CryptLib.class); // NOI18N + /** @see Reference */ + boolean CryptProtectData( + CryptIntegerBlob pDataIn, + WString szDataDescr, + CryptIntegerBlob pOptionalEntropy, + Pointer pvReserved, + Pointer pPromptStruct, + int dwFlags, + CryptIntegerBlob pDataOut + )/* throws LastErrorException*/; + /** @see Reference */ + boolean CryptUnprotectData( + CryptIntegerBlob pDataIn, + WString[] ppszDataDescr, + CryptIntegerBlob pOptionalEntropy, + Pointer pvReserved, + Pointer pPromptStruct, + int dwFlags, + CryptIntegerBlob pDataOut + )/* throws LastErrorException*/; + } + + public static class CryptIntegerBlob extends Structure { + public int cbData; + public /*byte[]*/Pointer pbData; + byte[] load() { + return pbData.getByteArray(0, cbData); + // XXX how to free pbData? [Kernel32]LocalFree? + } + void store(byte[] data) { + cbData = data.length; + pbData = new Memory(data.length); + pbData.write(0, data, 0, cbData); + } + void zero() { + ((Memory) pbData).clear(); + } + } + +} diff --git a/keyring/src/org/netbeans/spi/keyring/KeyringProvider.java b/keyring/src/org/netbeans/spi/keyring/KeyringProvider.java new file mode 100644 --- /dev/null +++ b/keyring/src/org/netbeans/spi/keyring/KeyringProvider.java @@ -0,0 +1,88 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.spi.keyring; + +/** + * Provider for a keyring. + * Should be registered in global lookup. + * Providers will be searched in the order in which they are encountered + * until one which is {@link #enabled} is found. + * There is a default platform-independent implementation at position 1000 + * which should always be enabled. + *

All SPI calls are made from one thread at a time, so providers need not be synchronized. + */ +public interface KeyringProvider { + + /** + * Check whether this provider can be used in the current JVM session. + * If integrating a native keyring, this should attempt to load related + * libraries and check whether they can be found. + * This method will be called at most once per JVM session, + * prior to any other methods in this interface being called. + * @return true if this provider should be used, false if not + */ + boolean enabled(); + + /** + * Read a key from the ring. + * @param key the identifier of the key + * @return its value if found (elements may be later nulled out), else null if not present + */ + char[] read(String key); + + /** + * Save a key to the ring. + * If it could not be saved, do nothing. + * If the key already existed, overwrite the password. + * @param key a key identifier + * @param password the password or other sensitive information associated with the key + * (elements will be later nulled out) + * @param description a user-visible description of the key (may be null) + */ + void save(String key, char[] password, String description); + + /** + * Delete a key from the ring. + * If the key was not in the ring to begin with, do nothing. + * @param key a key identifier + */ + void delete(String key); + +} diff --git a/keyring/test/unit/src/org/netbeans/modules/keyring/KeyringProviderTestBase.java b/keyring/test/unit/src/org/netbeans/modules/keyring/KeyringProviderTestBase.java new file mode 100644 --- /dev/null +++ b/keyring/test/unit/src/org/netbeans/modules/keyring/KeyringProviderTestBase.java @@ -0,0 +1,78 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring; + +import java.util.UUID; +import org.netbeans.junit.NbTestCase; +import org.netbeans.spi.keyring.KeyringProvider; + +public abstract class KeyringProviderTestBase extends NbTestCase { + + protected KeyringProviderTestBase(String n) { + super(n); + } + + protected abstract KeyringProvider createProvider(); + + public void testStorage() throws Exception { + KeyringProvider p = createProvider(); + if (!p.enabled()) { + System.err.println(p + "disabled on " + System.getProperty("os.name") + ", skipping"); + return; + } + doTestStorage(p, "something", "secret stuff " + UUID.randomUUID(), null); + doTestStorage(p, "more", "secret stuff", "a description here"); + doTestStorage(p, "klíč", "hezky česky", "můj heslo"); + doTestStorage(p, "klā′vē ər", "ॐ", "κρυπτός"); + } + private void doTestStorage(KeyringProvider p, String key, String password, String description) throws Exception { + key = "KeyringProviderTestBase." + key; // avoid interfering with anything real + assertEquals(null, p.read(key)); + try { + p.save(key, password.toCharArray(), description); + char[] loaded = p.read(key); + assertEquals(password, loaded != null ? new String(loaded) : null); + } finally { + p.delete(key); + assertEquals(null, p.read(key)); + } + } + +} diff --git a/keyring/test/unit/src/org/netbeans/modules/keyring/fallback/MasterPasswordEncryptionTest.java b/keyring/test/unit/src/org/netbeans/modules/keyring/fallback/MasterPasswordEncryptionTest.java new file mode 100644 --- /dev/null +++ b/keyring/test/unit/src/org/netbeans/modules/keyring/fallback/MasterPasswordEncryptionTest.java @@ -0,0 +1,78 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring.fallback; + +import org.netbeans.junit.NbTestCase; + +public class MasterPasswordEncryptionTest extends NbTestCase { + + public MasterPasswordEncryptionTest(String n) { + super(n); + } + + public void testEncryption() throws Exception { + doTestEncryption("Top Secret!", "my password"); + doTestEncryption("some extra secret pass phrase", "something pretty long here for whatever reason..."); + // Only ASCII is apparently supported for master passwords: + doTestEncryption("muj heslo", "hezky česky"); + doTestEncryption("muj heslo", "ॐ"); + } + private void doTestEncryption(String masterPassword, String password) throws Exception { + MasterPasswordEncryption p = new MasterPasswordEncryption(); + assertTrue(p.enabled()); + p.unlock(masterPassword.toCharArray()); + assertEquals(password, new String(p.decrypt(p.encrypt(password.toCharArray())))); + } + + public void testWrongPassword() throws Exception { + MasterPasswordEncryption p = new MasterPasswordEncryption(); + assertTrue(p.enabled()); + p.unlock("first password".toCharArray()); + byte[] ciphertext = p.encrypt("secret".toCharArray()); + p.unlock("second password".toCharArray()); + try { + p.decrypt(ciphertext); + fail("should not be able to decrypt with incorrect password"); + } catch (Exception x) { + // expected: "BadPaddingException: Given final block not properly padded" + } + } + +} diff --git a/keyring/test/unit/src/org/netbeans/modules/keyring/gnome/GnomeProviderTest.java b/keyring/test/unit/src/org/netbeans/modules/keyring/gnome/GnomeProviderTest.java new file mode 100644 --- /dev/null +++ b/keyring/test/unit/src/org/netbeans/modules/keyring/gnome/GnomeProviderTest.java @@ -0,0 +1,55 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring.gnome; + +import org.netbeans.modules.keyring.KeyringProviderTestBase; +import org.netbeans.spi.keyring.KeyringProvider; + +public class GnomeProviderTest extends KeyringProviderTestBase { + + public GnomeProviderTest(String n) { + super(n); + } + + protected KeyringProvider createProvider() { + return new GnomeProvider(); + } + +} diff --git a/keyring/test/unit/src/org/netbeans/modules/keyring/mac/MacProviderTest.java b/keyring/test/unit/src/org/netbeans/modules/keyring/mac/MacProviderTest.java new file mode 100644 --- /dev/null +++ b/keyring/test/unit/src/org/netbeans/modules/keyring/mac/MacProviderTest.java @@ -0,0 +1,55 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring.mac; + +import org.netbeans.modules.keyring.KeyringProviderTestBase; +import org.netbeans.spi.keyring.KeyringProvider; + +public class MacProviderTest extends KeyringProviderTestBase { + + public MacProviderTest(String n) { + super(n); + } + + protected KeyringProvider createProvider() { + return new MacProvider(); + } + +} diff --git a/keyring/test/unit/src/org/netbeans/modules/keyring/win32/Win32ProtectTest.java b/keyring/test/unit/src/org/netbeans/modules/keyring/win32/Win32ProtectTest.java new file mode 100644 --- /dev/null +++ b/keyring/test/unit/src/org/netbeans/modules/keyring/win32/Win32ProtectTest.java @@ -0,0 +1,67 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.keyring.win32; + +import org.netbeans.junit.NbTestCase; + +public class Win32ProtectTest extends NbTestCase { + + public Win32ProtectTest(String n) { + super(n); + } + + public void testEncryption() throws Exception { + Win32Protect p = new Win32Protect(); + if (!p.enabled()) { + System.err.println("Skipping Win32ProtectTest on " + System.getProperty("os.name")); + return; + } + doTestEncryption(p, "my password"); + doTestEncryption(p, "something pretty long here for whatever reason..."); + doTestEncryption(p, "hezky česky"); + doTestEncryption(p, "ॐ"); + } + private void doTestEncryption(Win32Protect p, String password) throws Exception { + byte[] ciphertext = p.encrypt(password.toCharArray()); + //System.err.println(password + " -> " + Arrays.toString(ciphertext)); + assertEquals(password, new String(p.decrypt(ciphertext))); + } + +} diff --git a/libs.jna/nbproject/project.xml b/libs.jna/nbproject/project.xml --- a/libs.jna/nbproject/project.xml +++ b/libs.jna/nbproject/project.xml @@ -53,6 +53,7 @@ org.netbeans.core.nativeaccess org.netbeans.modules.dlight.nativeexecution org.netbeans.modules.extexecution.destroy + org.netbeans.modules.keyring org.netbeans.modules.maven.killer org.netbeans.modules.python.qshell com.sun.jna diff --git a/nbbuild/cluster.properties b/nbbuild/cluster.properties --- a/nbbuild/cluster.properties +++ b/nbbuild/cluster.properties @@ -207,6 +207,7 @@ editor.mimelookup.impl,\ favorites,\ javahelp,\ + keyring,\ libs.jna,\ libs.jsr223,\ libs.junit4,\ diff --git a/o.n.core/nbproject/project.xml b/o.n.core/nbproject/project.xml --- a/o.n.core/nbproject/project.xml +++ b/o.n.core/nbproject/project.xml @@ -74,6 +74,14 @@ + org.netbeans.modules.keyring + + + + 1.0 + + + org.netbeans.swing.plaf diff --git a/o.n.core/src/org/netbeans/core/Bundle.properties b/o.n.core/src/org/netbeans/core/Bundle.properties --- a/o.n.core/src/org/netbeans/core/Bundle.properties +++ b/o.n.core/src/org/netbeans/core/Bundle.properties @@ -179,5 +179,9 @@ # NbAuthenticatorPanel NbAuthenticatorPanel.userNameLbl.text=&User Name: NbAuthenticatorPanel.passwordLbl.text=&Password: +# {0} - realm (domain name?) +NbAuthenticatorPanel.password.description=Password for {0} + +ProxySettings.password.description=Proxy password Title_WebBrowser=Web Browser diff --git a/o.n.core/src/org/netbeans/core/NbAuthenticatorPanel.java b/o.n.core/src/org/netbeans/core/NbAuthenticatorPanel.java --- a/o.n.core/src/org/netbeans/core/NbAuthenticatorPanel.java +++ b/o.n.core/src/org/netbeans/core/NbAuthenticatorPanel.java @@ -38,6 +38,11 @@ */ package org.netbeans.core; +import java.util.prefs.Preferences; +import org.netbeans.api.keyring.Keyring; +import org.openide.util.NbBundle; +import org.openide.util.NbPreferences; + /** * * @author lukas @@ -45,11 +50,23 @@ final class NbAuthenticatorPanel extends javax.swing.JPanel { private String realmName; + private final Preferences prefs; + private final String keyringKey; /** Creates new form AuthenticatorPanel */ public NbAuthenticatorPanel(String realmName) { this.realmName = realmName; initComponents(); + prefs = NbPreferences.forModule(NbAuthenticatorPanel.class).node("authentication"); // NOI18N + keyringKey = "authentication." + realmName; // NOI18N + String username = prefs.get(realmName, null); + if (username != null) { + userName.setText(username); + char[] pwd = Keyring.read(keyringKey); + if (pwd != null) { + password.setText(new String(pwd)); + } + } } /** This method is called from within the constructor to @@ -117,10 +134,14 @@ // End of variables declaration//GEN-END:variables public char[] getPassword() { - return password.getPassword(); + Keyring.save(keyringKey, password.getPassword(), + NbBundle.getMessage(NbAuthenticatorPanel.class, "NbAuthenticatorPanel.password.description", realmName)); + return password.getPassword(); // call getPassword again, since previous return value nulled out } public String getUserName() { - return userName.getText(); + String username = userName.getText(); + prefs.put(realmName, username); + return username; } } diff --git a/o.n.core/src/org/netbeans/core/ProxySettings.java b/o.n.core/src/org/netbeans/core/ProxySettings.java --- a/o.n.core/src/org/netbeans/core/ProxySettings.java +++ b/o.n.core/src/org/netbeans/core/ProxySettings.java @@ -49,6 +49,8 @@ import java.util.StringTokenizer; import java.util.prefs.PreferenceChangeListener; import java.util.prefs.Preferences; +import org.netbeans.api.keyring.Keyring; +import org.openide.util.NbBundle; import org.openide.util.NbPreferences; import org.openide.util.Utilities; @@ -148,9 +150,21 @@ } public static char[] getAuthenticationPassword () { - return getPreferences ().get (PROXY_AUTHENTICATION_PASSWORD, "").toCharArray (); + String old = getPreferences().get(PROXY_AUTHENTICATION_PASSWORD, null); + if (old != null) { + getPreferences().remove(PROXY_AUTHENTICATION_PASSWORD); + setAuthenticationPassword(old.toCharArray()); + } + char[] pwd = Keyring.read(PROXY_AUTHENTICATION_PASSWORD); + return pwd != null ? pwd : new char[0]; } + public static void setAuthenticationPassword(char[] password) { + Keyring.save(ProxySettings.PROXY_AUTHENTICATION_PASSWORD, password, + // XXX consider including getHttpHost and/or getHttpsHost + NbBundle.getMessage(ProxySettings.class, "ProxySettings.password.description")); + } + static void addPreferenceChangeListener (PreferenceChangeListener l) { getPreferences ().addPreferenceChangeListener (l); } @@ -158,7 +172,7 @@ static void removePreferenceChangeListener (PreferenceChangeListener l) { getPreferences ().removePreferenceChangeListener (l); } - + static class SystemProxySettings extends ProxySettings { public static String getHttpHost () { diff --git a/uihandler/nbproject/project.xml b/uihandler/nbproject/project.xml --- a/uihandler/nbproject/project.xml +++ b/uihandler/nbproject/project.xml @@ -73,6 +73,14 @@ + org.netbeans.modules.keyring + + + + 1.0 + + + org.netbeans.modules.options.api diff --git a/uihandler/src/org/netbeans/modules/exceptions/Bundle.properties b/uihandler/src/org/netbeans/modules/exceptions/Bundle.properties --- a/uihandler/src/org/netbeans/modules/exceptions/Bundle.properties +++ b/uihandler/src/org/netbeans/modules/exceptions/Bundle.properties @@ -49,3 +49,5 @@ submit a bug at \ http://www.netbeans.org/issues/enter_bug.cgi against logger, please. LoadingMessage=Loading + +ExceptionsSettings.password.description=Exception reporter login diff --git a/uihandler/src/org/netbeans/modules/exceptions/ExceptionsSettings.java b/uihandler/src/org/netbeans/modules/exceptions/ExceptionsSettings.java --- a/uihandler/src/org/netbeans/modules/exceptions/ExceptionsSettings.java +++ b/uihandler/src/org/netbeans/modules/exceptions/ExceptionsSettings.java @@ -42,6 +42,8 @@ package org.netbeans.modules.exceptions; import java.util.prefs.Preferences; +import org.netbeans.api.keyring.Keyring; +import org.openide.util.NbBundle; import org.openide.util.NbPreferences; /** @@ -52,6 +54,7 @@ private static final String userProp = "UserName"; // NOI18N private static final String passwdProp = "Passwd"; + private static final String passwdKey = "exceptionreporter"; // NOI18N private static final String guestProp = "Guest"; private static final String rememberProp = "RememberPasswd"; @@ -73,11 +76,18 @@ } public String getPasswd() { - return prefs().get(passwdProp, ""); + String old = prefs().get(passwdProp, null); + if (old != null) { + setPasswd(old); + prefs().remove(passwdProp); + } + char[] pwd = Keyring.read(passwdKey); + return pwd != null ? new String(pwd) : ""; } public void setPasswd(String passwd) { - prefs().put(passwdProp, passwd); + Keyring.save(passwdKey, passwd.toCharArray(), + NbBundle.getMessage(ExceptionsSettings.class, "ExceptionsSettings.password.description")); } public boolean isGuest() {