1 package org
.argeo
.jcr
.security
;
3 import java
.io
.ByteArrayInputStream
;
4 import java
.io
.InputStream
;
6 import javax
.crypto
.Cipher
;
7 import javax
.crypto
.CipherInputStream
;
8 import javax
.crypto
.SecretKey
;
9 import javax
.crypto
.spec
.IvParameterSpec
;
10 import javax
.jcr
.Binary
;
11 import javax
.jcr
.Node
;
12 import javax
.jcr
.Property
;
13 import javax
.jcr
.RepositoryException
;
14 import javax
.jcr
.Session
;
16 import org
.apache
.commons
.io
.IOUtils
;
17 import org
.argeo
.ArgeoException
;
18 import org
.argeo
.jcr
.ArgeoNames
;
19 import org
.argeo
.jcr
.ArgeoTypes
;
20 import org
.argeo
.jcr
.JcrUtils
;
21 import org
.argeo
.util
.crypto
.AbstractKeyring
;
22 import org
.argeo
.util
.crypto
.PBEKeySpecCallback
;
24 /** JCR based implementation of a keyring */
25 public class JcrKeyring
extends AbstractKeyring
implements ArgeoNames
{
26 private Session session
;
29 * When setup is called the session has not yet been saved and we don't want
30 * to save it since there maybe other data which would be inconsistent. So
31 * we keep a reference to this node which will then be used (an reset to
32 * null) when handling the PBE callback. We keep one per thread in case
33 * multiple users are accessing the same instance of a keyring.
35 private ThreadLocal
<Node
> notYetSavedKeyring
= new ThreadLocal
<Node
>() {
38 protected Node
initialValue() {
44 protected Boolean
isSetup() {
46 if (notYetSavedKeyring
.get() != null)
49 Node userHome
= JcrUtils
.getUserHome(session
);
50 return userHome
.hasNode(ARGEO_KEYRING
);
51 } catch (RepositoryException e
) {
52 throw new ArgeoException("Cannot check whether keyring is setup", e
);
57 protected void setup() {
59 InputStream in
= null;
61 Node userHome
= JcrUtils
.getUserHome(session
);
62 if (userHome
.hasNode(ARGEO_KEYRING
))
63 throw new ArgeoException("Keyring already setup");
64 Node keyring
= userHome
.addNode(ARGEO_KEYRING
);
65 keyring
.addMixin(ArgeoTypes
.ARGEO_PBE_SPEC
);
67 // deterministic salt and iteration count based on username
68 String username
= session
.getUserID();
69 byte[] salt
= new byte[8];
70 byte[] usernameBytes
= username
.getBytes();
71 for (int i
= 0; i
< salt
.length
; i
++) {
72 if (i
< usernameBytes
.length
)
73 salt
[i
] = usernameBytes
[i
];
77 in
= new ByteArrayInputStream(salt
);
78 binary
= session
.getValueFactory().createBinary(in
);
79 keyring
.setProperty(ARGEO_SALT
, binary
);
81 Long iterationCount
= username
.length() * 200l;
82 keyring
.setProperty(ARGEO_ITERATION_COUNT
, iterationCount
);
85 // TODO check if algo and key length are available, use DES if not
86 keyring
.setProperty(ARGEO_SECRET_KEY_FACTORY
, "PBKDF2WithHmacSHA1");
87 keyring
.setProperty(ARGEO_KEY_LENGTH
, 256l);
88 keyring
.setProperty(ARGEO_SECRET_KEY_ENCRYPTION
, "AES");
89 keyring
.setProperty(ARGEO_CIPHER
, "AES/CBC/PKCS5Padding");
91 notYetSavedKeyring
.set(keyring
);
92 } catch (RepositoryException e
) {
93 throw new ArgeoException("Cannot setup keyring", e
);
95 JcrUtils
.closeQuietly(binary
);
96 IOUtils
.closeQuietly(in
);
97 // JcrUtils.discardQuietly(session);
102 protected void handleKeySpecCallback(PBEKeySpecCallback pbeCallback
) {
104 Node userHome
= JcrUtils
.getUserHome(session
);
106 if (userHome
.hasNode(ARGEO_KEYRING
))
107 keyring
= userHome
.getNode(ARGEO_KEYRING
);
108 else if (notYetSavedKeyring
.get() != null)
109 keyring
= notYetSavedKeyring
.get();
111 throw new ArgeoException("Keyring not setup");
112 pbeCallback
.set(keyring
.getProperty(ARGEO_SECRET_KEY_FACTORY
)
113 .getString(), JcrUtils
.getBinaryAsBytes(keyring
114 .getProperty(ARGEO_SALT
)),
115 (int) keyring
.getProperty(ARGEO_ITERATION_COUNT
).getLong(),
116 (int) keyring
.getProperty(ARGEO_KEY_LENGTH
).getLong(),
117 keyring
.getProperty(ARGEO_SECRET_KEY_ENCRYPTION
)
120 if (notYetSavedKeyring
.get() != null)
121 notYetSavedKeyring
.remove();
122 } catch (RepositoryException e
) {
123 throw new ArgeoException("Cannot handle key spec callback", e
);
127 /** The node must already exist at this path */
129 protected void encrypt(String path
, InputStream unencrypted
) {
130 // should be called first for lazy initialization
131 SecretKey secretKey
= getSecretKey();
133 Binary binary
= null;
134 InputStream in
= null;
137 Cipher cipher
= createCipher();
138 if (!session
.nodeExists(path
))
139 throw new ArgeoException("No node at " + path
);
140 Node node
= session
.getNode(path
);
141 node
.addMixin(ArgeoTypes
.ARGEO_ENCRYPTED
);
142 cipher
.init(Cipher
.ENCRYPT_MODE
, secretKey
);
143 byte[] iv
= cipher
.getIV();
145 JcrUtils
.setBinaryAsBytes(node
, ARGEO_IV
, iv
);
147 in
= new CipherInputStream(unencrypted
, cipher
);
148 binary
= session
.getValueFactory().createBinary(in
);
149 node
.setProperty(Property
.JCR_DATA
, binary
);
150 } catch (Exception e
) {
151 throw new ArgeoException("Cannot encrypt", e
);
153 IOUtils
.closeQuietly(in
);
154 IOUtils
.closeQuietly(unencrypted
);
155 JcrUtils
.closeQuietly(binary
);
160 protected InputStream
decrypt(String path
) {
161 // should be called first for lazy initialization
162 SecretKey secretKey
= getSecretKey();
164 Binary binary
= null;
165 InputStream encrypted
= null;
168 Cipher cipher
= createCipher();
169 if (!session
.nodeExists(path
))
170 throw new ArgeoException("No node at " + path
);
171 Node node
= session
.getNode(path
);
172 if (node
.hasProperty(ARGEO_IV
)) {
173 byte[] iv
= JcrUtils
.getBinaryAsBytes(node
174 .getProperty(ARGEO_IV
));
175 cipher
.init(Cipher
.DECRYPT_MODE
, secretKey
,
176 new IvParameterSpec(iv
));
178 cipher
.init(Cipher
.DECRYPT_MODE
, secretKey
);
180 binary
= node
.getProperty(Property
.JCR_DATA
).getBinary();
181 encrypted
= binary
.getStream();
182 return new CipherInputStream(encrypted
, cipher
);
183 } catch (Exception e
) {
184 throw new ArgeoException("Cannot decrypt", e
);
186 // IOUtils.closeQuietly(encrypted);
187 JcrUtils
.closeQuietly(binary
);
191 protected Cipher
createCipher() {
193 Node userHome
= JcrUtils
.getUserHome(session
);
194 if (!userHome
.hasNode(ARGEO_KEYRING
))
195 throw new ArgeoException("Keyring not setup");
196 Node keyring
= userHome
.getNode(ARGEO_KEYRING
);
197 Cipher cipher
= Cipher
.getInstance(keyring
198 .getProperty(ARGEO_CIPHER
).getString());
200 } catch (Exception e
) {
201 throw new ArgeoException("Cannot get cipher", e
);
205 public void changePassword(char[] oldPassword
, char[] newPassword
) {
206 // TODO Auto-generated method stub
210 public Session
getSession() {
214 public void setSession(Session session
) {
215 this.session
= session
;