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.hc.client5.http.impl.auth;
28  
29  import java.net.UnknownHostException;
30  import java.security.Principal;
31  
32  import org.apache.hc.client5.http.DnsResolver;
33  import org.apache.hc.client5.http.SystemDefaultDnsResolver;
34  import org.apache.hc.client5.http.auth.AuthChallenge;
35  import org.apache.hc.client5.http.auth.AuthScheme;
36  import org.apache.hc.client5.http.auth.AuthScope;
37  import org.apache.hc.client5.http.auth.AuthenticationException;
38  import org.apache.hc.client5.http.auth.Credentials;
39  import org.apache.hc.client5.http.auth.CredentialsProvider;
40  import org.apache.hc.client5.http.auth.InvalidCredentialsException;
41  import org.apache.hc.client5.http.auth.MalformedChallengeException;
42  import org.apache.hc.client5.http.auth.StandardAuthScheme;
43  import org.apache.hc.client5.http.protocol.HttpClientContext;
44  import org.apache.hc.client5.http.utils.Base64;
45  import org.apache.hc.core5.http.HttpHost;
46  import org.apache.hc.core5.http.HttpRequest;
47  import org.apache.hc.core5.http.protocol.HttpContext;
48  import org.apache.hc.core5.util.Args;
49  import org.ietf.jgss.GSSContext;
50  import org.ietf.jgss.GSSCredential;
51  import org.ietf.jgss.GSSException;
52  import org.ietf.jgss.GSSManager;
53  import org.ietf.jgss.GSSName;
54  import org.ietf.jgss.Oid;
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  /**
59   * Common behavior for {@code GSS} based authentication schemes.
60   *
61   * @since 4.2
62   *
63   * @deprecated Do not use. The GGS based experimental authentication schemes are no longer
64   * supported. Consider using Basic or Bearer authentication with TLS instead.
65   */
66  @Deprecated
67  public abstract class GGSSchemeBase implements AuthScheme {
68  
69      enum State {
70          UNINITIATED,
71          CHALLENGE_RECEIVED,
72          TOKEN_GENERATED,
73          FAILED,
74      }
75  
76      private static final Logger LOG = LoggerFactory.getLogger(GGSSchemeBase.class);
77      private static final String NO_TOKEN = "";
78      private static final String KERBEROS_SCHEME = "HTTP";
79      private final org.apache.hc.client5.http.auth.KerberosConfig config;
80      private final DnsResolver dnsResolver;
81  
82      /** Authentication process state */
83      private State state;
84      private GSSCredential gssCredential;
85      private String challenge;
86      private byte[] token;
87  
88      GGSSchemeBase(final org.apache.hc.client5.http.auth.KerberosConfig config, final DnsResolver dnsResolver) {
89          super();
90          this.config = config != null ? config : org.apache.hc.client5.http.auth.KerberosConfig.DEFAULT;
91          this.dnsResolver = dnsResolver != null ? dnsResolver : SystemDefaultDnsResolver.INSTANCE;
92          this.state = State.UNINITIATED;
93      }
94  
95      GGSSchemeBase(final org.apache.hc.client5.http.auth.KerberosConfig config) {
96          this(config, SystemDefaultDnsResolver.INSTANCE);
97      }
98  
99      GGSSchemeBase() {
100         this(org.apache.hc.client5.http.auth.KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE);
101     }
102 
103     @Override
104     public String getRealm() {
105         return null;
106     }
107 
108     @Override
109     public void processChallenge(
110             final AuthChallenge authChallenge,
111             final HttpContext context) throws MalformedChallengeException {
112         Args.notNull(authChallenge, "AuthChallenge");
113 
114         this.challenge = authChallenge.getValue() != null ? authChallenge.getValue() : NO_TOKEN;
115 
116         if (state == State.UNINITIATED) {
117             token = Base64.decodeBase64(challenge.getBytes());
118             state = State.CHALLENGE_RECEIVED;
119         } else {
120             if (LOG.isDebugEnabled()) {
121                 final HttpClientContext clientContext = HttpClientContext.cast(context);
122                 final String exchangeId = clientContext.getExchangeId();
123                 LOG.debug("{} Authentication already attempted", exchangeId);
124             }
125             state = State.FAILED;
126         }
127     }
128 
129     protected GSSManager getManager() {
130         return GSSManager.getInstance();
131     }
132 
133     /**
134      * @since 4.4
135      */
136     protected byte[] generateGSSToken(
137             final byte[] input, final Oid oid, final String serviceName, final String authServer) throws GSSException {
138         final GSSManager manager = getManager();
139         final GSSName serverName = manager.createName(serviceName + "@" + authServer, GSSName.NT_HOSTBASED_SERVICE);
140 
141         final GSSContext gssContext = createGSSContext(manager, oid, serverName, gssCredential);
142         if (input != null) {
143             return gssContext.initSecContext(input, 0, input.length);
144         }
145         return gssContext.initSecContext(new byte[] {}, 0, 0);
146     }
147 
148     /**
149      * @since 5.0
150      */
151     protected GSSContext createGSSContext(
152             final GSSManager manager,
153             final Oid oid,
154             final GSSName serverName,
155             final GSSCredential gssCredential) throws GSSException {
156         final GSSContext gssContext = manager.createContext(serverName.canonicalize(oid), oid, gssCredential,
157                 GSSContext.DEFAULT_LIFETIME);
158         gssContext.requestMutualAuth(true);
159         if (config.getRequestDelegCreds() != org.apache.hc.client5.http.auth.KerberosConfig.Option.DEFAULT) {
160             gssContext.requestCredDeleg(config.getRequestDelegCreds() == org.apache.hc.client5.http.auth.KerberosConfig.Option.ENABLE);
161         }
162         return gssContext;
163     }
164     /**
165      * @since 4.4
166      */
167     protected abstract byte[] generateToken(byte[] input, String serviceName, String authServer) throws GSSException;
168 
169     @Override
170     public boolean isChallengeComplete() {
171         return this.state == State.TOKEN_GENERATED || this.state == State.FAILED;
172     }
173 
174     @Override
175     public boolean isResponseReady(
176             final HttpHost host,
177             final CredentialsProvider credentialsProvider,
178             final HttpContext context) throws AuthenticationException {
179 
180         Args.notNull(host, "Auth host");
181         Args.notNull(credentialsProvider, "CredentialsProvider");
182 
183         final Credentials credentials = credentialsProvider.getCredentials(
184                 new AuthScope(host, null, getName()), context);
185         if (credentials instanceof org.apache.hc.client5.http.auth.KerberosCredentials) {
186             this.gssCredential = ((org.apache.hc.client5.http.auth.KerberosCredentials) credentials).getGSSCredential();
187         } else {
188             this.gssCredential = null;
189         }
190         return true;
191     }
192 
193     @Override
194     public Principal getPrincipal() {
195         return null;
196     }
197 
198     @Override
199     public String generateAuthResponse(
200             final HttpHost host,
201             final HttpRequest request,
202             final HttpContext context) throws AuthenticationException {
203         Args.notNull(host, "HTTP host");
204         Args.notNull(request, "HTTP request");
205         switch (state) {
206         case UNINITIATED:
207             throw new AuthenticationException(getName() + " authentication has not been initiated");
208         case FAILED:
209             throw new AuthenticationException(getName() + " authentication has failed");
210         case CHALLENGE_RECEIVED:
211             try {
212                 final String authServer;
213                 String hostname = host.getHostName();
214                 if (config.getUseCanonicalHostname() != org.apache.hc.client5.http.auth.KerberosConfig.Option.DISABLE) {
215                     try {
216                         hostname = dnsResolver.resolveCanonicalHostname(host.getHostName());
217                     } catch (final UnknownHostException ignore) {
218                     }
219                 }
220                 if (config.getStripPort() != org.apache.hc.client5.http.auth.KerberosConfig.Option.DISABLE) {
221                     authServer = hostname;
222                 } else {
223                     authServer = hostname + ":" + host.getPort();
224                 }
225 
226                 if (LOG.isDebugEnabled()) {
227                     final HttpClientContext clientContext = HttpClientContext.cast(context);
228                     final String exchangeId = clientContext.getExchangeId();
229                     LOG.debug("{} init {}", exchangeId, authServer);
230                 }
231                 token = generateToken(token, KERBEROS_SCHEME, authServer);
232                 state = State.TOKEN_GENERATED;
233             } catch (final GSSException gsse) {
234                 state = State.FAILED;
235                 if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL
236                         || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) {
237                     throw new InvalidCredentialsException(gsse.getMessage(), gsse);
238                 }
239                 if (gsse.getMajor() == GSSException.NO_CRED ) {
240                     throw new InvalidCredentialsException(gsse.getMessage(), gsse);
241                 }
242                 if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN
243                         || gsse.getMajor() == GSSException.DUPLICATE_TOKEN
244                         || gsse.getMajor() == GSSException.OLD_TOKEN) {
245                     throw new AuthenticationException(gsse.getMessage(), gsse);
246                 }
247                 // other error
248                 throw new AuthenticationException(gsse.getMessage());
249             }
250         case TOKEN_GENERATED:
251             final Base64 codec = new Base64(0);
252             final String tokenstr = new String(codec.encode(token));
253             if (LOG.isDebugEnabled()) {
254                 final HttpClientContext clientContext = HttpClientContext.cast(context);
255                 final String exchangeId = clientContext.getExchangeId();
256                 LOG.debug("{} Sending response '{}' back to the auth server", exchangeId, tokenstr);
257             }
258             return StandardAuthScheme.SPNEGO + " " + tokenstr;
259         default:
260             throw new IllegalStateException("Illegal state: " + state);
261         }
262     }
263 
264     @Override
265     public String toString() {
266         return getName() + "{" + this.state + " " + challenge + '}';
267     }
268 
269 }