| /* |
| * Copyright 2000-2012 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.intellij.ide.passwordSafe.impl.providers.masterKey; |
| |
| import com.intellij.ide.passwordSafe.impl.providers.ByteArrayWrapper; |
| import com.intellij.openapi.components.PersistentStateComponent; |
| import com.intellij.openapi.components.State; |
| import com.intellij.openapi.components.Storage; |
| import com.intellij.openapi.components.StoragePathMacros; |
| import com.intellij.openapi.diagnostic.Logger; |
| import org.apache.commons.codec.DecoderException; |
| import org.apache.commons.codec.binary.Hex; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.TreeMap; |
| |
| /** |
| * The password database. The internal component for {@link MasterKeyPasswordSafe}. |
| */ |
| @State( |
| name = "PasswordDatabase", |
| storages = {@Storage( |
| file = StoragePathMacros.APP_CONFIG + "/security.xml")}) |
| public class PasswordDatabase implements PersistentStateComponent<PasswordDatabase.State> { |
| /** |
| * The name of logger |
| */ |
| private final static Logger LOG = Logger.getInstance(PasswordDatabase.class.getName()); |
| /** |
| * The password database |
| */ |
| private transient final Map<ByteArrayWrapper, byte[]> myDatabase = new HashMap<ByteArrayWrapper, byte[]>(); |
| /** |
| * OS-specific information about master password |
| */ |
| private transient byte[] myMasterPasswordInfo; |
| |
| /** |
| * Clear the password database |
| */ |
| public void clear() { |
| synchronized (myDatabase) { |
| myDatabase.clear(); |
| } |
| } |
| |
| /** |
| * @return true if the database is empty |
| */ |
| public boolean isEmpty() { |
| synchronized (myDatabase) { |
| return myDatabase.isEmpty(); |
| } |
| } |
| |
| /** |
| * Put password in the database |
| * |
| * @param key the encrypted key |
| * @param value the encrypted value |
| */ |
| public void put(byte[] key, byte[] value) { |
| synchronized (myDatabase) { |
| myDatabase.put(new ByteArrayWrapper(key), value); |
| } |
| } |
| |
| /** |
| * Get all entries in the database |
| * |
| * @param copy the copy to use |
| */ |
| public void copyTo(Map<ByteArrayWrapper, byte[]> copy) { |
| synchronized (myDatabase) { |
| copy.putAll(myDatabase); |
| } |
| } |
| |
| /** |
| * Put all entries to the database |
| * |
| * @param copy the copy to use |
| */ |
| public void putAll(Map<ByteArrayWrapper, byte[]> copy) { |
| synchronized (myDatabase) { |
| myDatabase.putAll(copy); |
| } |
| } |
| |
| |
| /** |
| * Get password from the database |
| * |
| * @param key the encrypted key |
| * @return the encrypted value or null |
| */ |
| public byte[] get(byte[] key) { |
| synchronized (myDatabase) { |
| return myDatabase.get(new ByteArrayWrapper(key)); |
| } |
| |
| } |
| |
| /** |
| * Remove password from the database |
| * |
| * @param key the encrypted key |
| */ |
| public void remove(byte[] key) { |
| synchronized (myDatabase) { |
| myDatabase.remove(new ByteArrayWrapper(key)); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public State getState() { |
| TreeMap<ByteArrayWrapper, byte[]> sorted; |
| String pi; |
| synchronized (myDatabase) { |
| pi = toHex(myMasterPasswordInfo); |
| sorted = new TreeMap<ByteArrayWrapper, byte[]>(myDatabase); |
| } |
| String[][] db = new String[2][sorted.size()]; |
| int i = 0; |
| for (Map.Entry<ByteArrayWrapper, byte[]> e : sorted.entrySet()) { |
| db[0][i] = toHex(e.getKey().unwrap()); |
| db[1][i] = toHex(e.getValue()); |
| i++; |
| } |
| State s = new State(); |
| s.PASSWORDS = db; |
| s.MASTER_PASSWORD_INFO = pi; |
| return s; |
| } |
| |
| |
| /** |
| * Covert bytes to hex |
| * |
| * @param bytes bytes to convert |
| * @return hex representation |
| */ |
| @Nullable |
| private static String toHex(byte[] bytes) { |
| return bytes == null ? null : new String(Hex.encodeHex(bytes)); |
| } |
| |
| /** |
| * Covert hex to bytes |
| * |
| * @param hex string to convert |
| * @return bytes representation |
| * @throws DecoderException if invalid data encountered |
| */ |
| @Nullable |
| private static byte[] fromHex(String hex) throws DecoderException { |
| return hex == null ? null : Hex.decodeHex(hex.toCharArray()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void loadState(State state) { |
| String[][] db = state.PASSWORDS; |
| String pi = state.MASTER_PASSWORD_INFO; |
| synchronized (myDatabase) { |
| try { |
| myMasterPasswordInfo = fromHex(pi); |
| if (myMasterPasswordInfo == null) { |
| myMasterPasswordInfo = new byte[0]; |
| } |
| } |
| catch (DecoderException e) { |
| myMasterPasswordInfo = new byte[0]; |
| } |
| myDatabase.clear(); |
| if (db[0].length != db[1].length) { |
| LOG.warn("The password database is in inconsistent state, ignoring it: " + db[0].length + " != " + db[1].length); |
| } |
| int n = db[0].length; |
| for (int i = 0; i < n; i++) { |
| try { |
| byte[] key = fromHex(db[0][i]); |
| byte[] value = fromHex(db[1][i]); |
| if (key != null && value != null) { |
| myDatabase.put(new ByteArrayWrapper(key), value); |
| } |
| } |
| catch (DecoderException e) { |
| // skip the entry |
| } |
| } |
| } |
| } |
| |
| /** |
| * @return the object over which database is synchronized |
| */ |
| Object getDbLock() { |
| return myDatabase; |
| } |
| |
| /** |
| * @return master password information |
| */ |
| byte[] getPasswordInfo() { |
| synchronized (myDatabase) { |
| return myMasterPasswordInfo; |
| } |
| } |
| |
| /** |
| * Set master password information |
| * |
| * @param bytes the bytes for the master password |
| */ |
| public void setPasswordInfo(byte[] bytes) { |
| synchronized (myDatabase) { |
| myMasterPasswordInfo = bytes; |
| } |
| } |
| |
| /** |
| * The state for passwords database |
| */ |
| public static class State { |
| /** |
| * Information about master password (used in OS specific way) |
| */ |
| public String MASTER_PASSWORD_INFO = ""; |
| /** |
| * The password database |
| */ |
| public String[][] PASSWORDS = new String[0][]; |
| } |
| } |