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. Session is saved. */
154 protected synchronized 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 if (session
.hasPendingChanges())
169 Node node
= session
.getNode(path
);
170 node
.addMixin(ArgeoTypes
.ARGEO_ENCRYPTED
);
171 SecureRandom random
= new SecureRandom();
172 byte[] iv
= new byte[16];
173 random
.nextBytes(iv
);
174 cipher
.init(Cipher
.ENCRYPT_MODE
, secretKey
, new IvParameterSpec(iv
));
175 // AlgorithmParameters params = cipher.getParameters();
177 // params.getParameterSpec(IvParameterSpec.class).getIV();
179 JcrUtils
.setBinaryAsBytes(node
, ARGEO_IV
, iv
);
181 // out = new ByteArrayOutputStream();
182 // // encrypted = new CipherOutputStream(out, cipher);
183 // IOUtils.copy(unencrypted, out);
184 // byte[] unenc = out.toByteArray();
185 // byte[] crypted = cipher.doFinal(unenc);
187 // Cipher decipher = createCipher();
188 // decipher.init(Cipher.DECRYPT_MODE, secretKey, new
191 // byte[] decrypted = decipher.doFinal(crypted);
192 // System.out.println("Password :'" + new String(decrypted) + "'");
194 // JcrUtils.setBinaryAsBytes(node, Property.JCR_DATA, crypted);
196 in
= new CipherInputStream(unencrypted
, cipher
);
197 binary
= session
.getValueFactory().createBinary(in
);
198 node
.setProperty(Property
.JCR_DATA
, binary
);
200 } catch (Exception e
) {
201 throw new ArgeoException("Cannot encrypt", e
);
203 // IOUtils.closeQuietly(out);
204 // IOUtils.closeQuietly(encrypted);
205 IOUtils
.closeQuietly(unencrypted
);
206 IOUtils
.closeQuietly(in
);
207 JcrUtils
.closeQuietly(binary
);
212 protected synchronized InputStream
decrypt(String path
) {
213 // should be called first for lazy initialization
214 SecretKey secretKey
= getSecretKey();
216 Binary binary
= null;
217 InputStream encrypted
= null;
220 Cipher cipher
= createCipher();
221 if (!session
.nodeExists(path
))
222 throw new ArgeoException("No node at " + path
);
223 Node node
= session
.getNode(path
);
224 if (node
.hasProperty(ARGEO_IV
)) {
225 byte[] iv
= JcrUtils
.getBinaryAsBytes(node
226 .getProperty(ARGEO_IV
));
227 cipher
.init(Cipher
.DECRYPT_MODE
, secretKey
,
228 new IvParameterSpec(iv
));
230 cipher
.init(Cipher
.DECRYPT_MODE
, secretKey
);
233 // byte[] arr = JcrUtils.getBinaryAsBytes(node
234 // .getProperty(Property.JCR_DATA));
235 // byte[] arr2 = cipher.doFinal(arr);
237 // return new ByteArrayInputStream(arr2);
239 binary
= node
.getProperty(Property
.JCR_DATA
).getBinary();
240 encrypted
= binary
.getStream();
241 return new CipherInputStream(encrypted
, cipher
);
242 } catch (Exception e
) {
243 throw new ArgeoException("Cannot decrypt", e
);
245 IOUtils
.closeQuietly(encrypted
);
246 JcrUtils
.closeQuietly(binary
);
250 protected Cipher
createCipher() {
252 Node userHome
= JcrUtils
.getUserHome(session
);
253 if (!userHome
.hasNode(ARGEO_KEYRING
))
254 throw new ArgeoException("Keyring not setup");
255 Node keyring
= userHome
.getNode(ARGEO_KEYRING
);
256 Cipher cipher
= Cipher
.getInstance(keyring
257 .getProperty(ARGEO_CIPHER
).getString());
259 } catch (Exception e
) {
260 throw new ArgeoException("Cannot get cipher", e
);
264 public synchronized void changePassword(char[] oldPassword
,
265 char[] newPassword
) {
266 // TODO decrypt with old pw / encrypt with new pw all argeo:encrypted
269 public synchronized Session
getSession() {
273 public synchronized void setSession(Session session
) {
274 this.session
= session
;