/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
*
* This will delegate negotiation to the windows machine. *
** EXPERIMENTAL *
* * @since 4.4 */ public class WindowsNegotiateScheme implements AuthScheme { private final Logger log = LogManager.getLogger(getClass()); // NTLM or Negotiate private final String scheme; private final String servicePrincipalName; private ChallengeType challengeType; private String challenge; private CredHandle clientCred; private CtxtHandle sspiContext; private boolean continueNeeded; public WindowsNegotiateScheme(final String scheme, final String servicePrincipalName) { super(); this.scheme = (scheme == null) ? AuthSchemes.SPNEGO : scheme; this.continueNeeded = true; this.servicePrincipalName = servicePrincipalName; if (this.log.isDebugEnabled()) { this.log.debug("Created WindowsNegotiateScheme using " + this.scheme); } } public void dispose() { if (clientCred != null && !clientCred.isNull()) { final int rc = Secur32.INSTANCE.FreeCredentialsHandle(clientCred); if (WinError.SEC_E_OK != rc) { throw new Win32Exception(rc); } } if (sspiContext != null && !sspiContext.isNull()) { final int rc = Secur32.INSTANCE.DeleteSecurityContext(sspiContext); if (WinError.SEC_E_OK != rc) { throw new Win32Exception(rc); } } continueNeeded = true; // waiting clientCred = null; sspiContext = null; } @Override public void finalize() throws Throwable { dispose(); super.finalize(); } @Override public String getName() { return scheme; } @Override public boolean isConnectionBased() { return true; } @Override public String getRealm() { return null; } @Override public void processChallenge( final AuthChallenge authChallenge, final HttpContext context) throws MalformedChallengeException { Args.notNull(authChallenge, "AuthChallenge"); if (authChallenge.getValue() == null) { throw new MalformedChallengeException("Missing auth challenge"); } challengeType = authChallenge.getChallengeType(); challenge = authChallenge.getValue(); if (challenge.isEmpty()) { if (clientCred != null) { dispose(); // run cleanup first before throwing an exception otherwise can leak OS resources if (continueNeeded) { throw new IllegalStateException("Unexpected token"); } } } } @Override public boolean isResponseReady( final HttpHost host, final CredentialsProvider credentialsProvider, final HttpContext context) throws AuthenticationException { return true; } /** * Get the SAM-compatible username of the currently logged-on user. * * @return String. */ public static String getCurrentUsername() { return Secur32Util.getUserNameEx(Secur32.EXTENDED_NAME_FORMAT.NameSamCompatible); } @Override public Principal getPrincipal() { return new BasicUserPrincipal(getCurrentUsername()); } @Override public String generateAuthResponse( final HttpHost host, final HttpRequest request, final HttpContext context) throws AuthenticationException { final HttpClientContext clientContext = HttpClientContext.adapt(context); final String response; if (clientCred == null) { // client credentials handle try { final String username = getCurrentUsername(); final TimeStamp lifetime = new TimeStamp(); clientCred = new CredHandle(); final int rc = Secur32.INSTANCE.AcquireCredentialsHandle(username, scheme, Sspi.SECPKG_CRED_OUTBOUND, null, null, null, null, clientCred, lifetime); if (WinError.SEC_E_OK != rc) { throw new Win32Exception(rc); } final String targetName = getServicePrincipalName(request, clientContext); response = getToken(null, null, targetName); } catch (final RuntimeException ex) { failAuthCleanup(); if (ex instanceof Win32Exception) { throw new AuthenticationException("Authentication Failed", ex); } throw ex; } } else if (challenge == null || challenge.isEmpty()) { failAuthCleanup(); throw new AuthenticationException("Authentication Failed"); } else { try { final byte[] continueTokenBytes = Base64.decodeBase64(challenge); final SecBufferDesc continueTokenBuffer = new SecBufferDesc( Sspi.SECBUFFER_TOKEN, continueTokenBytes); final String targetName = getServicePrincipalName(request, clientContext); response = getToken(this.sspiContext, continueTokenBuffer, targetName); } catch (final RuntimeException ex) { failAuthCleanup(); if (ex instanceof Win32Exception) { throw new AuthenticationException("Authentication Failed", ex); } throw ex; } } return scheme + " " + response; } private void failAuthCleanup() { dispose(); this.continueNeeded = false; } // Per RFC4559, the Service Principal Name should HTTP/