1/*
2 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.security.sasl.gsskerb;
27
28import javax.security.sasl.*;
29import java.io.*;
30import java.util.Map;
31import java.util.logging.Level;
32
33// JAAS
34import javax.security.auth.callback.*;
35
36// JGSS
37import org.ietf.jgss.*;
38
39/**
40  * Implements the GSSAPI SASL server mechanism for Kerberos V5.
41  * (<A HREF="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</A>,
42  * <a HREF="http://www.ietf.org/internet-drafts/draft-ietf-cat-sasl-gssapi-00.txt">draft-ietf-cat-sasl-gssapi-00.txt</a>).
43  *
44  * Expects thread's Subject to contain server's Kerberos credentials
45  * - If not, underlying KRB5 mech will attempt to acquire Kerberos creds
46  *   by logging into Kerberos (via default TextCallbackHandler).
47  * - These creds will be used for exchange with client.
48  *
49  * Required callbacks:
50  * - AuthorizeCallback
51  *      handler must verify that authid/authzids are allowed and set
52  *      authorized ID to be the canonicalized authzid (if applicable).
53  *
54  * Environment properties that affect behavior of implementation:
55  *
56  * javax.security.sasl.qop
57  * - quality of protection; list of auth, auth-int, auth-conf; default is "auth"
58  * javax.security.sasl.maxbuf
59  * - max receive buffer size; default is 65536
60  * javax.security.sasl.sendmaxbuffer
61  * - max send buffer size; default is 65536; (min with client max recv size)
62  *
63  * @author Rosanna Lee
64  */
65final class GssKrb5Server extends GssKrb5Base implements SaslServer {
66    private static final String MY_CLASS_NAME = GssKrb5Server.class.getName();
67
68    private int handshakeStage = 0;
69    private String peer;
70    private String me;
71    private String authzid;
72    private CallbackHandler cbh;
73
74    // When serverName is null, the server will be unbound. We need to save and
75    // check the protocol name after the context is established. This value
76    // will be null if serverName is not null.
77    private final String protocolSaved;
78    /**
79     * Creates a SASL mechanism with server credentials that it needs
80     * to participate in GSS-API/Kerberos v5 authentication exchange
81     * with the client.
82     */
83    GssKrb5Server(String protocol, String serverName,
84        Map<String, ?> props, CallbackHandler cbh) throws SaslException {
85
86        super(props, MY_CLASS_NAME);
87
88        this.cbh = cbh;
89
90        String service;
91        if (serverName == null) {
92            protocolSaved = protocol;
93            service = null;
94        } else {
95            protocolSaved = null;
96            service = protocol + "@" + serverName;
97        }
98
99        logger.log(Level.FINE, "KRB5SRV01:Using service name: {0}", service);
100
101        try {
102            GSSManager mgr = GSSManager.getInstance();
103
104            // Create the name for the requested service entity for Krb5 mech
105            GSSName serviceName = service == null ? null:
106                    mgr.createName(service, GSSName.NT_HOSTBASED_SERVICE, KRB5_OID);
107
108            GSSCredential cred = mgr.createCredential(serviceName,
109                GSSCredential.INDEFINITE_LIFETIME,
110                KRB5_OID, GSSCredential.ACCEPT_ONLY);
111
112            // Create a context using the server's credentials
113            secCtx = mgr.createContext(cred);
114
115            if ((allQop&INTEGRITY_ONLY_PROTECTION) != 0) {
116                // Might need integrity
117                secCtx.requestInteg(true);
118            }
119
120            if ((allQop&PRIVACY_PROTECTION) != 0) {
121                // Might need privacy
122                secCtx.requestConf(true);
123            }
124        } catch (GSSException e) {
125            throw new SaslException("Failure to initialize security context", e);
126        }
127        logger.log(Level.FINE, "KRB5SRV02:Initialization complete");
128    }
129
130
131    /**
132     * Processes the response data.
133     *
134     * The client sends response data to which the server must
135     * process using GSS_accept_sec_context.
136     * As per RFC 2222, the GSS authenication completes (GSS_S_COMPLETE)
137     * we do an extra hand shake to determine the negotiated security protection
138     * and buffer sizes.
139     *
140     * @param responseData A non-null but possible empty byte array containing the
141     * response data from the client.
142     * @return A non-null byte array containing the challenge to be
143     * sent to the client, or null when no more data is to be sent.
144     */
145    public byte[] evaluateResponse(byte[] responseData) throws SaslException {
146        if (completed) {
147            throw new SaslException(
148                "SASL authentication already complete");
149        }
150
151        if (logger.isLoggable(Level.FINER)) {
152            traceOutput(MY_CLASS_NAME, "evaluateResponse",
153                "KRB5SRV03:Response [raw]:", responseData);
154        }
155
156        switch (handshakeStage) {
157        case 1:
158            return doHandshake1(responseData);
159
160        case 2:
161            return doHandshake2(responseData);
162
163        default:
164            // Security context not established yet; continue with accept
165
166            try {
167                byte[] gssOutToken = secCtx.acceptSecContext(responseData,
168                    0, responseData.length);
169
170                if (logger.isLoggable(Level.FINER)) {
171                    traceOutput(MY_CLASS_NAME, "evaluateResponse",
172                        "KRB5SRV04:Challenge: [after acceptSecCtx]", gssOutToken);
173                }
174
175                if (secCtx.isEstablished()) {
176                    handshakeStage = 1;
177
178                    peer = secCtx.getSrcName().toString();
179                    me = secCtx.getTargName().toString();
180
181                    logger.log(Level.FINE,
182                            "KRB5SRV05:Peer name is : {0}, my name is : {1}",
183                            new Object[]{peer, me});
184
185                    // me might take the form of proto@host or proto/host
186                    if (protocolSaved != null &&
187                            !protocolSaved.equalsIgnoreCase(me.split("[/@]")[0])) {
188                        throw new SaslException(
189                                "GSS context targ name protocol error: " + me);
190                    }
191
192                    if (gssOutToken == null) {
193                        return doHandshake1(EMPTY);
194                    }
195                }
196
197                return gssOutToken;
198            } catch (GSSException e) {
199                throw new SaslException("GSS initiate failed", e);
200            }
201        }
202    }
203
204    private byte[] doHandshake1(byte[] responseData) throws SaslException {
205        try {
206            // Security context already established. responseData
207            // should contain no data
208            if (responseData != null && responseData.length > 0) {
209                throw new SaslException(
210                    "Handshake expecting no response data from server");
211            }
212
213            // Construct 4 octets of data:
214            // First octet contains bitmask specifying protections supported
215            // 2nd-4th octets contains max receive buffer of server
216
217            byte[] gssInToken = new byte[4];
218            gssInToken[0] = allQop;
219            intToNetworkByteOrder(recvMaxBufSize, gssInToken, 1, 3);
220
221            if (logger.isLoggable(Level.FINE)) {
222                logger.log(Level.FINE,
223                    "KRB5SRV06:Supported protections: {0}; recv max buf size: {1}",
224                    new Object[]{allQop,
225                                 recvMaxBufSize});
226            }
227
228            handshakeStage = 2;  // progress to next stage
229
230            if (logger.isLoggable(Level.FINER)) {
231                traceOutput(MY_CLASS_NAME, "doHandshake1",
232                    "KRB5SRV07:Challenge [raw]", gssInToken);
233            }
234
235            byte[] gssOutToken = secCtx.wrap(gssInToken, 0, gssInToken.length,
236                new MessageProp(0 /* gop */, false /* privacy */));
237
238            if (logger.isLoggable(Level.FINER)) {
239                traceOutput(MY_CLASS_NAME, "doHandshake1",
240                    "KRB5SRV08:Challenge [after wrap]", gssOutToken);
241            }
242            return gssOutToken;
243
244        } catch (GSSException e) {
245            throw new SaslException("Problem wrapping handshake1", e);
246        }
247    }
248
249    private byte[] doHandshake2(byte[] responseData) throws SaslException {
250        try {
251            // Expecting 4 octets from client selected protection
252            // and client's receive buffer size
253            byte[] gssOutToken = secCtx.unwrap(responseData, 0,
254                responseData.length, new MessageProp(0, false));
255
256            if (logger.isLoggable(Level.FINER)) {
257                traceOutput(MY_CLASS_NAME, "doHandshake2",
258                    "KRB5SRV09:Response [after unwrap]", gssOutToken);
259            }
260
261            // First octet is a bit-mask specifying the selected protection
262            byte selectedQop = gssOutToken[0];
263            if ((selectedQop&allQop) == 0) {
264                throw new SaslException("Client selected unsupported protection: "
265                    + selectedQop);
266            }
267            if ((selectedQop&PRIVACY_PROTECTION) != 0) {
268                privacy = true;
269                integrity = true;
270            } else if ((selectedQop&INTEGRITY_ONLY_PROTECTION) != 0) {
271                integrity = true;
272            }
273
274            // 2nd-4th octets specifies maximum buffer size expected by
275            // client (in network byte order). This is the server's send
276            // buffer maximum.
277            int clntMaxBufSize = networkByteOrderToInt(gssOutToken, 1, 3);
278
279            // Determine the max send buffer size based on what the
280            // client is able to receive and our specified max
281            sendMaxBufSize = (sendMaxBufSize == 0) ? clntMaxBufSize :
282                Math.min(sendMaxBufSize, clntMaxBufSize);
283
284            // Update context to limit size of returned buffer
285            rawSendSize = secCtx.getWrapSizeLimit(JGSS_QOP, privacy,
286                sendMaxBufSize);
287
288            if (logger.isLoggable(Level.FINE)) {
289                logger.log(Level.FINE,
290            "KRB5SRV10:Selected protection: {0}; privacy: {1}; integrity: {2}",
291                    new Object[]{selectedQop,
292                                 Boolean.valueOf(privacy),
293                                 Boolean.valueOf(integrity)});
294                logger.log(Level.FINE,
295"KRB5SRV11:Client max recv size: {0}; server max send size: {1}; rawSendSize: {2}",
296                    new Object[] {clntMaxBufSize,
297                                  sendMaxBufSize,
298                                  rawSendSize});
299            }
300
301            // Get authorization identity, if any
302            if (gssOutToken.length > 4) {
303                try {
304                    authzid = new String(gssOutToken, 4,
305                        gssOutToken.length - 4, "UTF-8");
306                } catch (UnsupportedEncodingException uee) {
307                    throw new SaslException ("Cannot decode authzid", uee);
308                }
309            } else {
310                authzid = peer;
311            }
312            logger.log(Level.FINE, "KRB5SRV12:Authzid: {0}", authzid);
313
314            AuthorizeCallback acb = new AuthorizeCallback(peer, authzid);
315
316            // In Kerberos, realm is embedded in peer name
317            cbh.handle(new Callback[] {acb});
318            if (acb.isAuthorized()) {
319                authzid = acb.getAuthorizedID();
320                completed = true;
321            } else {
322                // Authorization failed
323                throw new SaslException(peer +
324                    " is not authorized to connect as " + authzid);
325            }
326
327            return null;
328        } catch (GSSException e) {
329            throw new SaslException("Final handshake step failed", e);
330        } catch (IOException e) {
331            throw new SaslException("Problem with callback handler", e);
332        } catch (UnsupportedCallbackException e) {
333            throw new SaslException("Problem with callback handler", e);
334        }
335    }
336
337    public String getAuthorizationID() {
338        if (completed) {
339            return authzid;
340        } else {
341            throw new IllegalStateException("Authentication incomplete");
342        }
343    }
344
345    public Object getNegotiatedProperty(String propName) {
346        if (!completed) {
347            throw new IllegalStateException("Authentication incomplete");
348        }
349
350        Object result;
351        switch (propName) {
352            case Sasl.BOUND_SERVER_NAME:
353                try {
354                    // me might take the form of proto@host or proto/host
355                    result = me.split("[/@]")[1];
356                } catch (Exception e) {
357                    result = null;
358                }
359                break;
360            default:
361                result = super.getNegotiatedProperty(propName);
362        }
363        return result;
364    }
365}
366