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