001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027package org.apache.http.impl.auth; 028 029import java.net.InetAddress; 030import java.net.UnknownHostException; 031 032import org.apache.commons.codec.binary.Base64; 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.apache.http.Header; 036import org.apache.http.HttpHost; 037import org.apache.http.HttpRequest; 038import org.apache.http.auth.AUTH; 039import org.apache.http.auth.AuthenticationException; 040import org.apache.http.auth.Credentials; 041import org.apache.http.auth.InvalidCredentialsException; 042import org.apache.http.auth.KerberosCredentials; 043import org.apache.http.auth.MalformedChallengeException; 044import org.apache.http.client.protocol.HttpClientContext; 045import org.apache.http.conn.routing.HttpRoute; 046import org.apache.http.message.BufferedHeader; 047import org.apache.http.protocol.HttpContext; 048import org.apache.http.util.Args; 049import org.apache.http.util.CharArrayBuffer; 050import org.ietf.jgss.GSSContext; 051import org.ietf.jgss.GSSCredential; 052import org.ietf.jgss.GSSException; 053import org.ietf.jgss.GSSManager; 054import org.ietf.jgss.GSSName; 055import org.ietf.jgss.Oid; 056 057/** 058 * @since 4.2 059 */ 060public abstract class GGSSchemeBase extends AuthSchemeBase { 061 062 enum State { 063 UNINITIATED, 064 CHALLENGE_RECEIVED, 065 TOKEN_GENERATED, 066 FAILED, 067 } 068 069 private final Log log = LogFactory.getLog(getClass()); 070 071 private final Base64 base64codec; 072 private final boolean stripPort; 073 private final boolean useCanonicalHostname; 074 075 /** Authentication process state */ 076 private State state; 077 078 /** base64 decoded challenge **/ 079 private byte[] token; 080 081 GGSSchemeBase(final boolean stripPort, final boolean useCanonicalHostname) { 082 super(); 083 this.base64codec = new Base64(0); 084 this.stripPort = stripPort; 085 this.useCanonicalHostname = useCanonicalHostname; 086 this.state = State.UNINITIATED; 087 } 088 089 GGSSchemeBase(final boolean stripPort) { 090 this(stripPort, true); 091 } 092 093 GGSSchemeBase() { 094 this(true,true); 095 } 096 097 protected GSSManager getManager() { 098 return GSSManager.getInstance(); 099 } 100 101 protected byte[] generateGSSToken( 102 final byte[] input, final Oid oid, final String authServer) throws GSSException { 103 return generateGSSToken(input, oid, authServer, null); 104 } 105 106 /** 107 * @since 4.4 108 */ 109 protected byte[] generateGSSToken( 110 final byte[] input, final Oid oid, final String authServer, 111 final Credentials credentials) throws GSSException { 112 final GSSManager manager = getManager(); 113 final GSSName serverName = manager.createName("HTTP@" + authServer, GSSName.NT_HOSTBASED_SERVICE); 114 115 final GSSCredential gssCredential; 116 if (credentials instanceof KerberosCredentials) { 117 gssCredential = ((KerberosCredentials) credentials).getGSSCredential(); 118 } else { 119 gssCredential = null; 120 } 121 122 final GSSContext gssContext = createGSSContext(manager, oid, serverName, gssCredential); 123 if (input != null) { 124 return gssContext.initSecContext(input, 0, input.length); 125 } else { 126 return gssContext.initSecContext(new byte[] {}, 0, 0); 127 } 128 } 129 130 GSSContext createGSSContext( 131 final GSSManager manager, 132 final Oid oid, 133 final GSSName serverName, 134 final GSSCredential gssCredential) throws GSSException { 135 final GSSContext gssContext = manager.createContext(serverName.canonicalize(oid), oid, gssCredential, 136 GSSContext.DEFAULT_LIFETIME); 137 gssContext.requestMutualAuth(true); 138 return gssContext; 139 } 140 /** 141 * @deprecated (4.4) Use {@link #generateToken(byte[], String, org.apache.http.auth.Credentials)}. 142 */ 143 @Deprecated 144 protected byte[] generateToken(final byte[] input, final String authServer) throws GSSException { 145 return null; 146 } 147 148 /** 149 * @since 4.4 150 */ 151 //TODO: make this method abstract 152 @SuppressWarnings("deprecation") 153 protected byte[] generateToken( 154 final byte[] input, final String authServer, final Credentials credentials) throws GSSException { 155 return generateToken(input, authServer); 156 } 157 158 @Override 159 public boolean isComplete() { 160 return this.state == State.TOKEN_GENERATED || this.state == State.FAILED; 161 } 162 163 /** 164 * @deprecated (4.2) Use {@link org.apache.http.auth.ContextAwareAuthScheme#authenticate( 165 * Credentials, HttpRequest, org.apache.http.protocol.HttpContext)} 166 */ 167 @Override 168 @Deprecated 169 public Header authenticate( 170 final Credentials credentials, 171 final HttpRequest request) throws AuthenticationException { 172 return authenticate(credentials, request, null); 173 } 174 175 @Override 176 public Header authenticate( 177 final Credentials credentials, 178 final HttpRequest request, 179 final HttpContext context) throws AuthenticationException { 180 Args.notNull(request, "HTTP request"); 181 switch (state) { 182 case UNINITIATED: 183 throw new AuthenticationException(getSchemeName() + " authentication has not been initiated"); 184 case FAILED: 185 throw new AuthenticationException(getSchemeName() + " authentication has failed"); 186 case CHALLENGE_RECEIVED: 187 try { 188 final HttpRoute route = (HttpRoute) context.getAttribute(HttpClientContext.HTTP_ROUTE); 189 if (route == null) { 190 throw new AuthenticationException("Connection route is not available"); 191 } 192 HttpHost host; 193 if (isProxy()) { 194 host = route.getProxyHost(); 195 if (host == null) { 196 host = route.getTargetHost(); 197 } 198 } else { 199 host = route.getTargetHost(); 200 } 201 final String authServer; 202 String hostname = host.getHostName(); 203 204 if (this.useCanonicalHostname){ 205 try { 206 //TODO: uncomment this statement and delete the resolveCanonicalHostname, 207 //TODO: as soon canonical hostname resolving is implemented in the SystemDefaultDnsResolver 208 //final DnsResolver dnsResolver = SystemDefaultDnsResolver.INSTANCE; 209 //hostname = dnsResolver.resolveCanonicalHostname(host.getHostName()); 210 hostname = resolveCanonicalHostname(hostname); 211 } catch (final UnknownHostException ignore){ 212 } 213 } 214 if (this.stripPort) { // || host.getPort()==80 || host.getPort()==443) { 215 authServer = hostname; 216 } else { 217 authServer = hostname + ":" + host.getPort(); 218 } 219 220 if (log.isDebugEnabled()) { 221 log.debug("init " + authServer); 222 } 223 token = generateToken(token, authServer, credentials); 224 state = State.TOKEN_GENERATED; 225 } catch (final GSSException gsse) { 226 state = State.FAILED; 227 if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL 228 || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) { 229 throw new InvalidCredentialsException(gsse.getMessage(), gsse); 230 } 231 if (gsse.getMajor() == GSSException.NO_CRED ) { 232 throw new InvalidCredentialsException(gsse.getMessage(), gsse); 233 } 234 if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN 235 || gsse.getMajor() == GSSException.DUPLICATE_TOKEN 236 || gsse.getMajor() == GSSException.OLD_TOKEN) { 237 throw new AuthenticationException(gsse.getMessage(), gsse); 238 } 239 // other error 240 throw new AuthenticationException(gsse.getMessage()); 241 } 242 case TOKEN_GENERATED: 243 final String tokenstr = new String(base64codec.encode(token)); 244 if (log.isDebugEnabled()) { 245 log.debug("Sending response '" + tokenstr + "' back to the auth server"); 246 } 247 final CharArrayBuffer buffer = new CharArrayBuffer(32); 248 if (isProxy()) { 249 buffer.append(AUTH.PROXY_AUTH_RESP); 250 } else { 251 buffer.append(AUTH.WWW_AUTH_RESP); 252 } 253 buffer.append(": Negotiate "); 254 buffer.append(tokenstr); 255 return new BufferedHeader(buffer); 256 default: 257 throw new IllegalStateException("Illegal state: " + state); 258 } 259 } 260 261 @Override 262 protected void parseChallenge( 263 final CharArrayBuffer buffer, 264 final int beginIndex, final int endIndex) throws MalformedChallengeException { 265 final String challenge = buffer.substringTrimmed(beginIndex, endIndex); 266 if (log.isDebugEnabled()) { 267 log.debug("Received challenge '" + challenge + "' from the auth server"); 268 } 269 if (state == State.UNINITIATED) { 270 token = Base64.decodeBase64(challenge.getBytes()); 271 state = State.CHALLENGE_RECEIVED; 272 } else { 273 log.debug("Authentication already attempted"); 274 state = State.FAILED; 275 } 276 } 277 278 private String resolveCanonicalHostname(final String host) throws UnknownHostException { 279 final InetAddress in = InetAddress.getByName(host); 280 final String canonicalServer = in.getCanonicalHostName(); 281 if (in.getHostAddress().contentEquals(canonicalServer)) { 282 return host; 283 } 284 return canonicalServer; 285 } 286 287}