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.adapt(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         } else {
145             return gssContext.initSecContext(new byte[] {}, 0, 0);
146         }
147     }
148 
149     /**
150      * @since 5.0
151      */
152     protected GSSContext createGSSContext(
153             final GSSManager manager,
154             final Oid oid,
155             final GSSName serverName,
156             final GSSCredential gssCredential) throws GSSException {
157         final GSSContext gssContext = manager.createContext(serverName.canonicalize(oid), oid, gssCredential,
158                 GSSContext.DEFAULT_LIFETIME);
159         gssContext.requestMutualAuth(true);
160         if (config.getRequestDelegCreds() != org.apache.hc.client5.http.auth.KerberosConfig.Option.DEFAULT) {
161             gssContext.requestCredDeleg(config.getRequestDelegCreds() == org.apache.hc.client5.http.auth.KerberosConfig.Option.ENABLE);
162         }
163         return gssContext;
164     }
165     /**
166      * @since 4.4
167      */
168     protected abstract byte[] generateToken(byte[] input, String serviceName, String authServer) throws GSSException;
169 
170     @Override
171     public boolean isChallengeComplete() {
172         return this.state == State.TOKEN_GENERATED || this.state == State.FAILED;
173     }
174 
175     @Override
176     public boolean isResponseReady(
177             final HttpHost host,
178             final CredentialsProvider credentialsProvider,
179             final HttpContext context) throws AuthenticationException {
180 
181         Args.notNull(host, "Auth host");
182         Args.notNull(credentialsProvider, "CredentialsProvider");
183 
184         final Credentials credentials = credentialsProvider.getCredentials(
185                 new AuthScope(host, null, getName()), context);
186         if (credentials instanceof org.apache.hc.client5.http.auth.KerberosCredentials) {
187             this.gssCredential = ((org.apache.hc.client5.http.auth.KerberosCredentials) credentials).getGSSCredential();
188         } else {
189             this.gssCredential = null;
190         }
191         return true;
192     }
193 
194     @Override
195     public Principal getPrincipal() {
196         return null;
197     }
198 
199     @Override
200     public String generateAuthResponse(
201             final HttpHost host,
202             final HttpRequest request,
203             final HttpContext context) throws AuthenticationException {
204         Args.notNull(host, "HTTP host");
205         Args.notNull(request, "HTTP request");
206         switch (state) {
207         case UNINITIATED:
208             throw new AuthenticationException(getName() + " authentication has not been initiated");
209         case FAILED:
210             throw new AuthenticationException(getName() + " authentication has failed");
211         case CHALLENGE_RECEIVED:
212             try {
213                 final String authServer;
214                 String hostname = host.getHostName();
215                 if (config.getUseCanonicalHostname() != org.apache.hc.client5.http.auth.KerberosConfig.Option.DISABLE){
216                     try {
217                          hostname = dnsResolver.resolveCanonicalHostname(host.getHostName());
218                     } catch (final UnknownHostException ignore){
219                     }
220                 }
221                 if (config.getStripPort() != org.apache.hc.client5.http.auth.KerberosConfig.Option.DISABLE) {
222                     authServer = hostname;
223                 } else {
224                     authServer = hostname + ":" + host.getPort();
225                 }
226 
227                 if (LOG.isDebugEnabled()) {
228                     final HttpClientContext clientContext = HttpClientContext.adapt(context);
229                     final String exchangeId = clientContext.getExchangeId();
230                     LOG.debug("{} init {}", exchangeId, authServer);
231                 }
232                 token = generateToken(token, KERBEROS_SCHEME, authServer);
233                 state = State.TOKEN_GENERATED;
234             } catch (final GSSException gsse) {
235                 state = State.FAILED;
236                 if (gsse.getMajor() == GSSException.DEFECTIVE_CREDENTIAL
237                         || gsse.getMajor() == GSSException.CREDENTIALS_EXPIRED) {
238                     throw new InvalidCredentialsException(gsse.getMessage(), gsse);
239                 }
240                 if (gsse.getMajor() == GSSException.NO_CRED ) {
241                     throw new InvalidCredentialsException(gsse.getMessage(), gsse);
242                 }
243                 if (gsse.getMajor() == GSSException.DEFECTIVE_TOKEN
244                         || gsse.getMajor() == GSSException.DUPLICATE_TOKEN
245                         || gsse.getMajor() == GSSException.OLD_TOKEN) {
246                     throw new AuthenticationException(gsse.getMessage(), gsse);
247                 }
248                 // other error
249                 throw new AuthenticationException(gsse.getMessage());
250             }
251         case TOKEN_GENERATED:
252             final Base64 codec = new Base64(0);
253             final String tokenstr = new String(codec.encode(token));
254             if (LOG.isDebugEnabled()) {
255                 final HttpClientContext clientContext = HttpClientContext.adapt(context);
256                 final String exchangeId = clientContext.getExchangeId();
257                 LOG.debug("{} Sending response '{}' back to the auth server", exchangeId, tokenstr);
258             }
259             return StandardAuthScheme.SPNEGO + " " + tokenstr;
260         default:
261             throw new IllegalStateException("Illegal state: " + state);
262         }
263     }
264 
265     @Override
266     public String toString() {
267         return getName() + "{" + this.state + " " + challenge + '}';
268     }
269 
270 }