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