blob: 55a149d3c44da2b41d262e7320109cabcd2db7fa [file] [log] [blame]
Stuart McCulloch2a0afd62012-09-06 18:28:06 +00001package aQute.lib.settings;
2
3import java.io.*;
4import java.security.*;
5import java.security.spec.*;
6import java.util.*;
7
8import aQute.lib.io.*;
9import aQute.lib.json.*;
10
11/**
12 * Maintains persistent settings for bnd (or other apps). The default is
13 * ~/.bnd/settings.json). The settings are normal string properties but it
14 * specially maintains a public/private key pair and it provides a method to
15 * sign a byte array with this pair.
16 * <p/>
17 * Why not keystore and preferences? Well, keystore is hard to use (you can only
18 * store a private key when you have a certificate, but you cannot create a
19 * certificate without using com.sun classes) and preferences are not editable.
20 */
21public class Settings implements Map<String,String> {
22 static JSONCodec codec = new JSONCodec();
23
24 private File where;
25 private PublicKey publicKey;
26 private PrivateKey privateKey;
27 private boolean loaded;
28 private boolean dirty;
29
30 public static class Data {
31 public int version = 1;
32 public byte[] secret;
33 public byte[] id;
34 public Map<String,String> map = new HashMap<String,String>();
35 }
36
37 Data data = new Data();
38
39 public Settings() {
40 this("~/.bnd/settings.json");
41 }
42
43 public Settings(String where) {
44 assert where != null;
45 this.where = IO.getFile(IO.work, where);
46 }
47
48 public boolean load() {
49 if (this.where.isFile() && this.where.length() > 1) {
50 try {
51 data = codec.dec().from(this.where).get(Data.class);
52 loaded = true;
53 return true;
54 }
55 catch (Exception e) {
56 throw new RuntimeException("Cannot read settings file " + this.where, e);
57 }
58 }
59
60 if (!data.map.containsKey("name"))
61 data.map.put("name", System.getProperty("user.name"));
62 return false;
63 }
64
65 private void check() {
66 if (loaded)
67 return;
68 load();
69 loaded = true;
70 }
71
72 public void save() {
73 if (!this.where.getParentFile().isDirectory() && !this.where.getParentFile().mkdirs())
74 throw new RuntimeException("Cannot create directory in " + this.where.getParent());
75
76 try {
77 codec.enc().to(this.where).put(data).flush();
78 assert this.where.isFile();
79 }
80 catch (Exception e) {
81 throw new RuntimeException("Cannot write settings file " + this.where, e);
82 }
83 }
84
85 public void generate() throws Exception {
86 KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
87 SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
88 keyGen.initialize(1024, random);
89 KeyPair pair = keyGen.generateKeyPair();
90 privateKey = pair.getPrivate();
91 publicKey = pair.getPublic();
92 data.secret = privateKey.getEncoded();
93 data.id = publicKey.getEncoded();
94 save();
95 }
96
97 public String getEmail() {
98 return get("email");
99 }
100
101 public void setEmail(String email) {
102 put("email", email);
103 }
104
105 public void setName(String v) {
106 put("name", v);
107 }
108
109 public String getName() {
110 String name = get("name");
111 if (name != null)
112 return name;
113 return System.getProperty("user.name");
114 }
115
116 /**
117 * Return an encoded public RSA key. this key can be decoded with an
118 * X509EncodedKeySpec
119 *
120 * @return an encoded public key.
121 * @throws Exception
122 */
123 public byte[] getPublicKey() throws Exception {
124 initKeys();
125 return data.id;
126 }
127
128 /**
129 * Return an encoded private RSA key. this key can be decoded with an
130 * PKCS8EncodedKeySpec
131 *
132 * @return an encoded private key.
133 * @throws Exception
134 */
135 public byte[] getPrivateKey() throws Exception {
136 initKeys();
137 return data.secret;
138 }
139
140 /*
141 * Initialize the keys.
142 */
143 private void initKeys() throws Exception {
144 check();
145 if (publicKey != null)
146 return;
147
148 if (data.id == null || data.secret == null) {
149 generate();
150 } else {
151 PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(data.secret);
152 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(data.id);
153 KeyFactory keyFactory = KeyFactory.getInstance("RSA");
154 privateKey = keyFactory.generatePrivate(privateKeySpec);
155 publicKey = keyFactory.generatePublic(publicKeySpec);
156 }
157 }
158
159 /**
160 * Sign a byte array
161 */
162 public byte[] sign(byte[] con) throws Exception {
163 initKeys();
164
165 Signature hmac = Signature.getInstance("SHA1withRSA");
166 hmac.initSign(privateKey);
167 hmac.update(con);
168 return hmac.sign();
169 }
170
171 /**
172 * Verify a signed byte array
173 */
174 public boolean verify(byte[] con) throws Exception {
175 initKeys();
176
177 Signature hmac = Signature.getInstance("SHA1withRSA");
178 hmac.initVerify(publicKey);
179 hmac.update(con);
180 return hmac.verify(con);
181 }
182
183 public void clear() {
184 data = new Data();
185 IO.delete(where);
186 }
187
188 public boolean containsKey(Object key) {
189 check();
190 return data.map.containsKey(key);
191 }
192
193 public boolean containsValue(Object value) {
194 check();
195 return data.map.containsValue(value);
196 }
197
198 public Set<java.util.Map.Entry<String,String>> entrySet() {
199 check();
200 return data.map.entrySet();
201 }
202
203 public String get(Object key) {
204 check();
205
206 return data.map.get(key);
207 }
208
209 public boolean isEmpty() {
210 check();
211 return data.map.isEmpty();
212 }
213
214 public Set<String> keySet() {
215 check();
216 return data.map.keySet();
217 }
218
219 public String put(String key, String value) {
220 check();
221 dirty = true;
222 return data.map.put(key, value);
223 }
224
225 public void putAll(Map< ? extends String, ? extends String> v) {
226 check();
227 dirty = true;
228 data.map.putAll(v);
229 }
230
231 public String remove(Object key) {
232 check();
233 dirty = true;
234 return data.map.remove(key);
235 }
236
237 public int size() {
238 check();
239 return data.map.size();
240 }
241
242 public Collection<String> values() {
243 check();
244 return data.map.values();
245 }
246
247 public boolean isDirty() {
248 return dirty;
249 }
250
251}