]> git.argeo.org Git - lgpl/argeo-commons.git/blob - JcrKeyring.java
7d9baad956ed9b9481230619ed80b4cafbc6a6cc
[lgpl/argeo-commons.git] / JcrKeyring.java
1 package org.argeo.jcr.security;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.InputStream;
5
6 import javax.crypto.Cipher;
7 import javax.crypto.CipherInputStream;
8 import javax.crypto.SecretKey;
9 import javax.crypto.spec.IvParameterSpec;
10 import javax.jcr.Binary;
11 import javax.jcr.Node;
12 import javax.jcr.Property;
13 import javax.jcr.RepositoryException;
14 import javax.jcr.Session;
15
16 import org.apache.commons.io.IOUtils;
17 import org.argeo.ArgeoException;
18 import org.argeo.jcr.ArgeoNames;
19 import org.argeo.jcr.ArgeoTypes;
20 import org.argeo.jcr.JcrUtils;
21 import org.argeo.util.crypto.AbstractKeyring;
22 import org.argeo.util.crypto.PBEKeySpecCallback;
23
24 /** JCR based implementation of a keyring */
25 public class JcrKeyring extends AbstractKeyring implements ArgeoNames {
26 private Session session;
27
28 /**
29 * When setup is called the session has not yet been saved and we don't want
30 * to save it since there maybe other data which would be inconsistent. So
31 * we keep a reference to this node which will then be used (an reset to
32 * null) when handling the PBE callback. We keep one per thread in case
33 * multiple users are accessing the same instance of a keyring.
34 */
35 private ThreadLocal<Node> notYetSavedKeyring = new ThreadLocal<Node>() {
36
37 @Override
38 protected Node initialValue() {
39 return null;
40 }
41 };
42
43 @Override
44 protected Boolean isSetup() {
45 try {
46 if (notYetSavedKeyring.get() != null)
47 return true;
48
49 Node userHome = JcrUtils.getUserHome(session);
50 return userHome.hasNode(ARGEO_KEYRING);
51 } catch (RepositoryException e) {
52 throw new ArgeoException("Cannot check whether keyring is setup", e);
53 }
54 }
55
56 @Override
57 protected void setup() {
58 Binary binary = null;
59 InputStream in = null;
60 try {
61 Node userHome = JcrUtils.getUserHome(session);
62 if (userHome.hasNode(ARGEO_KEYRING))
63 throw new ArgeoException("Keyring already setup");
64 Node keyring = userHome.addNode(ARGEO_KEYRING);
65 keyring.addMixin(ArgeoTypes.ARGEO_PBE_SPEC);
66
67 // deterministic salt and iteration count based on username
68 String username = session.getUserID();
69 byte[] salt = new byte[8];
70 byte[] usernameBytes = username.getBytes();
71 for (int i = 0; i < salt.length; i++) {
72 if (i < usernameBytes.length)
73 salt[i] = usernameBytes[i];
74 else
75 salt[i] = 0;
76 }
77 in = new ByteArrayInputStream(salt);
78 binary = session.getValueFactory().createBinary(in);
79 keyring.setProperty(ARGEO_SALT, binary);
80
81 Long iterationCount = username.length() * 200l;
82 keyring.setProperty(ARGEO_ITERATION_COUNT, iterationCount);
83
84 // default algo
85 // TODO check if algo and key length are available, use DES if not
86 keyring.setProperty(ARGEO_SECRET_KEY_FACTORY, "PBKDF2WithHmacSHA1");
87 keyring.setProperty(ARGEO_KEY_LENGTH, 256l);
88 keyring.setProperty(ARGEO_SECRET_KEY_ENCRYPTION, "AES");
89 keyring.setProperty(ARGEO_CIPHER, "AES/CBC/PKCS5Padding");
90
91 notYetSavedKeyring.set(keyring);
92 } catch (RepositoryException e) {
93 throw new ArgeoException("Cannot setup keyring", e);
94 } finally {
95 JcrUtils.closeQuietly(binary);
96 IOUtils.closeQuietly(in);
97 // JcrUtils.discardQuietly(session);
98 }
99 }
100
101 @Override
102 protected void handleKeySpecCallback(PBEKeySpecCallback pbeCallback) {
103 try {
104 Node userHome = JcrUtils.getUserHome(session);
105 Node keyring;
106 if (userHome.hasNode(ARGEO_KEYRING))
107 keyring = userHome.getNode(ARGEO_KEYRING);
108 else if (notYetSavedKeyring.get() != null)
109 keyring = notYetSavedKeyring.get();
110 else
111 throw new ArgeoException("Keyring not setup");
112 pbeCallback.set(keyring.getProperty(ARGEO_SECRET_KEY_FACTORY)
113 .getString(), JcrUtils.getBinaryAsBytes(keyring
114 .getProperty(ARGEO_SALT)),
115 (int) keyring.getProperty(ARGEO_ITERATION_COUNT).getLong(),
116 (int) keyring.getProperty(ARGEO_KEY_LENGTH).getLong(),
117 keyring.getProperty(ARGEO_SECRET_KEY_ENCRYPTION)
118 .getString());
119
120 if (notYetSavedKeyring.get() != null)
121 notYetSavedKeyring.remove();
122 } catch (RepositoryException e) {
123 throw new ArgeoException("Cannot handle key spec callback", e);
124 }
125 }
126
127 /** The node must already exist at this path */
128 @Override
129 protected void encrypt(String path, InputStream unencrypted) {
130 // should be called first for lazy initialization
131 SecretKey secretKey = getSecretKey();
132
133 Binary binary = null;
134 InputStream in = null;
135
136 try {
137 Cipher cipher = createCipher();
138 if (!session.nodeExists(path))
139 throw new ArgeoException("No node at " + path);
140 Node node = session.getNode(path);
141 node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED);
142 cipher.init(Cipher.ENCRYPT_MODE, secretKey);
143 byte[] iv = cipher.getIV();
144 if (iv != null) {
145 JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv);
146 }
147 in = new CipherInputStream(unencrypted, cipher);
148 binary = session.getValueFactory().createBinary(in);
149 node.setProperty(Property.JCR_DATA, binary);
150 } catch (Exception e) {
151 throw new ArgeoException("Cannot encrypt", e);
152 } finally {
153 IOUtils.closeQuietly(in);
154 IOUtils.closeQuietly(unencrypted);
155 JcrUtils.closeQuietly(binary);
156 }
157 }
158
159 @Override
160 protected InputStream decrypt(String path) {
161 // should be called first for lazy initialization
162 SecretKey secretKey = getSecretKey();
163
164 Binary binary = null;
165 InputStream encrypted = null;
166
167 try {
168 Cipher cipher = createCipher();
169 if (!session.nodeExists(path))
170 throw new ArgeoException("No node at " + path);
171 Node node = session.getNode(path);
172 if (node.hasProperty(ARGEO_IV)) {
173 byte[] iv = JcrUtils.getBinaryAsBytes(node
174 .getProperty(ARGEO_IV));
175 cipher.init(Cipher.DECRYPT_MODE, secretKey,
176 new IvParameterSpec(iv));
177 } else {
178 cipher.init(Cipher.DECRYPT_MODE, secretKey);
179 }
180 binary = node.getProperty(Property.JCR_DATA).getBinary();
181 encrypted = binary.getStream();
182 return new CipherInputStream(encrypted, cipher);
183 } catch (Exception e) {
184 throw new ArgeoException("Cannot decrypt", e);
185 } finally {
186 // IOUtils.closeQuietly(encrypted);
187 JcrUtils.closeQuietly(binary);
188 }
189 }
190
191 protected Cipher createCipher() {
192 try {
193 Node userHome = JcrUtils.getUserHome(session);
194 if (!userHome.hasNode(ARGEO_KEYRING))
195 throw new ArgeoException("Keyring not setup");
196 Node keyring = userHome.getNode(ARGEO_KEYRING);
197 Cipher cipher = Cipher.getInstance(keyring
198 .getProperty(ARGEO_CIPHER).getString());
199 return cipher;
200 } catch (Exception e) {
201 throw new ArgeoException("Cannot get cipher", e);
202 }
203 }
204
205 public void changePassword(char[] oldPassword, char[] newPassword) {
206 // TODO Auto-generated method stub
207
208 }
209
210 public Session getSession() {
211 return session;
212 }
213
214 public void setSession(Session session) {
215 this.session = session;
216 }
217
218 }