Sync bndlib code

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1381708 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/lib/settings/Settings.java b/bundleplugin/src/main/java/aQute/lib/settings/Settings.java
new file mode 100644
index 0000000..55a149d
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/lib/settings/Settings.java
@@ -0,0 +1,251 @@
+package aQute.lib.settings;
+
+import java.io.*;
+import java.security.*;
+import java.security.spec.*;
+import java.util.*;
+
+import aQute.lib.io.*;
+import aQute.lib.json.*;
+
+/**
+ * Maintains persistent settings for bnd (or other apps). The default is
+ * ~/.bnd/settings.json). The settings are normal string properties but it
+ * specially maintains a public/private key pair and it provides a method to
+ * sign a byte array with this pair.
+ * <p/>
+ * Why not keystore and preferences? Well, keystore is hard to use (you can only
+ * store a private key when you have a certificate, but you cannot create a
+ * certificate without using com.sun classes) and preferences are not editable.
+ */
+public class Settings implements Map<String,String> {
+	static JSONCodec	codec	= new JSONCodec();
+
+	private File		where;
+	private PublicKey	publicKey;
+	private PrivateKey	privateKey;
+	private boolean		loaded;
+	private boolean		dirty;
+
+	public static class Data {
+		public int					version	= 1;
+		public byte[]				secret;
+		public byte[]				id;
+		public Map<String,String>	map		= new HashMap<String,String>();
+	}
+
+	Data	data	= new Data();
+
+	public Settings() {
+		this("~/.bnd/settings.json");
+	}
+
+	public Settings(String where) {
+		assert where != null;
+		this.where = IO.getFile(IO.work, where);
+	}
+
+	public boolean load() {
+		if (this.where.isFile() && this.where.length() > 1) {
+			try {
+				data = codec.dec().from(this.where).get(Data.class);
+				loaded = true;
+				return true;
+			}
+			catch (Exception e) {
+				throw new RuntimeException("Cannot read settings file " + this.where, e);
+			}
+		}
+
+		if (!data.map.containsKey("name"))
+			data.map.put("name", System.getProperty("user.name"));
+		return false;
+	}
+
+	private void check() {
+		if (loaded)
+			return;
+		load();
+		loaded = true;
+	}
+
+	public void save() {
+		if (!this.where.getParentFile().isDirectory() && !this.where.getParentFile().mkdirs())
+			throw new RuntimeException("Cannot create directory in " + this.where.getParent());
+
+		try {
+			codec.enc().to(this.where).put(data).flush();
+			assert this.where.isFile();
+		}
+		catch (Exception e) {
+			throw new RuntimeException("Cannot write settings file " + this.where, e);
+		}
+	}
+
+	public void generate() throws Exception {
+		KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+		SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
+		keyGen.initialize(1024, random);
+		KeyPair pair = keyGen.generateKeyPair();
+		privateKey = pair.getPrivate();
+		publicKey = pair.getPublic();
+		data.secret = privateKey.getEncoded();
+		data.id = publicKey.getEncoded();
+		save();
+	}
+
+	public String getEmail() {
+		return get("email");
+	}
+
+	public void setEmail(String email) {
+		put("email", email);
+	}
+
+	public void setName(String v) {
+		put("name", v);
+	}
+
+	public String getName() {
+		String name = get("name");
+		if (name != null)
+			return name;
+		return System.getProperty("user.name");
+	}
+
+	/**
+	 * Return an encoded public RSA key. this key can be decoded with an
+	 * X509EncodedKeySpec
+	 * 
+	 * @return an encoded public key.
+	 * @throws Exception
+	 */
+	public byte[] getPublicKey() throws Exception {
+		initKeys();
+		return data.id;
+	}
+
+	/**
+	 * Return an encoded private RSA key. this key can be decoded with an
+	 * PKCS8EncodedKeySpec
+	 * 
+	 * @return an encoded private key.
+	 * @throws Exception
+	 */
+	public byte[] getPrivateKey() throws Exception {
+		initKeys();
+		return data.secret;
+	}
+
+	/*
+	 * Initialize the keys.
+	 */
+	private void initKeys() throws Exception {
+		check();
+		if (publicKey != null)
+			return;
+
+		if (data.id == null || data.secret == null) {
+			generate();
+		} else {
+			PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(data.secret);
+			X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(data.id);
+			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+			privateKey = keyFactory.generatePrivate(privateKeySpec);
+			publicKey = keyFactory.generatePublic(publicKeySpec);
+		}
+	}
+
+	/**
+	 * Sign a byte array
+	 */
+	public byte[] sign(byte[] con) throws Exception {
+		initKeys();
+
+		Signature hmac = Signature.getInstance("SHA1withRSA");
+		hmac.initSign(privateKey);
+		hmac.update(con);
+		return hmac.sign();
+	}
+
+	/**
+	 * Verify a signed byte array
+	 */
+	public boolean verify(byte[] con) throws Exception {
+		initKeys();
+
+		Signature hmac = Signature.getInstance("SHA1withRSA");
+		hmac.initVerify(publicKey);
+		hmac.update(con);
+		return hmac.verify(con);
+	}
+
+	public void clear() {
+		data = new Data();
+		IO.delete(where);
+	}
+
+	public boolean containsKey(Object key) {
+		check();
+		return data.map.containsKey(key);
+	}
+
+	public boolean containsValue(Object value) {
+		check();
+		return data.map.containsValue(value);
+	}
+
+	public Set<java.util.Map.Entry<String,String>> entrySet() {
+		check();
+		return data.map.entrySet();
+	}
+
+	public String get(Object key) {
+		check();
+
+		return data.map.get(key);
+	}
+
+	public boolean isEmpty() {
+		check();
+		return data.map.isEmpty();
+	}
+
+	public Set<String> keySet() {
+		check();
+		return data.map.keySet();
+	}
+
+	public String put(String key, String value) {
+		check();
+		dirty = true;
+		return data.map.put(key, value);
+	}
+
+	public void putAll(Map< ? extends String, ? extends String> v) {
+		check();
+		dirty = true;
+		data.map.putAll(v);
+	}
+
+	public String remove(Object key) {
+		check();
+		dirty = true;
+		return data.map.remove(key);
+	}
+
+	public int size() {
+		check();
+		return data.map.size();
+	}
+
+	public Collection<String> values() {
+		check();
+		return data.map.values();
+	}
+
+	public boolean isDirty() {
+		return dirty;
+	}
+
+}