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[] token = input;
95          if (token == null) {
96              token = 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(token, 0, token.length);
105     }
106 
107     protected abstract byte[] generateToken(
108             byte[] input, final String authServer) throws GSSException;
109 
110     public boolean isComplete() {
111         return this.state == State.TOKEN_GENERATED || this.state == State.FAILED;
112     }
113 
114     /**
115      * @deprecated (4.2) Use {@link org.apache.http.auth.ContextAwareAuthScheme#authenticate(
116      *   Credentials, HttpRequest, org.apache.http.protocol.HttpContext)}
117      */
118     @Deprecated
119     public Header authenticate(
120             final Credentials credentials,
121             final HttpRequest request) throws AuthenticationException {
122         return authenticate(credentials, request, null);
123     }
124 
125     @Override
126     public Header authenticate(
127             final Credentials credentials,
128             final HttpRequest request,
129             final HttpContext context) throws AuthenticationException {
130         Args.notNull(request, "HTTP request");
131         switch (state) {
132         case UNINITIATED:
133             throw new AuthenticationException(getSchemeName() + " authentication has not been initiated");
134         case FAILED:
135             throw new AuthenticationException(getSchemeName() + " authentication has failed");
136         case CHALLENGE_RECEIVED:
137             try {
138                 final HttpRoute route = (HttpRoute) context.getAttribute(HttpClientContext.HTTP_ROUTE);
139                 if (route == null) {
140                     throw new AuthenticationException("Connection route is not available");
141                 }
142                 HttpHost host;
143                 if (isProxy()) {
144                     host = route.getProxyHost();
145                     if (host == null) {
146                         host = route.getTargetHost();
147                     }
148                 } else {
149                     host = route.getTargetHost();
150                 }
151                 final String authServer;
152                 if (!this.stripPort && host.getPort() > 0) {
153                     authServer = host.toHostString();
154                 } else {
155                     authServer = host.getHostName();
156                 }
157 
158                 if (log.isDebugEnabled()) {
159                     log.debug("init " + authServer);
160                 }
161                 token = generateToken(token, authServer);
162                 state = State.TOKEN_GENERATED;
163             } catch (final GSSException gsse) {
164                 state = State.FAILED;
165                 if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL
166                         || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) {
167                     throw new InvalidCredentialsException(gsse.getMessage(), gsse);
168                 }
169                 if (gsse.getMajor() == GSSException.NO_CRED ) {
170                     throw new InvalidCredentialsException(gsse.getMessage(), gsse);
171                 }
172                 if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN
173                         || gsse.getMajor() == GSSException.DUPLICATE_TOKEN
174                         || gsse.getMajor() == GSSException.OLD_TOKEN) {
175                     throw new AuthenticationException(gsse.getMessage(), gsse);
176                 }
177                 // other error
178                 throw new AuthenticationException(gsse.getMessage());
179             }
180         case TOKEN_GENERATED:
181             final String tokenstr = new String(base64codec.encode(token));
182             if (log.isDebugEnabled()) {
183                 log.debug("Sending response '" + tokenstr + "' back to the auth server");
184             }
185             final CharArrayBuffer buffer = new CharArrayBuffer(32);
186             if (isProxy()) {
187                 buffer.append(AUTH.PROXY_AUTH_RESP);
188             } else {
189                 buffer.append(AUTH.WWW_AUTH_RESP);
190             }
191             buffer.append(": Negotiate ");
192             buffer.append(tokenstr);
193             return new BufferedHeader(buffer);
194         default:
195             throw new IllegalStateException("Illegal state: " + state);
196         }
197     }
198 
199     @Override
200     protected void parseChallenge(
201             final CharArrayBuffer buffer,
202             final int beginIndex, final int endIndex) throws MalformedChallengeException {
203         final String challenge = buffer.substringTrimmed(beginIndex, endIndex);
204         if (log.isDebugEnabled()) {
205             log.debug("Received challenge '" + challenge + "' from the auth server");
206         }
207         if (state == State.UNINITIATED) {
208             token = Base64.decodeBase64(challenge.getBytes());
209             state = State.CHALLENGE_RECEIVED;
210         } else {
211             log.debug("Authentication already attempted");
212             state = State.FAILED;
213         }
214     }
215 
216 }