1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package davmail.http;
20
21 import davmail.Settings;
22 import davmail.ui.CredentialPromptDialog;
23 import org.apache.log4j.Logger;
24 import org.ietf.jgss.*;
25
26 import javax.security.auth.RefreshFailedException;
27 import javax.security.auth.Subject;
28 import javax.security.auth.callback.*;
29 import javax.security.auth.kerberos.KerberosTicket;
30 import javax.security.auth.login.LoginContext;
31 import javax.security.auth.login.LoginException;
32 import java.awt.*;
33 import java.io.BufferedReader;
34 import java.io.IOException;
35 import java.io.InputStreamReader;
36 import java.security.PrivilegedAction;
37 import java.security.Security;
38
39
40
41
42
43 @SuppressWarnings( {"removal"})
44 public class KerberosHelper {
45 protected static final Logger LOGGER = Logger.getLogger(KerberosHelper.class);
46 protected static final Object LOCK = new Object();
47 protected static final KerberosCallbackHandler KERBEROS_CALLBACK_HANDLER;
48 private static LoginContext clientLoginContext;
49
50 static {
51
52 Security.setProperty("login.configuration.provider", "davmail.http.KerberosLoginConfiguration");
53
54 KERBEROS_CALLBACK_HANDLER = new KerberosCallbackHandler();
55 }
56
57 private KerberosHelper() {
58 }
59
60 @SuppressWarnings("UseOfSystemOutOrSystemErr")
61 protected static class KerberosCallbackHandler implements CallbackHandler {
62 String principal;
63 String password;
64
65 public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
66 for (Callback callback : callbacks) {
67 if (callback instanceof NameCallback) {
68 if (principal == null) {
69
70 if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) {
71
72 System.out.print(((NameCallback) callback).getPrompt());
73 BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
74 principal = inReader.readLine();
75 } else {
76 CredentialPromptDialog credentialPromptDialog = new CredentialPromptDialog(((NameCallback) callback).getPrompt());
77 principal = credentialPromptDialog.getPrincipal();
78 password = String.valueOf(credentialPromptDialog.getPassword());
79 }
80 }
81 if (principal == null) {
82 throw new IOException("KerberosCallbackHandler: failed to retrieve principal");
83 }
84 ((NameCallback) callback).setName(principal);
85
86 } else if (callback instanceof PasswordCallback) {
87 if (password == null) {
88
89 if (Settings.getBooleanProperty("davmail.server") || GraphicsEnvironment.isHeadless()) {
90
91 System.out.print(((PasswordCallback) callback).getPrompt());
92 BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
93 password = inReader.readLine();
94 }
95 }
96 if (password == null) {
97 throw new IOException("KerberosCallbackHandler: failed to retrieve password");
98 }
99 ((PasswordCallback) callback).setPassword(password.toCharArray());
100
101 } else {
102 throw new UnsupportedCallbackException(callback);
103 }
104 }
105 }
106 }
107
108
109
110
111
112
113 public static void setClientPrincipal(String principal) {
114 KERBEROS_CALLBACK_HANDLER.principal = principal;
115 }
116
117
118
119
120
121
122 public static void setClientPassword(String password) {
123 KERBEROS_CALLBACK_HANDLER.password = password;
124 }
125
126
127
128
129
130
131
132
133
134
135
136 public static byte[] initSecurityContext(final String protocol, final String host, final byte[] token) throws GSSException, LoginException {
137 return initSecurityContext(protocol, host, null, token);
138 }
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153 public static byte[] initSecurityContext(final String protocol, final String host, final GSSCredential delegatedCredentials, final byte[] token) throws GSSException, LoginException {
154 LOGGER.debug("KerberosHelper.initSecurityContext " + protocol + '@' + host + ' ' + token.length + " bytes token");
155
156 synchronized (LOCK) {
157
158 if (clientLoginContext != null) {
159 for (KerberosTicket kerberosTicket : clientLoginContext.getSubject().getPrivateCredentials(KerberosTicket.class)) {
160 if (kerberosTicket.getServer().getName().startsWith("krbtgt") && !kerberosTicket.isCurrent()) {
161 LOGGER.debug("KerberosHelper.clientLogin cached TGT expired, try to relogin");
162 clientLoginContext = null;
163 }
164 }
165 }
166
167 if (clientLoginContext == null) {
168 final LoginContext localLoginContext = new LoginContext("spnego-client", KERBEROS_CALLBACK_HANDLER);
169 localLoginContext.login();
170 clientLoginContext = localLoginContext;
171 }
172
173 for (KerberosTicket kerberosTicket : clientLoginContext.getSubject().getPrivateCredentials(KerberosTicket.class)) {
174 LOGGER.debug("KerberosHelper.clientLogin ticket for " + kerberosTicket.getServer().getName() + " expires at " + kerberosTicket.getEndTime());
175 if (kerberosTicket.getEndTime().getTime() < System.currentTimeMillis() + 10000) {
176 if (kerberosTicket.isRenewable()) {
177 try {
178 kerberosTicket.refresh();
179 } catch (RefreshFailedException e) {
180 LOGGER.debug("KerberosHelper.clientLogin failed to renew ticket " + kerberosTicket);
181 }
182 } else {
183 LOGGER.debug("KerberosHelper.clientLogin ticket is not renewable");
184 }
185 }
186 }
187
188 Object result = internalInitSecContext(protocol, host, delegatedCredentials, token);
189 if (result instanceof GSSException) {
190 LOGGER.info("KerberosHelper.initSecurityContext exception code " + ((GSSException) result).getMajor() + " minor code " + ((GSSException) result).getMinor() + " message " + ((Throwable) result).getMessage());
191 throw (GSSException) result;
192 }
193
194 LOGGER.debug("KerberosHelper.initSecurityContext return " + ((byte[]) result).length + " bytes token");
195 return (byte[]) result;
196 }
197 }
198
199 protected static Object internalInitSecContext(final String protocol, final String host, final GSSCredential delegatedCredentials, final byte[] token) {
200 return Subject.doAs(clientLoginContext.getSubject(), (PrivilegedAction<Object>) () -> {
201 Object result;
202 GSSContext context = null;
203 try {
204 GSSManager manager = GSSManager.getInstance();
205 GSSName serverName = manager.createName(protocol + '@' + host, GSSName.NT_HOSTBASED_SERVICE);
206
207 Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");
208
209 context = manager.createContext(serverName, krb5Oid, delegatedCredentials, GSSContext.DEFAULT_LIFETIME);
210
211
212
213 context.requestCredDeleg(true);
214
215 result = context.initSecContext(token, 0, token.length);
216 } catch (GSSException e) {
217 result = e;
218 } finally {
219 if (context != null) {
220 try {
221 context.dispose();
222 } catch (GSSException e) {
223 LOGGER.debug("KerberosHelper.internalInitSecContext " + e + ' ' + e.getMessage());
224 }
225 }
226 }
227 return result;
228 });
229 }
230
231
232
233
234
235
236
237
238
239 public static LoginContext serverLogin(final String serverPrincipal, final String serverPassword) throws LoginException {
240 LoginContext serverLoginContext = new LoginContext("spnego-server", callbacks -> {
241 for (Callback callback : callbacks) {
242 if (callback instanceof NameCallback) {
243 final NameCallback nameCallback = (NameCallback) callback;
244 nameCallback.setName(serverPrincipal);
245 } else if (callback instanceof PasswordCallback) {
246 final PasswordCallback passCallback = (PasswordCallback) callback;
247 passCallback.setPassword(serverPassword.toCharArray());
248 } else {
249 throw new UnsupportedCallbackException(callback);
250 }
251 }
252
253 });
254 serverLoginContext.login();
255 return serverLoginContext;
256 }
257
258
259
260
261 public static class SecurityContext {
262
263
264
265 public byte[] token;
266
267
268
269 public String principal;
270
271
272
273 public GSSCredential clientCredential;
274 }
275
276
277
278
279
280
281
282
283
284 public static SecurityContext acceptSecurityContext(LoginContext serverLoginContext, final byte[] token) throws GSSException {
285 Object result = Subject.doAs(serverLoginContext.getSubject(), (PrivilegedAction<Object>) () -> {
286 Object innerResult;
287 SecurityContext securityContext = new SecurityContext();
288 GSSContext context = null;
289 try {
290 GSSManager manager = GSSManager.getInstance();
291
292
293 Oid krb5oid = new Oid("1.2.840.113554.1.2.2");
294 GSSCredential serverCreds = manager.createCredential(null,
295 GSSCredential.DEFAULT_LIFETIME,
296 krb5oid,
297 GSSCredential.ACCEPT_ONLY);
298 context = manager.createContext(serverCreds);
299
300 securityContext.token = context.acceptSecContext(token, 0, token.length);
301 if (context.isEstablished()) {
302 securityContext.principal = context.getSrcName().toString();
303 LOGGER.debug("Authenticated user: " + securityContext.principal);
304 if (!context.getCredDelegState()) {
305 LOGGER.debug("Credentials can not be delegated");
306 } else {
307
308 securityContext.clientCredential = context.getDelegCred();
309 }
310 }
311 innerResult = securityContext;
312 } catch (GSSException e) {
313 innerResult = e;
314 } finally {
315 if (context != null) {
316 try {
317 context.dispose();
318 } catch (GSSException e) {
319 LOGGER.debug("KerberosHelper.acceptSecurityContext " + e + ' ' + e.getMessage());
320 }
321 }
322 }
323 return innerResult;
324 });
325 if (result instanceof GSSException) {
326 LOGGER.info("KerberosHelper.acceptSecurityContext exception code " + ((GSSException) result).getMajor() + " minor code " + ((GSSException) result).getMinor() + " message " + ((Throwable) result).getMessage());
327 throw (GSSException) result;
328 }
329 return (SecurityContext) result;
330 }
331 }