@@ -77,15 +77,16 @@ enum State {
7777 }
7878
7979 private static final Logger LOG = LoggerFactory .getLogger (GssSchemeBase .class );
80- private static final String KERBEROS_SCHEME = "HTTP" ;
80+ private static final String PEER_SERVICE_NAME = "HTTP" ;
8181
8282 // The GSS spec does not specify how long the conversation can be. This should be plenty.
8383 // Realistically, we get one initial token, then one maybe one more for mutual authentication.
84+ // TODO In the future this might need to be configurable with the upcoming IAKerb support
8485 private static final int MAX_GSS_CHALLENGES = 3 ;
8586 private final GssConfig config ;
8687 private final DnsResolver dnsResolver ;
8788 private final boolean requireMutualAuth ;
88- private final boolean ignoreMissingToken ;
89+ private final boolean ignoreIncompleteSecurityContext ;
8990 private int challengesLeft = MAX_GSS_CHALLENGES ;
9091
9192 /** Authentication process state */
@@ -100,10 +101,24 @@ enum State {
100101 this .config = config != null ? config : GssConfig .DEFAULT ;
101102 this .dnsResolver = dnsResolver != null ? dnsResolver : SystemDefaultDnsResolver .INSTANCE ;
102103 this .requireMutualAuth = config .isRequireMutualAuth ();
103- this .ignoreMissingToken = config .isIgnoreMissingToken ();
104+ this .ignoreIncompleteSecurityContext = config .isIgnoreIncompleteSecurityContext ();
104105 this .state = State .UNINITIATED ;
105106 }
106107
108+ private void dispose () {
109+ // remove sensitive information from memory
110+ // cleaning up the credential is the caller's job
111+ try {
112+ if (gssContext != null ) {
113+ gssContext .dispose ();
114+ }
115+ } catch (final Exception e ) {
116+ if (LOG .isWarnEnabled ()) {
117+ LOG .warn ("Exception caught while calling gssContext.dispose()" , e );
118+ }
119+ }
120+ }
121+
107122 GssSchemeBase (final GssConfig config ) {
108123 this (config , SystemDefaultDnsResolver .INSTANCE );
109124 }
@@ -136,24 +151,28 @@ public void processChallenge(
136151 ) throws AuthenticationException {
137152
138153 if (challengesLeft -- <= 0 ) {
139- if (LOG .isDebugEnabled ()) {
154+ if (LOG .isWarnEnabled ()) {
140155 final HttpClientContext clientContext = HttpClientContext .cast (context );
141156 final String exchangeId = clientContext .getExchangeId ();
142- LOG .debug ("{} GSS error: too many challenges received. Infinite loop ?" , exchangeId );
157+ LOG .warn ("{} GSS error: too many challenges received. Infinite loop ?" , exchangeId );
143158 }
144- // TODO: Should we throw an exception ? There is a test for this behaviour.
145159 state = State .FAILED ;
146160 return ;
147161 }
148162
149- final byte [] challengeToken = Base64 . decodeBase64 (authChallenge == null ? null : authChallenge .getValue ());
163+ final byte [] challengeToken = (authChallenge == null ) ? null : Base64 . decodeBase64 ( authChallenge .getValue ());
150164
151165 final String gssHostname ;
152166 String hostname = host .getHostName ();
153167 if (config .isUseCanonicalHostname ()) {
154168 try {
155169 hostname = dnsResolver .resolveCanonicalHostname (host .getHostName ());
156170 } catch (final UnknownHostException ignore ) {
171+ if (LOG .isWarnEnabled ()) {
172+ final HttpClientContext clientContext = HttpClientContext .cast (context );
173+ final String exchangeId = clientContext .getExchangeId ();
174+ LOG .warn ("{} Could not canonicalize hostname {}, using as is." , exchangeId , host .getHostName ());
175+ }
157176 }
158177 }
159178 if (config .isAddPort ()) {
@@ -171,42 +190,42 @@ public void processChallenge(
171190 switch (state ) {
172191 case UNINITIATED :
173192 setGssCredential (HttpClientContext .cast (context ).getCredentialsProvider (), host , context );
174- queuedToken = generateToken (challengeToken , KERBEROS_SCHEME , gssHostname );
175- if (challengeToken != null ) {
193+ if (challengeToken == null ) {
194+ queuedToken = generateToken (challengeToken , PEER_SERVICE_NAME , gssHostname );
195+ state = State .TOKEN_READY ;
196+ } else {
176197 if (LOG .isDebugEnabled ()) {
177198 final HttpClientContext clientContext = HttpClientContext .cast (context );
178199 final String exchangeId = clientContext .getExchangeId ();
179200 LOG .debug ("{} Internal GSS error: token received when none was sent yet: {}" , exchangeId , challengeToken );
180201 }
181- // TODO Should we fail ? That would break existing tests that send a token
182- // in the first response, which is against the RFC.
202+ state = State .FAILED ;
183203 }
184- state = State .TOKEN_READY ;
185204 break ;
186205 case TOKEN_SENT :
187206 if (challengeToken == null ) {
188- if (!challenged && ignoreMissingToken ) {
207+ if (!challenged && ignoreIncompleteSecurityContext ) {
189208 // Got a Non 401/407 code without a challenge. Old non RFC compliant server.
190- if (LOG .isDebugEnabled ()) {
209+ if (LOG .isWarnEnabled ()) {
191210 final HttpClientContext clientContext = HttpClientContext .cast (context );
192211 final String exchangeId = clientContext .getExchangeId ();
193- LOG .debug ("{} GSS Context is not established, but continuing because GssConfig.ignoreMissingToken is true." , exchangeId );
212+ LOG .warn ("{} GSS Context is not established, but continuing because GssConfig.ignoreIncompleteSecurityContext is true." , exchangeId );
194213 }
195214 state = State .SUCCEEDED ;
196215 break ;
197216 } else {
198217 if (LOG .isDebugEnabled ()) {
199218 final HttpClientContext clientContext = HttpClientContext .cast (context );
200219 final String exchangeId = clientContext .getExchangeId ();
201- LOG .debug ("{} Did not receive required challenge and GssConfig.ignoreMissingToken is false ." ,
220+ LOG .debug ("{} Did not receive required challenge." ,
202221 exchangeId );
203222 }
204223 state = State .FAILED ;
205224 throw new AuthenticationException (
206- "Did not receive required challenge and GssConfig.ignoreMissingToken is false ." );
225+ "Did not receive required challenge." );
207226 }
208227 }
209- queuedToken = generateToken (challengeToken , KERBEROS_SCHEME , gssHostname );
228+ queuedToken = generateToken (challengeToken , PEER_SERVICE_NAME , gssHostname );
210229 if (challenged ) {
211230 state = State .TOKEN_READY ;
212231 } else if (!gssContext .isEstablished ()) {
@@ -237,16 +256,16 @@ public void processChallenge(
237256 LOG .debug ("{} GSSContext MutualAuthState is false, but continuing because GssConfig.requireMutualAuth is false." ,
238257 exchangeId );
239258 }
240- state = State .FAILED ;
259+ state = State .SUCCEEDED ;
241260 }
242261 } else {
243262 state = State .SUCCEEDED ;
244263 }
245264 break ;
246265 default :
266+ final State prevState = state ;
247267 state = State .FAILED ;
248- throw new IllegalStateException ("Illegal state: " + state );
249-
268+ throw new IllegalStateException ("Illegal state: " + prevState );
250269 }
251270 } catch (final GSSException gsse ) {
252271 state = State .FAILED ;
@@ -264,6 +283,10 @@ public void processChallenge(
264283 }
265284 // other error
266285 throw new AuthenticationException (gsse .getMessage (), gsse );
286+ } finally {
287+ if ((state == State .FAILED || state == State .SUCCEEDED ) && gssContext != null ) {
288+ dispose ();
289+ }
267290 }
268291 }
269292
@@ -336,9 +359,6 @@ public boolean isResponseReady(
336359 protected void setGssCredential (final CredentialsProvider credentialsProvider ,
337360 final HttpHost host ,
338361 final HttpContext context ) {
339- if (this .gssCredential != null ) {
340- return ;
341- }
342362 final Credentials credentials =
343363 credentialsProvider .getCredentials (new AuthScope (host , null , getName ()), context );
344364 if (credentials instanceof org .apache .hc .client5 .http .auth .gss .GssCredentials ) {
0 commit comments