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