1 package org
.argeo
.util
.crypto
;
3 import java
.io
.ByteArrayInputStream
;
4 import java
.io
.ByteArrayOutputStream
;
5 import java
.io
.CharArrayWriter
;
6 import java
.io
.IOException
;
7 import java
.io
.InputStream
;
8 import java
.io
.InputStreamReader
;
9 import java
.io
.OutputStreamWriter
;
10 import java
.io
.Reader
;
11 import java
.io
.Writer
;
12 import java
.security
.AccessController
;
13 import java
.util
.Arrays
;
14 import java
.util
.Iterator
;
16 import javax
.crypto
.SecretKey
;
17 import javax
.security
.auth
.Subject
;
18 import javax
.security
.auth
.callback
.Callback
;
19 import javax
.security
.auth
.callback
.CallbackHandler
;
20 import javax
.security
.auth
.callback
.PasswordCallback
;
21 import javax
.security
.auth
.callback
.TextOutputCallback
;
22 import javax
.security
.auth
.callback
.UnsupportedCallbackException
;
23 import javax
.security
.auth
.login
.LoginContext
;
24 import javax
.security
.auth
.login
.LoginException
;
26 import org
.argeo
.ArgeoException
;
27 import org
.argeo
.StreamUtils
;
29 /** username / password based keyring. TODO internationalize */
30 public abstract class AbstractKeyring
implements Keyring
{
31 public final static String DEFAULT_KEYRING_LOGIN_CONTEXT
= "KEYRING";
33 private String loginContextName
= DEFAULT_KEYRING_LOGIN_CONTEXT
;
34 private CallbackHandler defaultCallbackHandler
;
36 private String charset
= "UTF-8";
39 * Whether the keyring has already been created in the past with a master
42 protected abstract Boolean
isSetup();
45 * Setup the keyring persistently, {@link #isSetup()} must return true
48 protected abstract void setup();
50 /** Populates the key spec callback */
51 protected abstract void handleKeySpecCallback(PBEKeySpecCallback pbeCallback
);
53 protected abstract void encrypt(String path
, InputStream unencrypted
);
55 protected abstract InputStream
decrypt(String path
);
57 /** Triggers lazy initialization */
58 protected SecretKey
getSecretKey() {
59 Subject subject
= Subject
.getSubject(AccessController
.getContext());
60 // we assume only one secrete key is available
61 Iterator
<SecretKey
> iterator
= subject
.getPrivateCredentials(
62 SecretKey
.class).iterator();
63 if (!iterator
.hasNext()) {// not initialized
64 CallbackHandler callbackHandler
= new KeyringCallbackHandler();
66 LoginContext loginContext
= new LoginContext(loginContextName
,
67 subject
, callbackHandler
);
69 // FIXME will login even if password is wrong
70 iterator
= subject
.getPrivateCredentials(SecretKey
.class)
72 return iterator
.next();
73 } catch (LoginException e
) {
74 throw new ArgeoException("Keyring login failed", e
);
78 SecretKey secretKey
= iterator
.next();
79 if (iterator
.hasNext())
80 throw new ArgeoException(
81 "More than one secret key in private credentials");
86 public InputStream
getAsStream(String path
) {
90 public void set(String path
, InputStream in
) {
94 public char[] getAsChars(String path
) {
95 InputStream in
= getAsStream(path
);
96 CharArrayWriter writer
= null;
99 writer
= new CharArrayWriter();
100 reader
= new InputStreamReader(in
, charset
);
101 StreamUtils
.copy(reader
, writer
);
102 return writer
.toCharArray();
103 } catch (IOException e
) {
104 throw new ArgeoException("Cannot decrypt to char array", e
);
106 StreamUtils
.closeQuietly(reader
);
107 StreamUtils
.closeQuietly(in
);
108 StreamUtils
.closeQuietly(writer
);
112 public void set(String path
, char[] arr
) {
113 ByteArrayOutputStream out
= new ByteArrayOutputStream();
114 ByteArrayInputStream in
= null;
115 Writer writer
= null;
117 writer
= new OutputStreamWriter(out
, charset
);
119 in
= new ByteArrayInputStream(out
.toByteArray());
121 } catch (IOException e
) {
122 throw new ArgeoException("Cannot encrypt to char array", e
);
124 StreamUtils
.closeQuietly(writer
);
125 StreamUtils
.closeQuietly(out
);
126 StreamUtils
.closeQuietly(in
);
130 public void setLoginContextName(String loginContextName
) {
131 this.loginContextName
= loginContextName
;
134 public void setDefaultCallbackHandler(CallbackHandler defaultCallbackHandler
) {
135 this.defaultCallbackHandler
= defaultCallbackHandler
;
138 public void setCharset(String charset
) {
139 this.charset
= charset
;
142 class KeyringCallbackHandler
implements CallbackHandler
{
143 public void handle(Callback
[] callbacks
) throws IOException
,
144 UnsupportedCallbackException
{
146 if (callbacks
.length
!= 2)
147 throw new IllegalArgumentException(
148 "Keyring required 2 and only 2 callbacks: {PasswordCallback,PBEKeySpecCallback}");
149 if (!(callbacks
[0] instanceof PasswordCallback
))
150 throw new UnsupportedCallbackException(callbacks
[0]);
151 if (!(callbacks
[1] instanceof PBEKeySpecCallback
))
152 throw new UnsupportedCallbackException(callbacks
[0]);
154 PasswordCallback passwordCb
= (PasswordCallback
) callbacks
[0];
155 PBEKeySpecCallback pbeCb
= (PBEKeySpecCallback
) callbacks
[1];
158 Callback
[] dialogCbs
= new Callback
[] { passwordCb
};
159 defaultCallbackHandler
.handle(dialogCbs
);
160 } else {// setup keyring
161 TextOutputCallback textCb1
= new TextOutputCallback(
162 TextOutputCallback
.INFORMATION
,
163 "Enter a master password");
164 TextOutputCallback textCb2
= new TextOutputCallback(
165 TextOutputCallback
.INFORMATION
,
166 "It will encrypt your private data");
167 TextOutputCallback textCb3
= new TextOutputCallback(
168 TextOutputCallback
.INFORMATION
,
169 "Don't forget it or your data is lost");
170 PasswordCallback confirmPasswordCb
= new PasswordCallback(
171 "Confirm password", false);
173 Callback
[] dialogCbs
= new Callback
[] { textCb1
, textCb2
,
174 textCb3
, passwordCb
, confirmPasswordCb
};
175 defaultCallbackHandler
.handle(dialogCbs
);
177 // if passwords different, retry (except if cancelled)
178 while (passwordCb
.getPassword() != null
179 && !Arrays
.equals(passwordCb
.getPassword(),
180 confirmPasswordCb
.getPassword())) {
181 TextOutputCallback textCb
= new TextOutputCallback(
182 TextOutputCallback
.ERROR
,
183 "The passwords do not match");
184 dialogCbs
= new Callback
[] { textCb
, passwordCb
,
186 defaultCallbackHandler
.handle(dialogCbs
);
189 if (passwordCb
.getPassword() != null)// not cancelled
193 if (passwordCb
.getPassword() != null)
194 handleKeySpecCallback(pbeCb
);