1 package org
.argeo
.jcr
.security
;
3 import java
.io
.ByteArrayInputStream
;
4 import java
.io
.ByteArrayOutputStream
;
5 import java
.io
.InputStream
;
6 import java
.io
.OutputStream
;
7 import java
.security
.AlgorithmParameters
;
8 import java
.security
.SecureRandom
;
10 import javax
.crypto
.Cipher
;
11 import javax
.crypto
.CipherInputStream
;
12 import javax
.crypto
.CipherOutputStream
;
13 import javax
.crypto
.SecretKey
;
14 import javax
.crypto
.spec
.IvParameterSpec
;
15 import javax
.jcr
.Binary
;
16 import javax
.jcr
.Node
;
17 import javax
.jcr
.Property
;
18 import javax
.jcr
.RepositoryException
;
19 import javax
.jcr
.Session
;
21 import org
.apache
.commons
.io
.IOUtils
;
22 import org
.argeo
.ArgeoException
;
23 import org
.argeo
.jcr
.ArgeoNames
;
24 import org
.argeo
.jcr
.ArgeoTypes
;
25 import org
.argeo
.jcr
.JcrUtils
;
26 import org
.argeo
.util
.crypto
.AbstractKeyring
;
27 import org
.argeo
.util
.crypto
.PBEKeySpecCallback
;
29 /** JCR based implementation of a keyring */
30 public class JcrKeyring
extends AbstractKeyring
implements ArgeoNames
{
31 private Session session
;
34 * When setup is called the session has not yet been saved and we don't want
35 * to save it since there maybe other data which would be inconsistent. So
36 * we keep a reference to this node which will then be used (an reset to
37 * null) when handling the PBE callback. We keep one per thread in case
38 * multiple users are accessing the same instance of a keyring.
40 private ThreadLocal
<Node
> notYetSavedKeyring
= new ThreadLocal
<Node
>() {
43 protected Node
initialValue() {
49 protected Boolean
isSetup() {
51 if (notYetSavedKeyring
.get() != null)
54 Node userHome
= JcrUtils
.getUserHome(session
);
55 return userHome
.hasNode(ARGEO_KEYRING
);
56 } catch (RepositoryException e
) {
57 throw new ArgeoException("Cannot check whether keyring is setup", e
);
62 protected void setup(char[] password
) {
64 InputStream in
= null;
66 Node userHome
= JcrUtils
.getUserHome(session
);
67 if (userHome
.hasNode(ARGEO_KEYRING
))
68 throw new ArgeoException("Keyring already setup");
69 Node keyring
= userHome
.addNode(ARGEO_KEYRING
);
70 keyring
.addMixin(ArgeoTypes
.ARGEO_PBE_SPEC
);
72 // deterministic salt and iteration count based on username
73 String username
= session
.getUserID();
74 byte[] salt
= new byte[8];
75 byte[] usernameBytes
= username
.getBytes();
76 for (int i
= 0; i
< salt
.length
; i
++) {
77 if (i
< usernameBytes
.length
)
78 salt
[i
] = usernameBytes
[i
];
82 in
= new ByteArrayInputStream(salt
);
83 binary
= session
.getValueFactory().createBinary(in
);
84 keyring
.setProperty(ARGEO_SALT
, binary
);
86 Integer iterationCount
= username
.length() * 200;
87 keyring
.setProperty(ARGEO_ITERATION_COUNT
, iterationCount
);
90 // TODO check if algo and key length are available, use DES if not
91 keyring
.setProperty(ARGEO_SECRET_KEY_FACTORY
, "PBKDF2WithHmacSHA1");
92 keyring
.setProperty(ARGEO_KEY_LENGTH
, 256l);
93 keyring
.setProperty(ARGEO_SECRET_KEY_ENCRYPTION
, "AES");
94 keyring
.setProperty(ARGEO_CIPHER
, "AES/CBC/PKCS5Padding");
96 // encrypted password hash
97 // IOUtils.closeQuietly(in);
98 // JcrUtils.closeQuietly(binary);
99 // byte[] btPass = hash(password, salt, iterationCount);
100 // in = new ByteArrayInputStream(btPass);
101 // binary = session.getValueFactory().createBinary(in);
102 // keyring.setProperty(ARGEO_PASSWORD, binary);
104 notYetSavedKeyring
.set(keyring
);
105 } catch (Exception e
) {
106 throw new ArgeoException("Cannot setup keyring", e
);
108 JcrUtils
.closeQuietly(binary
);
109 IOUtils
.closeQuietly(in
);
110 // JcrUtils.discardQuietly(session);
115 protected void handleKeySpecCallback(PBEKeySpecCallback pbeCallback
) {
117 Node userHome
= JcrUtils
.getUserHome(session
);
119 if (userHome
.hasNode(ARGEO_KEYRING
))
120 keyring
= userHome
.getNode(ARGEO_KEYRING
);
121 else if (notYetSavedKeyring
.get() != null)
122 keyring
= notYetSavedKeyring
.get();
124 throw new ArgeoException("Keyring not setup");
126 pbeCallback
.set(keyring
.getProperty(ARGEO_SECRET_KEY_FACTORY
)
127 .getString(), JcrUtils
.getBinaryAsBytes(keyring
128 .getProperty(ARGEO_SALT
)),
129 (int) keyring
.getProperty(ARGEO_ITERATION_COUNT
).getLong(),
130 (int) keyring
.getProperty(ARGEO_KEY_LENGTH
).getLong(),
131 keyring
.getProperty(ARGEO_SECRET_KEY_ENCRYPTION
)
134 if (notYetSavedKeyring
.get() != null)
135 notYetSavedKeyring
.remove();
136 } catch (RepositoryException e
) {
137 throw new ArgeoException("Cannot handle key spec callback", e
);
141 /** The node must already exist at this path */
143 protected void encrypt(String path
, InputStream unencrypted
) {
144 // should be called first for lazy initialization
145 SecretKey secretKey
= getSecretKey();
147 Binary binary
= null;
148 InputStream in
= null;
149 // ByteArrayOutputStream out = null;
150 // OutputStream encrypted = null;
153 Cipher cipher
= createCipher();
154 if (!session
.nodeExists(path
))
155 throw new ArgeoException("No node at " + path
);
156 Node node
= session
.getNode(path
);
157 node
.addMixin(ArgeoTypes
.ARGEO_ENCRYPTED
);
158 SecureRandom random
= new SecureRandom();
159 byte[] iv
= new byte[16];
160 random
.nextBytes(iv
);
161 cipher
.init(Cipher
.ENCRYPT_MODE
, secretKey
, new IvParameterSpec(iv
));
162 // AlgorithmParameters params = cipher.getParameters();
164 // params.getParameterSpec(IvParameterSpec.class).getIV();
166 JcrUtils
.setBinaryAsBytes(node
, ARGEO_IV
, iv
);
168 // out = new ByteArrayOutputStream();
169 // // encrypted = new CipherOutputStream(out, cipher);
170 // IOUtils.copy(unencrypted, out);
171 // byte[] unenc = out.toByteArray();
172 // byte[] crypted = cipher.doFinal(unenc);
174 // Cipher decipher = createCipher();
175 // decipher.init(Cipher.DECRYPT_MODE, secretKey, new
178 // byte[] decrypted = decipher.doFinal(crypted);
179 // System.out.println("Password :'" + new String(decrypted) + "'");
181 // JcrUtils.setBinaryAsBytes(node, Property.JCR_DATA, crypted);
183 in
= new CipherInputStream(unencrypted
, cipher
);
184 binary
= session
.getValueFactory().createBinary(in
);
185 node
.setProperty(Property
.JCR_DATA
, binary
);
186 } catch (Exception e
) {
187 throw new ArgeoException("Cannot encrypt", e
);
189 // IOUtils.closeQuietly(out);
190 // IOUtils.closeQuietly(encrypted);
191 IOUtils
.closeQuietly(unencrypted
);
192 IOUtils
.closeQuietly(in
);
193 JcrUtils
.closeQuietly(binary
);
198 protected InputStream
decrypt(String path
) {
199 // should be called first for lazy initialization
200 SecretKey secretKey
= getSecretKey();
202 Binary binary
= null;
203 InputStream encrypted
= null;
206 Cipher cipher
= createCipher();
207 if (!session
.nodeExists(path
))
208 throw new ArgeoException("No node at " + path
);
209 Node node
= session
.getNode(path
);
210 if (node
.hasProperty(ARGEO_IV
)) {
211 byte[] iv
= JcrUtils
.getBinaryAsBytes(node
212 .getProperty(ARGEO_IV
));
213 cipher
.init(Cipher
.DECRYPT_MODE
, secretKey
,
214 new IvParameterSpec(iv
));
216 cipher
.init(Cipher
.DECRYPT_MODE
, secretKey
);
219 // byte[] arr = JcrUtils.getBinaryAsBytes(node
220 // .getProperty(Property.JCR_DATA));
221 // byte[] arr2 = cipher.doFinal(arr);
223 // return new ByteArrayInputStream(arr2);
225 binary
= node
.getProperty(Property
.JCR_DATA
).getBinary();
226 encrypted
= binary
.getStream();
227 return new CipherInputStream(encrypted
, cipher
);
228 } catch (Exception e
) {
229 throw new ArgeoException("Cannot decrypt", e
);
231 IOUtils
.closeQuietly(encrypted
);
232 JcrUtils
.closeQuietly(binary
);
236 protected Cipher
createCipher() {
238 Node userHome
= JcrUtils
.getUserHome(session
);
239 if (!userHome
.hasNode(ARGEO_KEYRING
))
240 throw new ArgeoException("Keyring not setup");
241 Node keyring
= userHome
.getNode(ARGEO_KEYRING
);
242 Cipher cipher
= Cipher
.getInstance(keyring
243 .getProperty(ARGEO_CIPHER
).getString());
245 } catch (Exception e
) {
246 throw new ArgeoException("Cannot get cipher", e
);
250 public void changePassword(char[] oldPassword
, char[] newPassword
) {
251 // TODO Auto-generated method stub
255 public Session
getSession() {
259 public void setSession(Session session
) {
260 this.session
= session
;