blob: 98503c2cad323e3558d799445713a66a477f4a73 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2000-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package com.sun.security.sasl.gsskerb;
27
28import java.io.IOException;
29import java.util.Map;
30import java.util.logging.Logger;
31import java.util.logging.Level;
32import javax.security.sasl.*;
33
34// JAAS
35import javax.security.auth.callback.CallbackHandler;
36
37// JGSS
38import org.ietf.jgss.*;
39
40/**
41 * Implements the GSSAPI SASL client mechanism for Kerberos V5.
42 * (<A HREF="ftp://ftp.isi.edu/in-notes/rfc2222.txt">RFC 2222</A>,
43 * <a HREF="http://www.ietf.org/internet-drafts/draft-ietf-cat-sasl-gssapi-04.txt">draft-ietf-cat-sasl-gssapi-04.txt</a>).
44 * It uses the Java Bindings for GSSAPI
45 * (<A HREF="ftp://ftp.isi.edu/in-notes/rfc2853.txt">RFC 2853</A>)
46 * for getting GSSAPI/Kerberos V5 support.
47 *
48 * The client/server interactions are:
49 * C0: bind (GSSAPI, initial response)
50 * S0: sasl-bind-in-progress, challenge 1 (output of accept_sec_context or [])
51 * C1: bind (GSSAPI, response 1 (output of init_sec_context or []))
52 * S1: sasl-bind-in-progress challenge 2 (security layer, server max recv size)
53 * C2: bind (GSSAPI, response 2 (security layer, client max recv size, authzid))
54 * S2: bind success response
55 *
56 * Expects the client's credentials to be supplied from the
57 * javax.security.sasl.credentials property or from the thread's Subject.
58 * Otherwise the underlying KRB5 mech will attempt to acquire Kerberos creds
59 * by logging into Kerberos (via default TextCallbackHandler).
60 * These creds will be used for exchange with server.
61 *
62 * Required callbacks: none.
63 *
64 * Environment properties that affect behavior of implementation:
65 *
66 * javax.security.sasl.qop
67 * - quality of protection; list of auth, auth-int, auth-conf; default is "auth"
68 * javax.security.sasl.maxbuf
69 * - max receive buffer size; default is 65536
70 * javax.security.sasl.sendmaxbuffer
71 * - max send buffer size; default is 65536; (min with server max recv size)
72 *
73 * javax.security.sasl.server.authentication
74 * - "true" means require mutual authentication; default is "false"
75 *
76 * javax.security.sasl.credentials
77 * - an {@link org.ietf.jgss.GSSCredential} used for delegated authentication.
78 *
79 * @author Rosanna Lee
80 */
81
82final class GssKrb5Client extends GssKrb5Base implements SaslClient {
83 // ---------------- Constants -----------------
84 private static final String MY_CLASS_NAME = GssKrb5Client.class.getName();
85
86 private boolean finalHandshake = false;
87 private boolean mutual = false; // default false
88 private byte[] authzID;
89
90 /**
91 * Creates a SASL mechanism with client credentials that it needs
92 * to participate in GSS-API/Kerberos v5 authentication exchange
93 * with the server.
94 */
95 GssKrb5Client(String authzID, String protocol, String serverName,
96 Map props, CallbackHandler cbh) throws SaslException {
97
98 super(props, MY_CLASS_NAME);
99
100 String service = protocol + "@" + serverName;
101 logger.log(Level.FINE, "KRB5CLNT01:Requesting service name: {0}",
102 service);
103
104 try {
105 GSSManager mgr = GSSManager.getInstance();
106
107 // Create the name for the requested service entity for Krb5 mech
108 GSSName acceptorName = mgr.createName(service,
109 GSSName.NT_HOSTBASED_SERVICE, KRB5_OID);
110
111 // Parse properties to check for supplied credentials
112 GSSCredential credentials = null;
113 if (props != null) {
114 Object prop = props.get(Sasl.CREDENTIALS);
115 if (prop != null && prop instanceof GSSCredential) {
116 credentials = (GSSCredential) prop;
117 logger.log(Level.FINE,
118 "KRB5CLNT01:Using the credentials supplied in " +
119 "javax.security.sasl.credentials");
120 }
121 }
122
123 // Create a context using credentials for Krb5 mech
124 secCtx = mgr.createContext(acceptorName,
125 KRB5_OID, /* mechanism */
126 credentials, /* credentials */
127 GSSContext.INDEFINITE_LIFETIME);
128
129 // Request credential delegation when credentials have been supplied
130 if (credentials != null) {
131 secCtx.requestCredDeleg(true);
132 }
133
134 // Parse properties to set desired context options
135 if (props != null) {
136 // Mutual authentication
137 String prop = (String)props.get(Sasl.SERVER_AUTH);
138 if (prop != null) {
139 mutual = "true".equalsIgnoreCase(prop);
140 }
141 }
142 secCtx.requestMutualAuth(mutual);
143
144 // Always specify potential need for integrity and confidentiality
145 // Decision will be made during final handshake
146 secCtx.requestConf(true);
147 secCtx.requestInteg(true);
148
149 } catch (GSSException e) {
150 throw new SaslException("Failure to initialize security context", e);
151 }
152
153 if (authzID != null && authzID.length() > 0) {
154 try {
155 this.authzID = authzID.getBytes("UTF8");
156 } catch (IOException e) {
157 throw new SaslException("Cannot encode authorization ID", e);
158 }
159 }
160 }
161
162 public boolean hasInitialResponse() {
163 return true;
164 }
165
166 /**
167 * Processes the challenge data.
168 *
169 * The server sends a challenge data using which the client must
170 * process using GSS_Init_sec_context.
171 * As per RFC 2222, when GSS_S_COMPLETE is returned, we do
172 * an extra handshake to determine the negotiated security protection
173 * and buffer sizes.
174 *
175 * @param challengeData A non-null byte array containing the
176 * challenge data from the server.
177 * @return A non-null byte array containing the response to be
178 * sent to the server.
179 */
180 public byte[] evaluateChallenge(byte[] challengeData) throws SaslException {
181 if (completed) {
182 throw new IllegalStateException(
183 "GSSAPI authentication already complete");
184 }
185
186 if (finalHandshake) {
187 return doFinalHandshake(challengeData);
188 } else {
189
190 // Security context not established yet; continue with init
191
192 try {
193 byte[] gssOutToken = secCtx.initSecContext(challengeData,
194 0, challengeData.length);
195 if (logger.isLoggable(Level.FINER)) {
196 traceOutput(MY_CLASS_NAME, "evaluteChallenge",
197 "KRB5CLNT02:Challenge: [raw]", challengeData);
198 traceOutput(MY_CLASS_NAME, "evaluateChallenge",
199 "KRB5CLNT03:Response: [after initSecCtx]", gssOutToken);
200 }
201
202 if (secCtx.isEstablished()) {
203 finalHandshake = true;
204 if (gssOutToken == null) {
205 // RFC 2222 7.2.1: Client responds with no data
206 return EMPTY;
207 }
208 }
209
210 return gssOutToken;
211 } catch (GSSException e) {
212 throw new SaslException("GSS initiate failed", e);
213 }
214 }
215 }
216
217 private byte[] doFinalHandshake(byte[] challengeData) throws SaslException {
218 try {
219 // Security context already established. challengeData
220 // should contain security layers and server's maximum buffer size
221
222 if (logger.isLoggable(Level.FINER)) {
223 traceOutput(MY_CLASS_NAME, "doFinalHandshake",
224 "KRB5CLNT04:Challenge [raw]:", challengeData);
225 }
226
227 if (challengeData.length == 0) {
228 // Received S0, should return []
229 return EMPTY;
230 }
231
232 // Received S1 (security layer, server max recv size)
233
234 byte[] gssOutToken = secCtx.unwrap(challengeData, 0,
235 challengeData.length, new MessageProp(0, false));
236
237 // First octet is a bit-mask specifying the protections
238 // supported by the server
239 if (logger.isLoggable(Level.FINE)) {
240 if (logger.isLoggable(Level.FINER)) {
241 traceOutput(MY_CLASS_NAME, "doFinalHandshake",
242 "KRB5CLNT05:Challenge [unwrapped]:", gssOutToken);
243 }
244 logger.log(Level.FINE, "KRB5CLNT06:Server protections: {0}",
245 new Byte(gssOutToken[0]));
246 }
247
248 // Client selects preferred protection
249 // qop is ordered list of qop values
250 byte selectedQop = findPreferredMask(gssOutToken[0], qop);
251 if (selectedQop == 0) {
252 throw new SaslException(
253 "No common protection layer between client and server");
254 }
255
256 if ((selectedQop&PRIVACY_PROTECTION) != 0) {
257 privacy = true;
258 integrity = true;
259 } else if ((selectedQop&INTEGRITY_ONLY_PROTECTION) != 0) {
260 integrity = true;
261 }
262
263 // 2nd-4th octets specifies maximum buffer size expected by
264 // server (in network byte order)
265 int srvMaxBufSize = networkByteOrderToInt(gssOutToken, 1, 3);
266
267 // Determine the max send buffer size based on what the
268 // server is able to receive and our specified max
269 sendMaxBufSize = (sendMaxBufSize == 0) ? srvMaxBufSize :
270 Math.min(sendMaxBufSize, srvMaxBufSize);
271
272 // Update context to limit size of returned buffer
273 rawSendSize = secCtx.getWrapSizeLimit(JGSS_QOP, privacy,
274 sendMaxBufSize);
275
276 if (logger.isLoggable(Level.FINE)) {
277 logger.log(Level.FINE,
278"KRB5CLNT07:Client max recv size: {0}; server max recv size: {1}; rawSendSize: {2}",
279 new Object[] {new Integer(recvMaxBufSize),
280 new Integer(srvMaxBufSize),
281 new Integer(rawSendSize)});
282 }
283
284 // Construct negotiated security layers and client's max
285 // receive buffer size and authzID
286 int len = 4;
287 if (authzID != null) {
288 len += authzID.length;
289 }
290
291 byte[] gssInToken = new byte[len];
292 gssInToken[0] = selectedQop;
293
294 if (logger.isLoggable(Level.FINE)) {
295 logger.log(Level.FINE,
296 "KRB5CLNT08:Selected protection: {0}; privacy: {1}; integrity: {2}",
297 new Object[]{new Byte(selectedQop),
298 Boolean.valueOf(privacy),
299 Boolean.valueOf(integrity)});
300 }
301
302 intToNetworkByteOrder(recvMaxBufSize, gssInToken, 1, 3);
303 if (authzID != null) {
304 // copy authorization id
305 System.arraycopy(authzID, 0, gssInToken, 4, authzID.length);
306 logger.log(Level.FINE, "KRB5CLNT09:Authzid: {0}", authzID);
307 }
308
309 if (logger.isLoggable(Level.FINER)) {
310 traceOutput(MY_CLASS_NAME, "doFinalHandshake",
311 "KRB5CLNT10:Response [raw]", gssInToken);
312 }
313
314 gssOutToken = secCtx.wrap(gssInToken,
315 0, gssInToken.length,
316 new MessageProp(0 /* qop */, false /* privacy */));
317
318 if (logger.isLoggable(Level.FINER)) {
319 traceOutput(MY_CLASS_NAME, "doFinalHandshake",
320 "KRB5CLNT11:Response [after wrap]", gssOutToken);
321 }
322
323 completed = true; // server authenticated
324 msgProp = new MessageProp(JGSS_QOP, privacy);
325
326 return gssOutToken;
327 } catch (GSSException e) {
328 throw new SaslException("Final handshake failed", e);
329 }
330 }
331}