View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.http.impl.auth;
28  
29  import org.apache.commons.codec.binary.Base64;
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.http.Header;
33  import org.apache.http.HttpHost;
34  import org.apache.http.HttpRequest;
35  import org.apache.http.annotation.NotThreadSafe;
36  import org.apache.http.auth.AUTH;
37  import org.apache.http.auth.AuthenticationException;
38  import org.apache.http.auth.Credentials;
39  import org.apache.http.auth.InvalidCredentialsException;
40  import org.apache.http.auth.MalformedChallengeException;
41  import org.apache.http.client.protocol.HttpClientContext;
42  import org.apache.http.conn.routing.HttpRoute;
43  import org.apache.http.message.BufferedHeader;
44  import org.apache.http.protocol.HttpContext;
45  import org.apache.http.util.Args;
46  import org.apache.http.util.CharArrayBuffer;
47  import org.ietf.jgss.GSSContext;
48  import org.ietf.jgss.GSSException;
49  import org.ietf.jgss.GSSManager;
50  import org.ietf.jgss.GSSName;
51  import org.ietf.jgss.Oid;
52  
53  /**
54   * @since 4.2
55   */
56  @NotThreadSafe
57  public abstract class GGSSchemeBase extends AuthSchemeBase {
58  
59      enum State {
60          UNINITIATED,
61          CHALLENGE_RECEIVED,
62          TOKEN_GENERATED,
63          FAILED,
64      }
65  
66      private final Log log = LogFactory.getLog(getClass());
67  
68      private final Base64 base64codec;
69      private final boolean stripPort;
70  
71      /** Authentication process state */
72      private State state;
73  
74      /** base64 decoded challenge **/
75      private byte[] token;
76  
77      GGSSchemeBase(final boolean stripPort) {
78          super();
79          this.base64codec = new Base64(0);
80          this.stripPort = stripPort;
81          this.state = State.UNINITIATED;
82      }
83  
84      GGSSchemeBase() {
85          this(false);
86      }
87  
88      protected GSSManager getManager() {
89          return GSSManager.getInstance();
90      }
91  
92      protected byte[] generateGSSToken(
93              final byte[] input, final Oid oid, final String authServer) throws GSSException {
94          byte[] inputBuff = input;
95          if (inputBuff == null) {
96              inputBuff = new byte[0];
97          }
98          final GSSManager manager = getManager();
99          final GSSName serverName = manager.createName("HTTP@" + authServer, GSSName.NT_HOSTBASED_SERVICE);
100         final GSSContext gssContext = manager.createContext(
101                 serverName.canonicalize(oid), oid, null, GSSContext.DEFAULT_LIFETIME);
102         gssContext.requestMutualAuth(true);
103         gssContext.requestCredDeleg(true);
104         return gssContext.initSecContext(inputBuff, 0, inputBuff.length);
105     }
106 
107     protected abstract byte[] generateToken(
108             byte[] input, final String authServer) throws GSSException;
109 
110     @Override
111     public boolean isComplete() {
112         return this.state == State.TOKEN_GENERATED || this.state == State.FAILED;
113     }
114 
115     /**
116      * @deprecated (4.2) Use {@link org.apache.http.auth.ContextAwareAuthScheme#authenticate(
117      *   Credentials, HttpRequest, org.apache.http.protocol.HttpContext)}
118      */
119     @Override
120     @Deprecated
121     public Header authenticate(
122             final Credentials credentials,
123             final HttpRequest request) throws AuthenticationException {
124         return authenticate(credentials, request, null);
125     }
126 
127     @Override
128     public Header authenticate(
129             final Credentials credentials,
130             final HttpRequest request,
131             final HttpContext context) throws AuthenticationException {
132         Args.notNull(request, "HTTP request");
133         switch (state) {
134         case UNINITIATED:
135             throw new AuthenticationException(getSchemeName() + " authentication has not been initiated");
136         case FAILED:
137             throw new AuthenticationException(getSchemeName() + " authentication has failed");
138         case CHALLENGE_RECEIVED:
139             try {
140                 final HttpRoute route = (HttpRoute) context.getAttribute(HttpClientContext.HTTP_ROUTE);
141                 if (route == null) {
142                     throw new AuthenticationException("Connection route is not available");
143                 }
144                 HttpHost host;
145                 if (isProxy()) {
146                     host = route.getProxyHost();
147                     if (host == null) {
148                         host = route.getTargetHost();
149                     }
150                 } else {
151                     host = route.getTargetHost();
152                 }
153                 final String authServer;
154                 if (!this.stripPort && host.getPort() > 0) {
155                     authServer = host.toHostString();
156                 } else {
157                     authServer = host.getHostName();
158                 }
159 
160                 if (log.isDebugEnabled()) {
161                     log.debug("init " + authServer);
162                 }
163                 token = generateToken(token, authServer);
164                 state = State.TOKEN_GENERATED;
165             } catch (final GSSException gsse) {
166                 state = State.FAILED;
167                 if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL
168                         || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) {
169                     throw new InvalidCredentialsException(gsse.getMessage(), gsse);
170                 }
171                 if (gsse.getMajor() == GSSException.NO_CRED ) {
172                     throw new InvalidCredentialsException(gsse.getMessage(), gsse);
173                 }
174                 if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN
175                         || gsse.getMajor() == GSSException.DUPLICATE_TOKEN
176                         || gsse.getMajor() == GSSException.OLD_TOKEN) {
177                     throw new AuthenticationException(gsse.getMessage(), gsse);
178                 }
179                 // other error
180                 throw new AuthenticationException(gsse.getMessage());
181             }
182         case TOKEN_GENERATED:
183             final String tokenstr = new String(base64codec.encode(token));
184             if (log.isDebugEnabled()) {
185                 log.debug("Sending response '" + tokenstr + "' back to the auth server");
186             }
187             final CharArrayBuffer buffer = new CharArrayBuffer(32);
188             if (isProxy()) {
189                 buffer.append(AUTH.PROXY_AUTH_RESP);
190             } else {
191                 buffer.append(AUTH.WWW_AUTH_RESP);
192             }
193             buffer.append(": Negotiate ");
194             buffer.append(tokenstr);
195             return new BufferedHeader(buffer);
196         default:
197             throw new IllegalStateException("Illegal state: " + state);
198         }
199     }
200 
201     @Override
202     protected void parseChallenge(
203             final CharArrayBuffer buffer,
204             final int beginIndex, final int endIndex) throws MalformedChallengeException {
205         final String challenge = buffer.substringTrimmed(beginIndex, endIndex);
206         if (log.isDebugEnabled()) {
207             log.debug("Received challenge '" + challenge + "' from the auth server");
208         }
209         if (state == State.UNINITIATED) {
210             token = Base64.decodeBase64(challenge.getBytes());
211             state = State.CHALLENGE_RECEIVED;
212         } else {
213             log.debug("Authentication already attempted");
214             state = State.FAILED;
215         }
216     }
217 
218 }