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