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;
+ }
+
+}