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
.util
.crypto
.AbstractKeyring
;
38 import org
.argeo
.util
.crypto
.PBEKeySpecCallback
;
40 /** JCR based implementation of a keyring */
41 public class JcrKeyring
extends AbstractKeyring
implements ArgeoNames
{
42 private Session session
;
45 * When setup is called the session has not yet been saved and we don't want
46 * to save it since there maybe other data which would be inconsistent. So
47 * we keep a reference to this node which will then be used (an reset to
48 * null) when handling the PBE callback. We keep one per thread in case
49 * multiple users are accessing the same instance of a keyring.
51 private ThreadLocal
<Node
> notYetSavedKeyring
= new ThreadLocal
<Node
>() {
54 protected Node
initialValue() {
60 protected Boolean
isSetup() {
62 if (notYetSavedKeyring
.get() != null)
65 Node userHome
= JcrUtils
.getUserHome(session
);
66 return userHome
.hasNode(ARGEO_KEYRING
);
67 } catch (RepositoryException e
) {
68 throw new ArgeoException("Cannot check whether keyring is setup", e
);
73 protected void setup(char[] password
) {
75 InputStream in
= null;
77 Node userHome
= JcrUtils
.getUserHome(session
);
78 if (userHome
.hasNode(ARGEO_KEYRING
))
79 throw new ArgeoException("Keyring already setup");
80 Node keyring
= userHome
.addNode(ARGEO_KEYRING
);
81 keyring
.addMixin(ArgeoTypes
.ARGEO_PBE_SPEC
);
83 // deterministic salt and iteration count based on username
84 String username
= session
.getUserID();
85 byte[] salt
= new byte[8];
86 byte[] usernameBytes
= username
.getBytes();
87 for (int i
= 0; i
< salt
.length
; i
++) {
88 if (i
< usernameBytes
.length
)
89 salt
[i
] = usernameBytes
[i
];
93 in
= new ByteArrayInputStream(salt
);
94 binary
= session
.getValueFactory().createBinary(in
);
95 keyring
.setProperty(ARGEO_SALT
, binary
);
97 Integer iterationCount
= username
.length() * 200;
98 keyring
.setProperty(ARGEO_ITERATION_COUNT
, iterationCount
);
101 // TODO check if algo and key length are available, use DES if not
102 keyring
.setProperty(ARGEO_SECRET_KEY_FACTORY
, "PBKDF2WithHmacSHA1");
103 keyring
.setProperty(ARGEO_KEY_LENGTH
, 256l);
104 keyring
.setProperty(ARGEO_SECRET_KEY_ENCRYPTION
, "AES");
105 keyring
.setProperty(ARGEO_CIPHER
, "AES/CBC/PKCS5Padding");
107 // encrypted password hash
108 // IOUtils.closeQuietly(in);
109 // JcrUtils.closeQuietly(binary);
110 // byte[] btPass = hash(password, salt, iterationCount);
111 // in = new ByteArrayInputStream(btPass);
112 // binary = session.getValueFactory().createBinary(in);
113 // keyring.setProperty(ARGEO_PASSWORD, binary);
115 notYetSavedKeyring
.set(keyring
);
116 } catch (Exception e
) {
117 throw new ArgeoException("Cannot setup keyring", e
);
119 JcrUtils
.closeQuietly(binary
);
120 IOUtils
.closeQuietly(in
);
121 // JcrUtils.discardQuietly(session);
126 protected void handleKeySpecCallback(PBEKeySpecCallback pbeCallback
) {
128 Node userHome
= JcrUtils
.getUserHome(session
);
130 if (userHome
.hasNode(ARGEO_KEYRING
))
131 keyring
= userHome
.getNode(ARGEO_KEYRING
);
132 else if (notYetSavedKeyring
.get() != null)
133 keyring
= notYetSavedKeyring
.get();
135 throw new ArgeoException("Keyring not setup");
137 pbeCallback
.set(keyring
.getProperty(ARGEO_SECRET_KEY_FACTORY
)
138 .getString(), JcrUtils
.getBinaryAsBytes(keyring
139 .getProperty(ARGEO_SALT
)),
140 (int) keyring
.getProperty(ARGEO_ITERATION_COUNT
).getLong(),
141 (int) keyring
.getProperty(ARGEO_KEY_LENGTH
).getLong(),
142 keyring
.getProperty(ARGEO_SECRET_KEY_ENCRYPTION
)
145 if (notYetSavedKeyring
.get() != null)
146 notYetSavedKeyring
.remove();
147 } catch (RepositoryException e
) {
148 throw new ArgeoException("Cannot handle key spec callback", e
);
152 /** The node must already exist at this path */
154 protected void encrypt(String path
, InputStream unencrypted
) {
155 // should be called first for lazy initialization
156 SecretKey secretKey
= getSecretKey();
158 Binary binary
= null;
159 InputStream in
= null;
160 // ByteArrayOutputStream out = null;
161 // OutputStream encrypted = null;
164 Cipher cipher
= createCipher();
165 if (!session
.nodeExists(path
))
166 throw new ArgeoException("No node at " + path
);
167 Node node
= session
.getNode(path
);
168 node
.addMixin(ArgeoTypes
.ARGEO_ENCRYPTED
);
169 SecureRandom random
= new SecureRandom();
170 byte[] iv
= new byte[16];
171 random
.nextBytes(iv
);
172 cipher
.init(Cipher
.ENCRYPT_MODE
, secretKey
, new IvParameterSpec(iv
));
173 // AlgorithmParameters params = cipher.getParameters();
175 // params.getParameterSpec(IvParameterSpec.class).getIV();
177 JcrUtils
.setBinaryAsBytes(node
, ARGEO_IV
, iv
);
179 // out = new ByteArrayOutputStream();
180 // // encrypted = new CipherOutputStream(out, cipher);
181 // IOUtils.copy(unencrypted, out);
182 // byte[] unenc = out.toByteArray();
183 // byte[] crypted = cipher.doFinal(unenc);
185 // Cipher decipher = createCipher();
186 // decipher.init(Cipher.DECRYPT_MODE, secretKey, new
189 // byte[] decrypted = decipher.doFinal(crypted);
190 // System.out.println("Password :'" + new String(decrypted) + "'");
192 // JcrUtils.setBinaryAsBytes(node, Property.JCR_DATA, crypted);
194 in
= new CipherInputStream(unencrypted
, cipher
);
195 binary
= session
.getValueFactory().createBinary(in
);
196 node
.setProperty(Property
.JCR_DATA
, binary
);
197 } catch (Exception e
) {
198 throw new ArgeoException("Cannot encrypt", e
);
200 // IOUtils.closeQuietly(out);
201 // IOUtils.closeQuietly(encrypted);
202 IOUtils
.closeQuietly(unencrypted
);
203 IOUtils
.closeQuietly(in
);
204 JcrUtils
.closeQuietly(binary
);
209 protected InputStream
decrypt(String path
) {
210 // should be called first for lazy initialization
211 SecretKey secretKey
= getSecretKey();
213 Binary binary
= null;
214 InputStream encrypted
= null;
217 Cipher cipher
= createCipher();
218 if (!session
.nodeExists(path
))
219 throw new ArgeoException("No node at " + path
);
220 Node node
= session
.getNode(path
);
221 if (node
.hasProperty(ARGEO_IV
)) {
222 byte[] iv
= JcrUtils
.getBinaryAsBytes(node
223 .getProperty(ARGEO_IV
));
224 cipher
.init(Cipher
.DECRYPT_MODE
, secretKey
,
225 new IvParameterSpec(iv
));
227 cipher
.init(Cipher
.DECRYPT_MODE
, secretKey
);
230 // byte[] arr = JcrUtils.getBinaryAsBytes(node
231 // .getProperty(Property.JCR_DATA));
232 // byte[] arr2 = cipher.doFinal(arr);
234 // return new ByteArrayInputStream(arr2);
236 binary
= node
.getProperty(Property
.JCR_DATA
).getBinary();
237 encrypted
= binary
.getStream();
238 return new CipherInputStream(encrypted
, cipher
);
239 } catch (Exception e
) {
240 throw new ArgeoException("Cannot decrypt", e
);
242 IOUtils
.closeQuietly(encrypted
);
243 JcrUtils
.closeQuietly(binary
);
247 protected Cipher
createCipher() {
249 Node userHome
= JcrUtils
.getUserHome(session
);
250 if (!userHome
.hasNode(ARGEO_KEYRING
))
251 throw new ArgeoException("Keyring not setup");
252 Node keyring
= userHome
.getNode(ARGEO_KEYRING
);
253 Cipher cipher
= Cipher
.getInstance(keyring
254 .getProperty(ARGEO_CIPHER
).getString());
256 } catch (Exception e
) {
257 throw new ArgeoException("Cannot get cipher", e
);
261 public void changePassword(char[] oldPassword
, char[] newPassword
) {
262 // TODO Auto-generated method stub
266 public Session
getSession() {
270 public void setSession(Session session
) {
271 this.session
= session
;