blob: 85b1ec0a1b0570fe9fb6862a1f501e5a9860c82e [file] [log] [blame]
/*
* 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][];
}
}