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 (Object ticket : clientLoginContext.getSubject().getPrivateCredentials(KerberosTicket.class)) {
160 KerberosTicket kerberosTicket = (KerberosTicket) ticket;
161 if (kerberosTicket.getServer().getName().startsWith("krbtgt") && !kerberosTicket.isCurrent()) {
162 LOGGER.debug("KerberosHelper.clientLogin cached TGT expired, try to relogin");
163 clientLoginContext = null;
164 }
165 }
166 }
167
168 if (clientLoginContext == null) {
169 final LoginContext localLoginContext = new LoginContext("spnego-client", KERBEROS_CALLBACK_HANDLER);
170 localLoginContext.login();
171 clientLoginContext = localLoginContext;
172 }
173
174 for (Object ticket : clientLoginContext.getSubject().getPrivateCredentials(KerberosTicket.class)) {
175 KerberosTicket kerberosTicket = (KerberosTicket) ticket;
176 LOGGER.debug("KerberosHelper.clientLogin ticket for " + kerberosTicket.getServer().getName() + " expires at " + kerberosTicket.getEndTime());
177 if (kerberosTicket.getEndTime().getTime() < System.currentTimeMillis() + 10000) {
178 if (kerberosTicket.isRenewable()) {
179 try {
180 kerberosTicket.refresh();
181 } catch (RefreshFailedException e) {
182 LOGGER.debug("KerberosHelper.clientLogin failed to renew ticket " + kerberosTicket);
183 }
184 } else {
185 LOGGER.debug("KerberosHelper.clientLogin ticket is not renewable");
186 }
187 }
188 }
189
190 Object result = internalInitSecContext(protocol, host, delegatedCredentials, token);
191 if (result instanceof GSSException) {
192 LOGGER.info("KerberosHelper.initSecurityContext exception code " + ((GSSException) result).getMajor() + " minor code " + ((GSSException) result).getMinor() + " message " + ((Throwable) result).getMessage());
193 throw (GSSException) result;
194 }
195
196 LOGGER.debug("KerberosHelper.initSecurityContext return " + ((byte[]) result).length + " bytes token");
197 return (byte[]) result;
198 }
199 }
200
201 protected static Object internalInitSecContext(final String protocol, final String host, final GSSCredential delegatedCredentials, final byte[] token) {
202 return Subject.doAs(clientLoginContext.getSubject(), (PrivilegedAction<Object>) () -> {
203 Object result;
204 GSSContext context = null;
205 try {
206 GSSManager manager = GSSManager.getInstance();
207 GSSName serverName = manager.createName(protocol + '@' + host, GSSName.NT_HOSTBASED_SERVICE);
208
209 Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");
210
211 context = manager.createContext(serverName, krb5Oid, delegatedCredentials, GSSContext.DEFAULT_LIFETIME);
212
213
214
215 context.requestCredDeleg(true);
216
217 result = context.initSecContext(token, 0, token.length);
218 } catch (GSSException e) {
219 result = e;
220 } finally {
221 if (context != null) {
222 try {
223 context.dispose();
224 } catch (GSSException e) {
225 LOGGER.debug("KerberosHelper.internalInitSecContext " + e + ' ' + e.getMessage());
226 }
227 }
228 }
229 return result;
230 });
231 }
232
233
234
235
236
237
238
239
240
241 public static LoginContext serverLogin(final String serverPrincipal, final String serverPassword) throws LoginException {
242 LoginContext serverLoginContext = new LoginContext("spnego-server", callbacks -> {
243 for (Callback callback : callbacks) {
244 if (callback instanceof NameCallback) {
245 final NameCallback nameCallback = (NameCallback) callback;
246 nameCallback.setName(serverPrincipal);
247 } else if (callback instanceof PasswordCallback) {
248 final PasswordCallback passCallback = (PasswordCallback) callback;
249 passCallback.setPassword(serverPassword.toCharArray());
250 } else {
251 throw new UnsupportedCallbackException(callback);
252 }
253 }
254
255 });
256 serverLoginContext.login();
257 return serverLoginContext;
258 }
259
260
261
262
263 public static class SecurityContext {
264
265
266
267 public byte[] token;
268
269
270
271 public String principal;
272
273
274
275 public GSSCredential clientCredential;
276 }
277
278
279
280
281
282
283
284
285
286 public static SecurityContext acceptSecurityContext(LoginContext serverLoginContext, final byte[] token) throws GSSException {
287 Object result = Subject.doAs(serverLoginContext.getSubject(), (PrivilegedAction<Object>) () -> {
288 Object innerResult;
289 SecurityContext securityContext = new SecurityContext();
290 GSSContext context = null;
291 try {
292 GSSManager manager = GSSManager.getInstance();
293
294
295 Oid krb5oid = new Oid("1.2.840.113554.1.2.2");
296 GSSCredential serverCreds = manager.createCredential(null,
297 GSSCredential.DEFAULT_LIFETIME,
298 krb5oid,
299 GSSCredential.ACCEPT_ONLY);
300 context = manager.createContext(serverCreds);
301
302 securityContext.token = context.acceptSecContext(token, 0, token.length);
303 if (context.isEstablished()) {
304 securityContext.principal = context.getSrcName().toString();
305 LOGGER.debug("Authenticated user: " + securityContext.principal);
306 if (!context.getCredDelegState()) {
307 LOGGER.debug("Credentials can not be delegated");
308 } else {
309
310 securityContext.clientCredential = context.getDelegCred();
311 }
312 }
313 innerResult = securityContext;
314 } catch (GSSException e) {
315 innerResult = e;
316 } finally {
317 if (context != null) {
318 try {
319 context.dispose();
320 } catch (GSSException e) {
321 LOGGER.debug("KerberosHelper.acceptSecurityContext " + e + ' ' + e.getMessage());
322 }
323 }
324 }
325 return innerResult;
326 });
327 if (result instanceof GSSException) {
328 LOGGER.info("KerberosHelper.acceptSecurityContext exception code " + ((GSSException) result).getMajor() + " minor code " + ((GSSException) result).getMinor() + " message " + ((Throwable) result).getMessage());
329 throw (GSSException) result;
330 }
331 return (SecurityContext) result;
332 }
333 }