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.auth.AUTH;
36  import org.apache.http.auth.AuthenticationException;
37  import org.apache.http.auth.ContextAwareAuthScheme;
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  public abstract class GGSSchemeBase extends AuthSchemeBase {
57  
58      enum State {
59          UNINITIATED,
60          CHALLENGE_RECEIVED,
61          TOKEN_GENERATED,
62          FAILED,
63      }
64  
65      private final Log log = LogFactory.getLog(getClass());
66  
67      private final Base64 base64codec;
68      private final boolean stripPort;
69  
70      /** Authentication process state */
71      private State state;
72  
73      /** base64 decoded challenge **/
74      private byte[] token;
75  
76      GGSSchemeBase(final boolean stripPort) {
77          super();
78          this.base64codec = new Base64(0);
79          this.stripPort = stripPort;
80          this.state = State.UNINITIATED;
81      }
82  
83      GGSSchemeBase() {
84          this(false);
85      }
86  
87      protected GSSManager getManager() {
88          return GSSManager.getInstance();
89      }
90  
91      protected byte[] generateGSSToken(
92              final byte[] input, final Oid oid, final String authServer) throws GSSException {
93          byte[] token = input;
94          if (token == null) {
95              token = new byte[0];
96          }
97          final GSSManager manager = getManager();
98          final GSSName serverName = manager.createName("HTTP@" + authServer, GSSName.NT_HOSTBASED_SERVICE);
99          final GSSContext gssContext = manager.createContext(
100                 serverName.canonicalize(oid), oid, null, GSSContext.DEFAULT_LIFETIME);
101         gssContext.requestMutualAuth(true);
102         gssContext.requestCredDeleg(true);
103         return gssContext.initSecContext(token, 0, token.length);
104     }
105 
106     protected abstract byte[] generateToken(
107             byte[] input, final String authServer) throws GSSException;
108 
109     public boolean isComplete() {
110         return this.state == State.TOKEN_GENERATED || this.state == State.FAILED;
111     }
112 
113     /**
114      * @deprecated (4.2) Use {@link ContextAwareAuthScheme#authenticate(Credentials, HttpRequest, org.apache.http.protocol.HttpContext)}
115      */
116     @Deprecated
117     public Header authenticate(
118             final Credentials credentials,
119             final HttpRequest request) throws AuthenticationException {
120         return authenticate(credentials, request, null);
121     }
122 
123     @Override
124     public Header authenticate(
125             final Credentials credentials,
126             final HttpRequest request,
127             final HttpContext context) throws AuthenticationException {
128         Args.notNull(request, "HTTP request");
129         switch (state) {
130         case UNINITIATED:
131             throw new AuthenticationException(getSchemeName() + " authentication has not been initiated");
132         case FAILED:
133             throw new AuthenticationException(getSchemeName() + " authentication has failed");
134         case CHALLENGE_RECEIVED:
135             try {
136                 final HttpRoute route = (HttpRoute) context.getAttribute(HttpClientContext.HTTP_ROUTE);
137                 if (route == null) {
138                     throw new AuthenticationException("Connection route is not available");
139                 }
140                 HttpHost host;
141                 if (isProxy()) {
142                     host = route.getProxyHost();
143                     if (host == null) {
144                         host = route.getTargetHost();
145                     }
146                 } else {
147                     host = route.getTargetHost();
148                 }
149                 String authServer;
150                 if (!this.stripPort && host.getPort() > 0) {
151                     authServer = host.toHostString();
152                 } else {
153                     authServer = host.getHostName();
154                 }
155 
156                 if (log.isDebugEnabled()) {
157                     log.debug("init " + authServer);
158                 }
159                 token = generateToken(token, authServer);
160                 state = State.TOKEN_GENERATED;
161             } catch (final GSSException gsse) {
162                 state = State.FAILED;
163                 if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL
164                         || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) {
165                     throw new InvalidCredentialsException(gsse.getMessage(), gsse);
166                 }
167                 if (gsse.getMajor() == GSSException.NO_CRED ) {
168                     throw new InvalidCredentialsException(gsse.getMessage(), gsse);
169                 }
170                 if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN
171                         || gsse.getMajor() == GSSException.DUPLICATE_TOKEN
172                         || gsse.getMajor() == GSSException.OLD_TOKEN) {
173                     throw new AuthenticationException(gsse.getMessage(), gsse);
174                 }
175                 // other error
176                 throw new AuthenticationException(gsse.getMessage());
177             }
178         case TOKEN_GENERATED:
179             final String tokenstr = new String(base64codec.encode(token));
180             if (log.isDebugEnabled()) {
181                 log.debug("Sending response '" + tokenstr + "' back to the auth server");
182             }
183             final CharArrayBuffer buffer = new CharArrayBuffer(32);
184             if (isProxy()) {
185                 buffer.append(AUTH.PROXY_AUTH_RESP);
186             } else {
187                 buffer.append(AUTH.WWW_AUTH_RESP);
188             }
189             buffer.append(": Negotiate ");
190             buffer.append(tokenstr);
191             return new BufferedHeader(buffer);
192         default:
193             throw new IllegalStateException("Illegal state: " + state);
194         }
195     }
196 
197     @Override
198     protected void parseChallenge(
199             final CharArrayBuffer buffer,
200             final int beginIndex, final int endIndex) throws MalformedChallengeException {
201         final String challenge = buffer.substringTrimmed(beginIndex, endIndex);
202         if (log.isDebugEnabled()) {
203             log.debug("Received challenge '" + challenge + "' from the auth server");
204         }
205         if (state == State.UNINITIATED) {
206             token = Base64.decodeBase64(challenge.getBytes());
207             state = State.CHALLENGE_RECEIVED;
208         } else {
209             log.debug("Authentication already attempted");
210             state = State.FAILED;
211         }
212     }
213 
214 }