2 * Copyright (C) 2007-2012 Mathieu Baudier
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org
.argeo
.jcr
.security
;
18 import java
.io
.ByteArrayInputStream
;
19 import java
.io
.InputStream
;
20 import java
.security
.SecureRandom
;
22 import javax
.crypto
.Cipher
;
23 import javax
.crypto
.CipherInputStream
;
24 import javax
.crypto
.SecretKey
;
25 import javax
.crypto
.spec
.IvParameterSpec
;
26 import javax
.jcr
.Binary
;
27 import javax
.jcr
.Node
;
28 import javax
.jcr
.Property
;
29 import javax
.jcr
.RepositoryException
;
30 import javax
.jcr
.Session
;
32 import org
.apache
.commons
.io
.IOUtils
;
33 import org
.argeo
.ArgeoException
;
34 import org
.argeo
.jcr
.ArgeoNames
;
35 import org
.argeo
.jcr
.ArgeoTypes
;
36 import org
.argeo
.jcr
.JcrUtils
;
37 import org
.argeo
.jcr
.UserJcrUtils
;
38 import org
.argeo
.util
.crypto
.AbstractKeyring
;
39 import org
.argeo
.util
.crypto
.PBEKeySpecCallback
;
41 /** JCR based implementation of a keyring */
42 public class JcrKeyring
extends AbstractKeyring
implements ArgeoNames
{
43 private Session session
;
46 * When setup is called the session has not yet been saved and we don't want
47 * to save it since there maybe other data which would be inconsistent. So
48 * we keep a reference to this node which will then be used (an reset to
49 * null) when handling the PBE callback. We keep one per thread in case
50 * multiple users are accessing the same instance of a keyring.
52 private ThreadLocal
<Node
> notYetSavedKeyring
= new ThreadLocal
<Node
>() {
55 protected Node
initialValue() {
61 protected Boolean
isSetup() {
63 if (notYetSavedKeyring
.get() != null)
66 Node userHome
= UserJcrUtils
.getUserHome(session
);
67 return userHome
.hasNode(ARGEO_KEYRING
);
68 } catch (RepositoryException e
) {
69 throw new ArgeoException("Cannot check whether keyring is setup", e
);
74 protected void setup(char[] password
) {
76 InputStream in
= null;
78 Node userHome
= UserJcrUtils
.getUserHome(session
);
79 if (userHome
.hasNode(ARGEO_KEYRING
))
80 throw new ArgeoException("Keyring already setup");
81 Node keyring
= userHome
.addNode(ARGEO_KEYRING
);
82 keyring
.addMixin(ArgeoTypes
.ARGEO_PBE_SPEC
);
84 // deterministic salt and iteration count based on username
85 String username
= session
.getUserID();
86 byte[] salt
= new byte[8];
87 byte[] usernameBytes
= username
.getBytes();
88 for (int i
= 0; i
< salt
.length
; i
++) {
89 if (i
< usernameBytes
.length
)
90 salt
[i
] = usernameBytes
[i
];
94 in
= new ByteArrayInputStream(salt
);
95 binary
= session
.getValueFactory().createBinary(in
);
96 keyring
.setProperty(ARGEO_SALT
, binary
);
98 Integer iterationCount
= username
.length() * 200;
99 keyring
.setProperty(ARGEO_ITERATION_COUNT
, iterationCount
);
102 // TODO check if algo and key length are available, use DES if not
103 keyring
.setProperty(ARGEO_SECRET_KEY_FACTORY
, "PBKDF2WithHmacSHA1");
104 keyring
.setProperty(ARGEO_KEY_LENGTH
, 256l);
105 keyring
.setProperty(ARGEO_SECRET_KEY_ENCRYPTION
, "AES");
106 keyring
.setProperty(ARGEO_CIPHER
, "AES/CBC/PKCS5Padding");
108 // encrypted password hash
109 // IOUtils.closeQuietly(in);
110 // JcrUtils.closeQuietly(binary);
111 // byte[] btPass = hash(password, salt, iterationCount);
112 // in = new ByteArrayInputStream(btPass);
113 // binary = session.getValueFactory().createBinary(in);
114 // keyring.setProperty(ARGEO_PASSWORD, binary);
116 notYetSavedKeyring
.set(keyring
);
117 } catch (Exception e
) {
118 throw new ArgeoException("Cannot setup keyring", e
);
120 JcrUtils
.closeQuietly(binary
);
121 IOUtils
.closeQuietly(in
);
122 // JcrUtils.discardQuietly(session);
127 protected void handleKeySpecCallback(PBEKeySpecCallback pbeCallback
) {
129 Node userHome
= UserJcrUtils
.getUserHome(session
);
131 if (userHome
.hasNode(ARGEO_KEYRING
))
132 keyring
= userHome
.getNode(ARGEO_KEYRING
);
133 else if (notYetSavedKeyring
.get() != null)
134 keyring
= notYetSavedKeyring
.get();
136 throw new ArgeoException("Keyring not setup");
138 pbeCallback
.set(keyring
.getProperty(ARGEO_SECRET_KEY_FACTORY
)
139 .getString(), JcrUtils
.getBinaryAsBytes(keyring
140 .getProperty(ARGEO_SALT
)),
141 (int) keyring
.getProperty(ARGEO_ITERATION_COUNT
).getLong(),
142 (int) keyring
.getProperty(ARGEO_KEY_LENGTH
).getLong(),
143 keyring
.getProperty(ARGEO_SECRET_KEY_ENCRYPTION
)
146 if (notYetSavedKeyring
.get() != null)
147 notYetSavedKeyring
.remove();
148 } catch (RepositoryException e
) {
149 throw new ArgeoException("Cannot handle key spec callback", e
);
153 /** The node must already exist at this path. Session is saved. */
155 protected synchronized void encrypt(String path
, InputStream unencrypted
) {
156 // should be called first for lazy initialization
157 SecretKey secretKey
= getSecretKey();
159 Binary binary
= null;
160 InputStream in
= null;
161 // ByteArrayOutputStream out = null;
162 // OutputStream encrypted = null;
165 Cipher cipher
= createCipher();
166 if (!session
.nodeExists(path
))
167 throw new ArgeoException("No node at " + path
);
168 if (session
.hasPendingChanges())
170 Node node
= session
.getNode(path
);
171 node
.addMixin(ArgeoTypes
.ARGEO_ENCRYPTED
);
172 SecureRandom random
= new SecureRandom();
173 byte[] iv
= new byte[16];
174 random
.nextBytes(iv
);
175 cipher
.init(Cipher
.ENCRYPT_MODE
, secretKey
, new IvParameterSpec(iv
));
176 // AlgorithmParameters params = cipher.getParameters();
178 // params.getParameterSpec(IvParameterSpec.class).getIV();
180 JcrUtils
.setBinaryAsBytes(node
, ARGEO_IV
, iv
);
182 // out = new ByteArrayOutputStream();
183 // // encrypted = new CipherOutputStream(out, cipher);
184 // IOUtils.copy(unencrypted, out);
185 // byte[] unenc = out.toByteArray();
186 // byte[] crypted = cipher.doFinal(unenc);
188 // Cipher decipher = createCipher();
189 // decipher.init(Cipher.DECRYPT_MODE, secretKey, new
192 // byte[] decrypted = decipher.doFinal(crypted);
193 // System.out.println("Password :'" + new String(decrypted) + "'");
195 // JcrUtils.setBinaryAsBytes(node, Property.JCR_DATA, crypted);
197 in
= new CipherInputStream(unencrypted
, cipher
);
198 binary
= session
.getValueFactory().createBinary(in
);
199 node
.setProperty(Property
.JCR_DATA
, binary
);
201 } catch (Exception e
) {
202 throw new ArgeoException("Cannot encrypt", e
);
204 // IOUtils.closeQuietly(out);
205 // IOUtils.closeQuietly(encrypted);
206 IOUtils
.closeQuietly(unencrypted
);
207 IOUtils
.closeQuietly(in
);
208 JcrUtils
.closeQuietly(binary
);
213 protected synchronized InputStream
decrypt(String path
) {
214 // should be called first for lazy initialization
215 SecretKey secretKey
= getSecretKey();
217 Binary binary
= null;
218 InputStream encrypted
= null;
221 Cipher cipher
= createCipher();
222 if (!session
.nodeExists(path
))
223 throw new ArgeoException("No node at " + path
);
224 Node node
= session
.getNode(path
);
225 if (node
.hasProperty(ARGEO_IV
)) {
226 byte[] iv
= JcrUtils
.getBinaryAsBytes(node
227 .getProperty(ARGEO_IV
));
228 cipher
.init(Cipher
.DECRYPT_MODE
, secretKey
,
229 new IvParameterSpec(iv
));
231 cipher
.init(Cipher
.DECRYPT_MODE
, secretKey
);
234 // byte[] arr = JcrUtils.getBinaryAsBytes(node
235 // .getProperty(Property.JCR_DATA));
236 // byte[] arr2 = cipher.doFinal(arr);
238 // return new ByteArrayInputStream(arr2);
240 binary
= node
.getProperty(Property
.JCR_DATA
).getBinary();
241 encrypted
= binary
.getStream();
242 return new CipherInputStream(encrypted
, cipher
);
243 } catch (Exception e
) {
244 throw new ArgeoException("Cannot decrypt", e
);
246 IOUtils
.closeQuietly(encrypted
);
247 JcrUtils
.closeQuietly(binary
);
251 protected Cipher
createCipher() {
253 Node userHome
= UserJcrUtils
.getUserHome(session
);
254 if (!userHome
.hasNode(ARGEO_KEYRING
))
255 throw new ArgeoException("Keyring not setup");
256 Node keyring
= userHome
.getNode(ARGEO_KEYRING
);
257 Cipher cipher
= Cipher
.getInstance(keyring
258 .getProperty(ARGEO_CIPHER
).getString());
260 } catch (Exception e
) {
261 throw new ArgeoException("Cannot get cipher", e
);
265 public synchronized void changePassword(char[] oldPassword
,
266 char[] newPassword
) {
267 // TODO decrypt with old pw / encrypt with new pw all argeo:encrypted
270 public synchronized Session
getSession() {
274 public synchronized void setSession(Session session
) {
275 this.session
= session
;