1 package org
.argeo
.jcr
.security
;
3 import java
.io
.ByteArrayInputStream
;
4 import java
.io
.InputStream
;
5 import java
.security
.SecureRandom
;
7 import javax
.crypto
.Cipher
;
8 import javax
.crypto
.CipherInputStream
;
9 import javax
.crypto
.SecretKey
;
10 import javax
.crypto
.spec
.IvParameterSpec
;
11 import javax
.jcr
.Binary
;
12 import javax
.jcr
.Node
;
13 import javax
.jcr
.Property
;
14 import javax
.jcr
.RepositoryException
;
15 import javax
.jcr
.Session
;
17 import org
.apache
.commons
.io
.IOUtils
;
18 import org
.argeo
.ArgeoException
;
19 import org
.argeo
.jcr
.ArgeoNames
;
20 import org
.argeo
.jcr
.ArgeoTypes
;
21 import org
.argeo
.jcr
.JcrUtils
;
22 import org
.argeo
.util
.crypto
.AbstractKeyring
;
23 import org
.argeo
.util
.crypto
.PBEKeySpecCallback
;
25 /** JCR based implementation of a keyring */
26 public class JcrKeyring
extends AbstractKeyring
implements ArgeoNames
{
27 private Session session
;
30 * When setup is called the session has not yet been saved and we don't want
31 * to save it since there maybe other data which would be inconsistent. So
32 * we keep a reference to this node which will then be used (an reset to
33 * null) when handling the PBE callback. We keep one per thread in case
34 * multiple users are accessing the same instance of a keyring.
36 private ThreadLocal
<Node
> notYetSavedKeyring
= new ThreadLocal
<Node
>() {
39 protected Node
initialValue() {
45 protected Boolean
isSetup() {
47 if (notYetSavedKeyring
.get() != null)
50 Node userHome
= JcrUtils
.getUserHome(session
);
51 return userHome
.hasNode(ARGEO_KEYRING
);
52 } catch (RepositoryException e
) {
53 throw new ArgeoException("Cannot check whether keyring is setup", e
);
58 protected void setup(char[] password
) {
60 InputStream in
= null;
62 Node userHome
= JcrUtils
.getUserHome(session
);
63 if (userHome
.hasNode(ARGEO_KEYRING
))
64 throw new ArgeoException("Keyring already setup");
65 Node keyring
= userHome
.addNode(ARGEO_KEYRING
);
66 keyring
.addMixin(ArgeoTypes
.ARGEO_PBE_SPEC
);
68 // deterministic salt and iteration count based on username
69 String username
= session
.getUserID();
70 byte[] salt
= new byte[8];
71 byte[] usernameBytes
= username
.getBytes();
72 for (int i
= 0; i
< salt
.length
; i
++) {
73 if (i
< usernameBytes
.length
)
74 salt
[i
] = usernameBytes
[i
];
78 in
= new ByteArrayInputStream(salt
);
79 binary
= session
.getValueFactory().createBinary(in
);
80 keyring
.setProperty(ARGEO_SALT
, binary
);
82 Integer iterationCount
= username
.length() * 200;
83 keyring
.setProperty(ARGEO_ITERATION_COUNT
, iterationCount
);
86 // TODO check if algo and key length are available, use DES if not
87 keyring
.setProperty(ARGEO_SECRET_KEY_FACTORY
, "PBKDF2WithHmacSHA1");
88 keyring
.setProperty(ARGEO_KEY_LENGTH
, 256l);
89 keyring
.setProperty(ARGEO_SECRET_KEY_ENCRYPTION
, "AES");
90 keyring
.setProperty(ARGEO_CIPHER
, "AES/CBC/PKCS5Padding");
92 // encrypted password hash
93 // IOUtils.closeQuietly(in);
94 // JcrUtils.closeQuietly(binary);
95 // byte[] btPass = hash(password, salt, iterationCount);
96 // in = new ByteArrayInputStream(btPass);
97 // binary = session.getValueFactory().createBinary(in);
98 // keyring.setProperty(ARGEO_PASSWORD, binary);
100 notYetSavedKeyring
.set(keyring
);
101 } catch (Exception e
) {
102 throw new ArgeoException("Cannot setup keyring", e
);
104 JcrUtils
.closeQuietly(binary
);
105 IOUtils
.closeQuietly(in
);
106 // JcrUtils.discardQuietly(session);
111 protected void handleKeySpecCallback(PBEKeySpecCallback pbeCallback
) {
113 Node userHome
= JcrUtils
.getUserHome(session
);
115 if (userHome
.hasNode(ARGEO_KEYRING
))
116 keyring
= userHome
.getNode(ARGEO_KEYRING
);
117 else if (notYetSavedKeyring
.get() != null)
118 keyring
= notYetSavedKeyring
.get();
120 throw new ArgeoException("Keyring not setup");
122 pbeCallback
.set(keyring
.getProperty(ARGEO_SECRET_KEY_FACTORY
)
123 .getString(), JcrUtils
.getBinaryAsBytes(keyring
124 .getProperty(ARGEO_SALT
)),
125 (int) keyring
.getProperty(ARGEO_ITERATION_COUNT
).getLong(),
126 (int) keyring
.getProperty(ARGEO_KEY_LENGTH
).getLong(),
127 keyring
.getProperty(ARGEO_SECRET_KEY_ENCRYPTION
)
130 if (notYetSavedKeyring
.get() != null)
131 notYetSavedKeyring
.remove();
132 } catch (RepositoryException e
) {
133 throw new ArgeoException("Cannot handle key spec callback", e
);
137 /** The node must already exist at this path */
139 protected void encrypt(String path
, InputStream unencrypted
) {
140 // should be called first for lazy initialization
141 SecretKey secretKey
= getSecretKey();
143 Binary binary
= null;
144 InputStream in
= null;
145 // ByteArrayOutputStream out = null;
146 // OutputStream encrypted = null;
149 Cipher cipher
= createCipher();
150 if (!session
.nodeExists(path
))
151 throw new ArgeoException("No node at " + path
);
152 Node node
= session
.getNode(path
);
153 node
.addMixin(ArgeoTypes
.ARGEO_ENCRYPTED
);
154 SecureRandom random
= new SecureRandom();
155 byte[] iv
= new byte[16];
156 random
.nextBytes(iv
);
157 cipher
.init(Cipher
.ENCRYPT_MODE
, secretKey
, new IvParameterSpec(iv
));
158 // AlgorithmParameters params = cipher.getParameters();
160 // params.getParameterSpec(IvParameterSpec.class).getIV();
162 JcrUtils
.setBinaryAsBytes(node
, ARGEO_IV
, iv
);
164 // out = new ByteArrayOutputStream();
165 // // encrypted = new CipherOutputStream(out, cipher);
166 // IOUtils.copy(unencrypted, out);
167 // byte[] unenc = out.toByteArray();
168 // byte[] crypted = cipher.doFinal(unenc);
170 // Cipher decipher = createCipher();
171 // decipher.init(Cipher.DECRYPT_MODE, secretKey, new
174 // byte[] decrypted = decipher.doFinal(crypted);
175 // System.out.println("Password :'" + new String(decrypted) + "'");
177 // JcrUtils.setBinaryAsBytes(node, Property.JCR_DATA, crypted);
179 in
= new CipherInputStream(unencrypted
, cipher
);
180 binary
= session
.getValueFactory().createBinary(in
);
181 node
.setProperty(Property
.JCR_DATA
, binary
);
182 } catch (Exception e
) {
183 throw new ArgeoException("Cannot encrypt", e
);
185 // IOUtils.closeQuietly(out);
186 // IOUtils.closeQuietly(encrypted);
187 IOUtils
.closeQuietly(unencrypted
);
188 IOUtils
.closeQuietly(in
);
189 JcrUtils
.closeQuietly(binary
);
194 protected InputStream
decrypt(String path
) {
195 // should be called first for lazy initialization
196 SecretKey secretKey
= getSecretKey();
198 Binary binary
= null;
199 InputStream encrypted
= null;
202 Cipher cipher
= createCipher();
203 if (!session
.nodeExists(path
))
204 throw new ArgeoException("No node at " + path
);
205 Node node
= session
.getNode(path
);
206 if (node
.hasProperty(ARGEO_IV
)) {
207 byte[] iv
= JcrUtils
.getBinaryAsBytes(node
208 .getProperty(ARGEO_IV
));
209 cipher
.init(Cipher
.DECRYPT_MODE
, secretKey
,
210 new IvParameterSpec(iv
));
212 cipher
.init(Cipher
.DECRYPT_MODE
, secretKey
);
215 // byte[] arr = JcrUtils.getBinaryAsBytes(node
216 // .getProperty(Property.JCR_DATA));
217 // byte[] arr2 = cipher.doFinal(arr);
219 // return new ByteArrayInputStream(arr2);
221 binary
= node
.getProperty(Property
.JCR_DATA
).getBinary();
222 encrypted
= binary
.getStream();
223 return new CipherInputStream(encrypted
, cipher
);
224 } catch (Exception e
) {
225 throw new ArgeoException("Cannot decrypt", e
);
227 IOUtils
.closeQuietly(encrypted
);
228 JcrUtils
.closeQuietly(binary
);
232 protected Cipher
createCipher() {
234 Node userHome
= JcrUtils
.getUserHome(session
);
235 if (!userHome
.hasNode(ARGEO_KEYRING
))
236 throw new ArgeoException("Keyring not setup");
237 Node keyring
= userHome
.getNode(ARGEO_KEYRING
);
238 Cipher cipher
= Cipher
.getInstance(keyring
239 .getProperty(ARGEO_CIPHER
).getString());
241 } catch (Exception e
) {
242 throw new ArgeoException("Cannot get cipher", e
);
246 public void changePassword(char[] oldPassword
, char[] newPassword
) {
247 // TODO Auto-generated method stub
251 public Session
getSession() {
255 public void setSession(Session session
) {
256 this.session
= session
;