From 61529d1f880ce4ff371adad74da580865f2680d6 Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Wed, 12 Jul 2017 17:29:17 -0700 Subject: [PATCH] Checkstyle fixes: Use final and tab police. --- .../impl/client/cache/CachingHttpClient.java | 6 +- .../impl/client/cache/CacheKeyGenerator.java | 2 +- .../http/impl/client/cache/CachingExec.java | 2 +- .../client/cache/HeapResourceFactory.java | 2 +- .../memcached/MemcachedCacheEntryImpl.java | 8 +- .../impl/HttpProxyConfigurationActivator.java | 2 +- .../http/conn/ssl/AbstractVerifier.java | 2 +- .../apache/http/impl/auth/CredSspScheme.java | 2252 ++++++++--------- .../org/apache/http/impl/auth/DebugUtil.java | 192 +- .../apache/http/impl/auth/NTLMEngineImpl.java | 12 +- .../http/impl/client/HttpClientBuilder.java | 2 +- .../apache/http/impl/cookie/RFC2109Spec.java | 2 +- .../http/conn/ssl/TestHostnameVerifier.java | 2 +- .../http/impl/auth/TestNTLMEngineImpl.java | 2 +- .../apache/http/localserver/EchoHandler.java | 2 +- 15 files changed, 1245 insertions(+), 1245 deletions(-) diff --git a/httpclient-cache/src/main/java-deprecated/org/apache/http/impl/client/cache/CachingHttpClient.java b/httpclient-cache/src/main/java-deprecated/org/apache/http/impl/client/cache/CachingHttpClient.java index c066eaf68..20f54cf62 100644 --- a/httpclient-cache/src/main/java-deprecated/org/apache/http/impl/client/cache/CachingHttpClient.java +++ b/httpclient-cache/src/main/java-deprecated/org/apache/http/impl/client/cache/CachingHttpClient.java @@ -387,7 +387,7 @@ public class CachingHttpClient implements HttpClient { private T handleAndConsume( final ResponseHandler responseHandler, final HttpResponse response) throws Error, IOException { - T result; + final T result; try { result = responseHandler.handleResponse(response); } catch (final Exception t) { @@ -429,7 +429,7 @@ public class CachingHttpClient implements HttpClient { public HttpResponse execute(final HttpHost target, final HttpRequest originalRequest, final HttpContext context) throws IOException { - HttpRequestWrapper request; + final HttpRequestWrapper request; if (originalRequest instanceof HttpRequestWrapper) { request = ((HttpRequestWrapper) originalRequest); } else { @@ -693,7 +693,7 @@ public class CachingHttpClient implements HttpClient { final VersionInfo vi = VersionInfo.loadVersionInfo("org.apache.http.client", getClass().getClassLoader()); final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE; - String value; + final String value; if ("http".equalsIgnoreCase(pv.getProtocol())) { value = String.format("%d.%d localhost (Apache-HttpClient/%s (cache))", pv.getMajor(), pv.getMinor(), release); diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheKeyGenerator.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheKeyGenerator.java index 91ca500eb..70df8cd04 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheKeyGenerator.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheKeyGenerator.java @@ -155,7 +155,7 @@ class CacheKeyGenerator { } Collections.sort(variantHeaderNames); - StringBuilder buf; + final StringBuilder buf; try { buf = new StringBuilder("{"); boolean first = true; diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java index 33bdbf10a..f7da4d0c9 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java @@ -527,7 +527,7 @@ public class CachingExec implements ClientExecChain { final VersionInfo vi = VersionInfo.loadVersionInfo("org.apache.http.client", getClass().getClassLoader()); final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE; - String value; + final String value; final int major = pv.getMajor(); final int minor = pv.getMinor(); if ("http".equalsIgnoreCase(pv.getProtocol())) { diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/HeapResourceFactory.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/HeapResourceFactory.java index 11506e24e..8cb9a2d82 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/HeapResourceFactory.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/HeapResourceFactory.java @@ -68,7 +68,7 @@ public class HeapResourceFactory implements ResourceFactory { public Resource copy( final String requestId, final Resource resource) throws IOException { - byte[] body; + final byte[] body; if (resource instanceof HeapResource) { body = ((HeapResource) resource).getByteArray(); } else { diff --git a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntryImpl.java b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntryImpl.java index e1c4e37c8..9bc11e52b 100644 --- a/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntryImpl.java +++ b/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/memcached/MemcachedCacheEntryImpl.java @@ -58,7 +58,7 @@ public class MemcachedCacheEntryImpl implements MemcachedCacheEntry { @Override synchronized public byte[] toByteArray() { final ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream oos; + final ObjectOutputStream oos; try { oos = new ObjectOutputStream(bos); oos.writeObject(this.key); @@ -92,9 +92,9 @@ public class MemcachedCacheEntryImpl implements MemcachedCacheEntry { @Override synchronized public void set(final byte[] bytes) { final ByteArrayInputStream bis = new ByteArrayInputStream(bytes); - ObjectInputStream ois; - String s; - HttpCacheEntry entry; + final ObjectInputStream ois; + final String s; + final HttpCacheEntry entry; try { ois = new ObjectInputStream(bis); s = (String)ois.readObject(); diff --git a/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/HttpProxyConfigurationActivator.java b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/HttpProxyConfigurationActivator.java index 16d5c1bf5..4ca17f14e 100644 --- a/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/HttpProxyConfigurationActivator.java +++ b/httpclient-osgi/src/main/java/org/apache/http/osgi/impl/HttpProxyConfigurationActivator.java @@ -155,7 +155,7 @@ public final class HttpProxyConfigurationActivator implements BundleActivator, M @Override public void updated(final String pid, @SuppressWarnings("rawtypes") final Dictionary config) throws ConfigurationException { final ServiceRegistration registration = registeredConfigurations.get(pid); - OSGiProxyConfiguration proxyConfiguration; + final OSGiProxyConfiguration proxyConfiguration; if (registration == null) { proxyConfiguration = new OSGiProxyConfiguration(); diff --git a/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java b/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java index 37771133c..ec7c0bfe1 100644 --- a/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java +++ b/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java @@ -203,7 +203,7 @@ public abstract class AbstractVerifier implements X509HostnameVerifier { final boolean doWildcard = parts.length >= 3 && parts[0].endsWith("*") && (!strict || validCountryWildcard(parts)); if (doWildcard) { - boolean match; + final boolean match; final String firstpart = parts[0]; if (firstpart.length() > 1) { // e.g. server* final String prefix = firstpart.substring(0, firstpart.length() - 1); // e.g. server diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/CredSspScheme.java b/httpclient/src/main/java/org/apache/http/impl/auth/CredSspScheme.java index f8db658c9..9cc5fc84e 100644 --- a/httpclient/src/main/java/org/apache/http/impl/auth/CredSspScheme.java +++ b/httpclient/src/main/java/org/apache/http/impl/auth/CredSspScheme.java @@ -1,1126 +1,1126 @@ -/* - * ==================================================================== - * 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 - * . - * - */ - -package org.apache.http.impl.auth; - - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Arrays; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLEngineResult; -import javax.net.ssl.SSLEngineResult.HandshakeStatus; -import javax.net.ssl.SSLEngineResult.Status; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.http.Consts; -import org.apache.http.Header; -import org.apache.http.HttpRequest; -import org.apache.http.auth.AUTH; -import org.apache.http.auth.AuthenticationException; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.InvalidCredentialsException; -import org.apache.http.auth.MalformedChallengeException; -import org.apache.http.auth.NTCredentials; -import org.apache.http.message.BufferedHeader; -import org.apache.http.protocol.HttpContext; -import org.apache.http.ssl.SSLContexts; -import org.apache.http.util.CharArrayBuffer; -import org.apache.http.util.CharsetUtils; - -/** - *

- * Client implementation of the CredSSP protocol specified in [MS-CSSP]. - *

- *

- * Note: This is implementation is NOT GSS based. It should be. But there is no Java NTLM - * implementation as GSS module. Maybe the NTLMEngine can be converted to GSS and then this - * can be also switched to GSS. In fact it only works in CredSSP+NTLM case. - *

- *

- * Based on [MS-CSSP]: Credential Security Support Provider (CredSSP) Protocol (Revision 13.0, 7/14/2016). - * The implementation was inspired by Python CredSSP and NTLM implementation by Jordan Borean. - *

- */ -public class CredSspScheme extends AuthSchemeBase -{ - private static final Charset UNICODE_LITTLE_UNMARKED = CharsetUtils.lookup( "UnicodeLittleUnmarked" ); - public static final String SCHEME_NAME = "CredSSP"; - - private final Log log = LogFactory.getLog( CredSspScheme.class ); - - enum State - { - // Nothing sent, nothing received - UNINITIATED, - - // We are handshaking. Several messages are exchanged in this state - TLS_HANDSHAKE, - - // TLS handshake finished. Channel established - TLS_HANDSHAKE_FINISHED, - - // NTLM NEGOTIATE message sent (strictly speaking this should be SPNEGO) - NEGO_TOKEN_SENT, - - // NTLM CHALLENGE message received (strictly speaking this should be SPNEGO) - NEGO_TOKEN_RECEIVED, - - // NTLM AUTHENTICATE message sent together with a server public key - PUB_KEY_AUTH_SENT, - - // Server public key authentication message received - PUB_KEY_AUTH_RECEIVED, - - // Credentials message sent. Protocol exchange finished. - CREDENTIALS_SENT; - } - - private State state; - private SSLEngine sslEngine; - private NTLMEngineImpl.Type1Message type1Message; - private NTLMEngineImpl.Type2Message type2Message; - private NTLMEngineImpl.Type3Message type3Message; - private CredSspTsRequest lastReceivedTsRequest; - private NTLMEngineImpl.Handle ntlmOutgoingHandle; - private NTLMEngineImpl.Handle ntlmIncomingHandle; - private byte[] peerPublicKey; - - - public CredSspScheme() { - state = State.UNINITIATED; - } - - - @Override - public String getSchemeName() - { - return SCHEME_NAME; - } - - - @Override - public String getParameter( final String name ) - { - return null; - } - - - @Override - public String getRealm() - { - return null; - } - - - @Override - public boolean isConnectionBased() - { - return true; - } - - - private SSLEngine getSSLEngine() - { - if ( sslEngine == null ) - { - sslEngine = createSSLEngine(); - } - return sslEngine; - } - - - private SSLEngine createSSLEngine() - { - SSLContext sslContext; - try - { - sslContext = SSLContexts.custom().build(); - } - catch ( NoSuchAlgorithmException e ) - { - throw new RuntimeException( "Error creating SSL Context: " + e.getMessage(), e ); - } - catch ( KeyManagementException e ) - { - throw new RuntimeException( "Error creating SSL Context: " + e.getMessage(), e ); - } - - final X509TrustManager tm = new X509TrustManager() - { - - @Override - public void checkClientTrusted( final X509Certificate[] chain, final String authType ) - throws CertificateException - { - // Nothing to do. - } - - - @Override - public void checkServerTrusted( final X509Certificate[] chain, final String authType ) - throws CertificateException - { - // Nothing to do, accept all. CredSSP server is using its own certificate without any - // binding to the PKI trust chains. The public key is verified as part of the CredSSP - // protocol exchange. - } - - - @Override - public X509Certificate[] getAcceptedIssuers() - { - return null; - } - - }; - try - { - sslContext.init( null, new TrustManager[] - { tm }, null ); - } - catch ( KeyManagementException e ) - { - throw new RuntimeException( "SSL Context initialization error: " + e.getMessage(), e ); - } - final SSLEngine sslEngine = sslContext.createSSLEngine(); - sslEngine.setUseClientMode( true ); - return sslEngine; - } - - - @Override - protected void parseChallenge( final CharArrayBuffer buffer, final int beginIndex, final int endIndex ) - throws MalformedChallengeException - { - final String inputString = buffer.substringTrimmed( beginIndex, endIndex ); - - if ( inputString.isEmpty() ) - { - if ( state == State.UNINITIATED ) - { - // This is OK, just send out first message. That should start TLS handshake - } - else - { - final String msg = "Received unexpected empty input in state " + state; - log.error( msg ); - throw new MalformedChallengeException( msg ); - } - } - - if ( state == State.TLS_HANDSHAKE ) - { - unwrapHandshake( inputString ); - if ( getSSLEngine().getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING ) - { - log.trace( "TLS handshake finished" ); - state = State.TLS_HANDSHAKE_FINISHED; - } - } - - if ( state == State.NEGO_TOKEN_SENT ) - { - final ByteBuffer buf = unwrap( inputString ); - state = State.NEGO_TOKEN_RECEIVED; - lastReceivedTsRequest = CredSspTsRequest.createDecoded( buf ); - } - - if ( state == State.PUB_KEY_AUTH_SENT ) - { - final ByteBuffer buf = unwrap( inputString ); - state = State.PUB_KEY_AUTH_RECEIVED; - lastReceivedTsRequest = CredSspTsRequest.createDecoded( buf ); - } - } - - - @Override - @Deprecated - public Header authenticate( - final Credentials credentials, - final HttpRequest request ) throws AuthenticationException - { - return authenticate( credentials, request, null ); - } - - - @Override - public Header authenticate( - final Credentials credentials, - final HttpRequest request, - final HttpContext context ) throws AuthenticationException - { - NTCredentials ntcredentials = null; - try - { - ntcredentials = ( NTCredentials ) credentials; - } - catch ( final ClassCastException e ) - { - throw new InvalidCredentialsException( - "Credentials cannot be used for CredSSP authentication: " - + credentials.getClass().getName() ); - } - - String outputString = null; - - if ( state == State.UNINITIATED ) - { - beginTlsHandshake(); - outputString = wrapHandshake(); - state = State.TLS_HANDSHAKE; - - } - else if ( state == State.TLS_HANDSHAKE ) - { - outputString = wrapHandshake(); - - } - else if ( state == State.TLS_HANDSHAKE_FINISHED ) - { - - final int ntlmFlags = getNtlmFlags(); - final ByteBuffer buf = allocateOutBuffer(); - type1Message = new NTLMEngineImpl.Type1Message( - ntcredentials.getDomain(), ntcredentials.getWorkstation(), ntlmFlags); - final byte[] ntlmNegoMessageEncoded = type1Message.getBytes(); - final CredSspTsRequest req = CredSspTsRequest.createNegoToken( ntlmNegoMessageEncoded ); - req.encode( buf ); - buf.flip(); - outputString = wrap( buf ); - state = State.NEGO_TOKEN_SENT; - - } - else if ( state == State.NEGO_TOKEN_RECEIVED ) - { - final ByteBuffer buf = allocateOutBuffer(); - type2Message = new NTLMEngineImpl.Type2Message( - lastReceivedTsRequest.getNegoToken()); - - final Certificate peerServerCertificate = getPeerServerCertificate(); - - type3Message = new NTLMEngineImpl.Type3Message( - ntcredentials.getDomain(), - ntcredentials.getWorkstation(), - ntcredentials.getUserName(), - ntcredentials.getPassword(), - type2Message.getChallenge(), - type2Message.getFlags(), - type2Message.getTarget(), - type2Message.getTargetInfo(), - peerServerCertificate, - type1Message.getBytes(), - type2Message.getBytes()); - - final byte[] ntlmAuthenticateMessageEncoded = type3Message.getBytes(); - - final byte[] exportedSessionKey = type3Message.getExportedSessionKey(); - - ntlmOutgoingHandle = new NTLMEngineImpl.Handle(exportedSessionKey, NTLMEngineImpl.Mode.CLIENT, true); - ntlmIncomingHandle = new NTLMEngineImpl.Handle(exportedSessionKey, NTLMEngineImpl.Mode.SERVER, true); - - final CredSspTsRequest req = CredSspTsRequest.createNegoToken( ntlmAuthenticateMessageEncoded ); - peerPublicKey = getSubjectPublicKeyDer( peerServerCertificate.getPublicKey() ); - final byte[] pubKeyAuth = createPubKeyAuth(); - req.setPubKeyAuth( pubKeyAuth ); - - req.encode( buf ); - buf.flip(); - outputString = wrap( buf ); - state = State.PUB_KEY_AUTH_SENT; - - } - else if ( state == State.PUB_KEY_AUTH_RECEIVED ) - { - verifyPubKeyAuthResponse( lastReceivedTsRequest.getPubKeyAuth() ); - final byte[] authInfo = createAuthInfo( ntcredentials ); - final CredSspTsRequest req = CredSspTsRequest.createAuthInfo( authInfo ); - - final ByteBuffer buf = allocateOutBuffer(); - req.encode( buf ); - buf.flip(); - outputString = wrap( buf ); - state = State.CREDENTIALS_SENT; - } - else - { - throw new AuthenticationException( "Wrong state " + state ); - } - final CharArrayBuffer buffer = new CharArrayBuffer( 32 ); - if ( isProxy() ) - { - buffer.append( AUTH.PROXY_AUTH_RESP ); - } - else - { - buffer.append( AUTH.WWW_AUTH_RESP ); - } - buffer.append( ": CredSSP " ); - buffer.append( outputString ); - return new BufferedHeader( buffer ); - } - - - private int getNtlmFlags() - { - return NTLMEngineImpl.FLAG_REQUEST_OEM_ENCODING | - NTLMEngineImpl.FLAG_REQUEST_SIGN | - NTLMEngineImpl.FLAG_REQUEST_SEAL | - NTLMEngineImpl.FLAG_DOMAIN_PRESENT | - NTLMEngineImpl.FLAG_REQUEST_ALWAYS_SIGN | - NTLMEngineImpl.FLAG_REQUEST_NTLM2_SESSION | - NTLMEngineImpl.FLAG_TARGETINFO_PRESENT | - NTLMEngineImpl.FLAG_REQUEST_VERSION | - NTLMEngineImpl.FLAG_REQUEST_128BIT_KEY_EXCH | - NTLMEngineImpl.FLAG_REQUEST_EXPLICIT_KEY_EXCH | - NTLMEngineImpl.FLAG_REQUEST_56BIT_ENCRYPTION; - } - - - private Certificate getPeerServerCertificate() throws AuthenticationException - { - Certificate[] peerCertificates; - try - { - peerCertificates = sslEngine.getSession().getPeerCertificates(); - } - catch ( SSLPeerUnverifiedException e ) - { - throw new AuthenticationException( e.getMessage(), e ); - } - for ( Certificate peerCertificate : peerCertificates ) - { - if ( !( peerCertificate instanceof X509Certificate ) ) - { - continue; - } - final X509Certificate peerX509Cerificate = ( X509Certificate ) peerCertificate; - if ( peerX509Cerificate.getBasicConstraints() != -1 ) - { - continue; - } - return peerX509Cerificate; - } - return null; - } - - - private byte[] createPubKeyAuth() throws AuthenticationException - { - return ntlmOutgoingHandle.signAndEncryptMessage( peerPublicKey ); - } - - - private void verifyPubKeyAuthResponse( final byte[] pubKeyAuthResponse ) throws AuthenticationException - { - final byte[] pubKeyReceived = ntlmIncomingHandle.decryptAndVerifySignedMessage( pubKeyAuthResponse ); - - // assert: pubKeyReceived = peerPublicKey + 1 - // The following algorithm is a bit simplified. But due to the ASN.1 encoding the first byte - // of the public key will be 0x30 we can pretty much rely on a fact that there will be no carry - if ( peerPublicKey.length != pubKeyReceived.length ) - { - throw new AuthenticationException( "Public key mismatch in pubKeyAuth response" ); - } - if ( ( peerPublicKey[0] + 1 ) != pubKeyReceived[0] ) - { - throw new AuthenticationException( "Public key mismatch in pubKeyAuth response" ); - } - for ( int i = 1; i < peerPublicKey.length; i++ ) - { - if ( peerPublicKey[i] != pubKeyReceived[i] ) - { - throw new AuthenticationException( "Public key mismatch in pubKeyAuth response" ); - } - } - log.trace( "Received public key response is valid" ); - } - - - private byte[] createAuthInfo( final NTCredentials ntcredentials ) throws AuthenticationException - { - - final byte[] domainBytes = encodeUnicode( ntcredentials.getDomain() ); - final byte[] domainOctetStringBytesLengthBytes = encodeLength( domainBytes.length ); - final int domainNameLength = 1 + domainOctetStringBytesLengthBytes.length + domainBytes.length; - final byte[] domainNameLengthBytes = encodeLength( domainNameLength ); - - final byte[] usernameBytes = encodeUnicode( ntcredentials.getUserName() ); - final byte[] usernameOctetStringBytesLengthBytes = encodeLength( usernameBytes.length ); - final int userNameLength = 1 + usernameOctetStringBytesLengthBytes.length + usernameBytes.length; - final byte[] userNameLengthBytes = encodeLength( userNameLength ); - - final byte[] passwordBytes = encodeUnicode( ntcredentials.getPassword() ); - final byte[] passwordOctetStringBytesLengthBytes = encodeLength( passwordBytes.length ); - final int passwordLength = 1 + passwordOctetStringBytesLengthBytes.length + passwordBytes.length; - final byte[] passwordLengthBytes = encodeLength( passwordLength ); - - final int tsPasswordLength = 1 + domainNameLengthBytes.length + domainNameLength + - 1 + userNameLengthBytes.length + userNameLength + - 1 + passwordLengthBytes.length + passwordLength; - final byte[] tsPasswordLengthBytes = encodeLength( tsPasswordLength ); - final int credentialsOctetStringLength = 1 + tsPasswordLengthBytes.length + tsPasswordLength; - final byte[] credentialsOctetStringLengthBytes = encodeLength( credentialsOctetStringLength ); - final int credentialsLength = 1 + credentialsOctetStringLengthBytes.length + credentialsOctetStringLength; - final byte[] credentialsLengthBytes = encodeLength( credentialsLength ); - final int tsCredentialsLength = 5 + 1 + credentialsLengthBytes.length + credentialsLength; - final byte[] tsCredentialsLengthBytes = encodeLength( tsCredentialsLength ); - - final ByteBuffer buf = ByteBuffer.allocate( 1 + tsCredentialsLengthBytes.length + tsCredentialsLength ); - - // TSCredentials structure [MS-CSSP] section 2.2.1.2 - buf.put( ( byte ) 0x30 ); // seq - buf.put( tsCredentialsLengthBytes ); - - buf.put( ( byte ) ( 0x00 | 0xa0 ) ); // credType tag [0] - buf.put( ( byte ) 3 ); // credType length - buf.put( ( byte ) 0x02 ); // type: INTEGER - buf.put( ( byte ) 1 ); // credType inner length - buf.put( ( byte ) 1 ); // credType value: 1 (password) - - buf.put( ( byte ) ( 0x01 | 0xa0 ) ); // credentials tag [1] - buf.put( credentialsLengthBytes ); - buf.put( ( byte ) 0x04 ); // type: OCTET STRING - buf.put( credentialsOctetStringLengthBytes ); - - // TSPasswordCreds structure [MS-CSSP] section 2.2.1.2.1 - buf.put( ( byte ) 0x30 ); // seq - buf.put( tsPasswordLengthBytes ); - - buf.put( ( byte ) ( 0x00 | 0xa0 ) ); // domainName tag [0] - buf.put( domainNameLengthBytes ); - buf.put( ( byte ) 0x04 ); // type: OCTET STRING - buf.put( domainOctetStringBytesLengthBytes ); - buf.put( domainBytes ); - - buf.put( ( byte ) ( 0x01 | 0xa0 ) ); // userName tag [1] - buf.put( userNameLengthBytes ); - buf.put( ( byte ) 0x04 ); // type: OCTET STRING - buf.put( usernameOctetStringBytesLengthBytes ); - buf.put( usernameBytes ); - - buf.put( ( byte ) ( 0x02 | 0xa0 ) ); // password tag [2] - buf.put( passwordLengthBytes ); - buf.put( ( byte ) 0x04 ); // type: OCTET STRING - buf.put( passwordOctetStringBytesLengthBytes ); - buf.put( passwordBytes ); - - final byte[] authInfo = buf.array(); - try - { - return ntlmOutgoingHandle.signAndEncryptMessage( authInfo ); - } - catch ( NTLMEngineException e ) - { - throw new AuthenticationException( e.getMessage(), e ); - } - } - - private final static byte[] EMPTYBUFFER = new byte[0]; - - private byte[] encodeUnicode( final String string ) - { - if (string == null) { - return EMPTYBUFFER; - } - return string.getBytes( UNICODE_LITTLE_UNMARKED ); - } - - - private byte[] getSubjectPublicKeyDer( final PublicKey publicKey ) throws AuthenticationException - { - // The publicKey.getEncoded() returns encoded SubjectPublicKeyInfo structure. But the CredSSP expects - // SubjectPublicKey subfield. I have found no easy way how to get just the SubjectPublicKey from - // java.security libraries. So let's use a primitive way and parse it out from the DER. - - try - { - final byte[] encodedPubKeyInfo = publicKey.getEncoded(); - - final ByteBuffer buf = ByteBuffer.wrap( encodedPubKeyInfo ); - getByteAndAssert( buf, 0x30, "initial sequence" ); - parseLength( buf ); - getByteAndAssert( buf, 0x30, "AlgorithmIdentifier sequence" ); - final int algIdSeqLength = parseLength( buf ); - buf.position( buf.position() + algIdSeqLength ); - getByteAndAssert( buf, 0x03, "subjectPublicKey type" ); - int subjectPublicKeyLegth = parseLength( buf ); - // There may be leading padding byte ... or whatever that is. Skip that. - final byte b = buf.get(); - if ( b == 0 ) - { - subjectPublicKeyLegth--; - } - else - { - buf.position( buf.position() - 1 ); - } - final byte[] subjectPublicKey = new byte[subjectPublicKeyLegth]; - buf.get( subjectPublicKey ); - return subjectPublicKey; - } - catch ( MalformedChallengeException e ) - { - throw new AuthenticationException( e.getMessage(), e ); - } - } - - - private void beginTlsHandshake() throws AuthenticationException - { - try - { - getSSLEngine().beginHandshake(); - } - catch ( SSLException e ) - { - throw new AuthenticationException( "SSL Engine error: " + e.getMessage(), e ); - } - } - - - private ByteBuffer allocateOutBuffer() - { - final SSLEngine sslEngine = getSSLEngine(); - final SSLSession sslSession = sslEngine.getSession(); - return ByteBuffer.allocate( sslSession.getApplicationBufferSize() ); - } - - - private String wrapHandshake() throws AuthenticationException - { - final ByteBuffer src = allocateOutBuffer(); - src.flip(); - final SSLEngine sslEngine = getSSLEngine(); - final SSLSession sslSession = sslEngine.getSession(); - // Needs to be twice the size as there may be two wraps during handshake. - // Primitive and inefficient solution, but it works. - final ByteBuffer dst = ByteBuffer.allocate( sslSession.getPacketBufferSize() * 2 ); - while ( sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP ) - { - wrap( src, dst ); - } - dst.flip(); - return encodeBase64( dst ); - } - - - private String wrap( final ByteBuffer src ) throws AuthenticationException - { - final SSLEngine sslEngine = getSSLEngine(); - final SSLSession sslSession = sslEngine.getSession(); - final ByteBuffer dst = ByteBuffer.allocate( sslSession.getPacketBufferSize() ); - wrap( src, dst ); - dst.flip(); - return encodeBase64( dst ); - } - - - private void wrap( final ByteBuffer src, final ByteBuffer dst ) throws AuthenticationException - { - final SSLEngine sslEngine = getSSLEngine(); - try - { - final SSLEngineResult engineResult = sslEngine.wrap( src, dst ); - if ( engineResult.getStatus() != Status.OK ) - { - throw new AuthenticationException( "SSL Engine error status: " + engineResult.getStatus() ); - } - } - catch ( SSLException e ) - { - throw new AuthenticationException( "SSL Engine wrap error: " + e.getMessage(), e ); - } - } - - - private void unwrapHandshake( final String inputString ) throws MalformedChallengeException - { - final SSLEngine sslEngine = getSSLEngine(); - final SSLSession sslSession = sslEngine.getSession(); - final ByteBuffer src = decodeBase64( inputString ); - final ByteBuffer dst = ByteBuffer.allocate( sslSession.getApplicationBufferSize() ); - while ( sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) - { - unwrap( src, dst ); - } - } - - - private ByteBuffer unwrap( final String inputString ) throws MalformedChallengeException - { - final SSLEngine sslEngine = getSSLEngine(); - final SSLSession sslSession = sslEngine.getSession(); - final ByteBuffer src = decodeBase64( inputString ); - final ByteBuffer dst = ByteBuffer.allocate( sslSession.getApplicationBufferSize() ); - unwrap( src, dst ); - dst.flip(); - return dst; - } - - - private void unwrap( final ByteBuffer src, final ByteBuffer dst ) throws MalformedChallengeException - { - - try - { - final SSLEngineResult engineResult = sslEngine.unwrap( src, dst ); - if ( engineResult.getStatus() != Status.OK ) - { - throw new MalformedChallengeException( "SSL Engine error status: " + engineResult.getStatus() ); - } - - if ( sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_TASK ) - { - final Runnable task = sslEngine.getDelegatedTask(); - task.run(); - } - - } - catch ( SSLException e ) - { - throw new MalformedChallengeException( "SSL Engine unwrap error: " + e.getMessage(), e ); - } - } - - - private String encodeBase64( final ByteBuffer buffer ) - { - final int limit = buffer.limit(); - final byte[] bytes = new byte[limit]; - buffer.get( bytes ); - return new String(Base64.encodeBase64(bytes), Consts.ASCII); - } - - - private ByteBuffer decodeBase64( final String inputString ) - { - final byte[] inputBytes = Base64.decodeBase64(inputString.getBytes(Consts.ASCII)); - final ByteBuffer buffer = ByteBuffer.wrap( inputBytes ); - return buffer; - } - - - @Override - public boolean isComplete() - { - return state == State.CREDENTIALS_SENT; - } - - /** - * Implementation of the TsRequest structure used in CredSSP protocol. - * It is specified in [MS-CPPS] section 2.2.1. - */ - static class CredSspTsRequest - { - - private static final int VERSION = 3; - - private byte[] negoToken; - private byte[] authInfo; - private byte[] pubKeyAuth; - - - protected CredSspTsRequest() - { - super(); - } - - - public static CredSspTsRequest createNegoToken( final byte[] negoToken ) - { - final CredSspTsRequest req = new CredSspTsRequest(); - req.negoToken = negoToken; - return req; - } - - - public static CredSspTsRequest createAuthInfo( final byte[] authInfo ) - { - final CredSspTsRequest req = new CredSspTsRequest(); - req.authInfo = authInfo; - return req; - } - - - public static CredSspTsRequest createDecoded( final ByteBuffer buf ) throws MalformedChallengeException - { - final CredSspTsRequest req = new CredSspTsRequest(); - req.decode( buf ); - return req; - } - - - public byte[] getNegoToken() - { - return negoToken; - } - - - public void setNegoToken( final byte[] negoToken ) - { - this.negoToken = negoToken; - } - - - public byte[] getAuthInfo() - { - return authInfo; - } - - - public void setAuthInfo( final byte[] authInfo ) - { - this.authInfo = authInfo; - } - - - public byte[] getPubKeyAuth() - { - return pubKeyAuth; - } - - - public void setPubKeyAuth( final byte[] pubKeyAuth ) - { - this.pubKeyAuth = pubKeyAuth; - } - - - public void decode( final ByteBuffer buf ) throws MalformedChallengeException - { - negoToken = null; - authInfo = null; - pubKeyAuth = null; - - getByteAndAssert( buf, 0x30, "initial sequence" ); - parseLength( buf ); - - while ( buf.hasRemaining() ) - { - final int contentTag = getAndAssertContentSpecificTag( buf, "content tag" ); - parseLength( buf ); - switch ( contentTag ) - { - case 0: - processVersion( buf ); - break; - case 1: - parseNegoTokens( buf ); - break; - case 2: - parseAuthInfo( buf ); - break; - case 3: - parsePubKeyAuth( buf ); - break; - case 4: - processErrorCode( buf ); - break; - default: - parseError( buf, "unexpected content tag " + contentTag ); - } - } - } - - - private void processVersion( final ByteBuffer buf ) throws MalformedChallengeException - { - getByteAndAssert( buf, 0x02, "version type" ); - getLengthAndAssert( buf, 1, "version length" ); - getByteAndAssert( buf, VERSION, "wrong protocol version" ); - } - - - private void parseNegoTokens( final ByteBuffer buf ) throws MalformedChallengeException - { - getByteAndAssert( buf, 0x30, "negoTokens sequence" ); - parseLength( buf ); - // I have seen both 0x30LL encoding and 0x30LL0x30LL encoding. Accept both. - byte bufByte = buf.get(); - if ( bufByte == 0x30 ) - { - parseLength( buf ); - bufByte = buf.get(); - } - if ( ( bufByte & 0xff ) != 0xa0 ) - { - parseError( buf, "negoTokens: wrong content-specific tag " + String.format( "%02X", bufByte ) ); - } - parseLength( buf ); - getByteAndAssert( buf, 0x04, "negoToken type" ); - - final int tokenLength = parseLength( buf ); - negoToken = new byte[tokenLength]; - buf.get( negoToken ); - } - - - private void parseAuthInfo( final ByteBuffer buf ) throws MalformedChallengeException - { - getByteAndAssert( buf, 0x04, "authInfo type" ); - final int length = parseLength( buf ); - authInfo = new byte[length]; - buf.get( authInfo ); - } - - - private void parsePubKeyAuth( final ByteBuffer buf ) throws MalformedChallengeException - { - getByteAndAssert( buf, 0x04, "pubKeyAuth type" ); - final int length = parseLength( buf ); - pubKeyAuth = new byte[length]; - buf.get( pubKeyAuth ); - } - - - private void processErrorCode( final ByteBuffer buf ) throws MalformedChallengeException - { - getLengthAndAssert( buf, 3, "error code length" ); - getByteAndAssert( buf, 0x02, "error code type" ); - getLengthAndAssert( buf, 1, "error code length" ); - final byte errorCode = buf.get(); - parseError( buf, "Error code " + errorCode ); - } - - - public void encode( final ByteBuffer buf ) - { - final ByteBuffer inner = ByteBuffer.allocate( buf.capacity() ); - - // version tag [0] - inner.put( ( byte ) ( 0x00 | 0xa0 ) ); - inner.put( ( byte ) 3 ); // length - - inner.put( ( byte ) ( 0x02 ) ); // INTEGER tag - inner.put( ( byte ) 1 ); // length - inner.put( ( byte ) VERSION ); // value - - if ( negoToken != null ) - { - int len = negoToken.length; - final byte[] negoTokenLengthBytes = encodeLength( len ); - len += 1 + negoTokenLengthBytes.length; - final byte[] negoTokenLength1Bytes = encodeLength( len ); - len += 1 + negoTokenLength1Bytes.length; - final byte[] negoTokenLength2Bytes = encodeLength( len ); - len += 1 + negoTokenLength2Bytes.length; - final byte[] negoTokenLength3Bytes = encodeLength( len ); - len += 1 + negoTokenLength3Bytes.length; - final byte[] negoTokenLength4Bytes = encodeLength( len ); - - inner.put( ( byte ) ( 0x01 | 0xa0 ) ); // negoData tag [1] - inner.put( negoTokenLength4Bytes ); // length - - inner.put( ( byte ) ( 0x30 ) ); // SEQUENCE tag - inner.put( negoTokenLength3Bytes ); // length - - inner.put( ( byte ) ( 0x30 ) ); // .. of SEQUENCE tag - inner.put( negoTokenLength2Bytes ); // length - - inner.put( ( byte ) ( 0x00 | 0xa0 ) ); // negoToken tag [0] - inner.put( negoTokenLength1Bytes ); // length - - inner.put( ( byte ) ( 0x04 ) ); // OCTET STRING tag - inner.put( negoTokenLengthBytes ); // length - - inner.put( negoToken ); - } - - if ( authInfo != null ) - { - final byte[] authInfoEncodedLength = encodeLength( authInfo.length ); - - inner.put( ( byte ) ( 0x02 | 0xa0 ) ); // authInfo tag [2] - inner.put( encodeLength( 1 + authInfoEncodedLength.length + authInfo.length ) ); // length - - inner.put( ( byte ) ( 0x04 ) ); // OCTET STRING tag - inner.put( authInfoEncodedLength ); - inner.put( authInfo ); - } - - if ( pubKeyAuth != null ) - { - final byte[] pubKeyAuthEncodedLength = encodeLength( pubKeyAuth.length ); - - inner.put( ( byte ) ( 0x03 | 0xa0 ) ); // pubKeyAuth tag [3] - inner.put( encodeLength( 1 + pubKeyAuthEncodedLength.length + pubKeyAuth.length ) ); // length - - inner.put( ( byte ) ( 0x04 ) ); // OCTET STRING tag - inner.put( pubKeyAuthEncodedLength ); - inner.put( pubKeyAuth ); - } - - inner.flip(); - - // SEQUENCE tag - buf.put( ( byte ) ( 0x10 | 0x20 ) ); - buf.put( encodeLength( inner.limit() ) ); - buf.put( inner ); - } - - - public String debugDump() - { - final StringBuilder sb = new StringBuilder( "TsRequest\n" ); - sb.append( " negoToken:\n" ); - sb.append( " " ); - DebugUtil.dump( sb, negoToken ); - sb.append( "\n" ); - sb.append( " authInfo:\n" ); - sb.append( " " ); - DebugUtil.dump( sb, authInfo ); - sb.append( "\n" ); - sb.append( " pubKeyAuth:\n" ); - sb.append( " " ); - DebugUtil.dump( sb, pubKeyAuth ); - return sb.toString(); - } - - - @Override - public String toString() - { - return "TsRequest(negoToken=" + Arrays.toString( negoToken ) + ", authInfo=" - + Arrays.toString( authInfo ) + ", pubKeyAuth=" + Arrays.toString( pubKeyAuth ) + ")"; - } - } - - static void getByteAndAssert( final ByteBuffer buf, final int expectedValue, final String errorMessage ) - throws MalformedChallengeException - { - final byte bufByte = buf.get(); - if ( bufByte != expectedValue ) - { - parseError( buf, errorMessage + expectMessage( expectedValue, bufByte ) ); - } - } - - private static String expectMessage( final int expectedValue, final int realValue ) - { - return "(expected " + String.format( "%02X", expectedValue ) + ", got " + String.format( "%02X", realValue ) - + ")"; - } - - static int parseLength( final ByteBuffer buf ) - { - byte bufByte = buf.get(); - if ( bufByte == 0x80 ) - { - return -1; // infinite - } - if ( ( bufByte & 0x80 ) == 0x80 ) - { - final int size = bufByte & 0x7f; - int length = 0; - for ( int i = 0; i < size; i++ ) - { - bufByte = buf.get(); - length = ( length << 8 ) + ( bufByte & 0xff ); - } - return length; - } - else - { - return bufByte; - } - } - - static void getLengthAndAssert( final ByteBuffer buf, final int expectedValue, final String errorMessage ) - throws MalformedChallengeException - { - final int bufLength = parseLength( buf ); - if ( expectedValue != bufLength ) - { - parseError( buf, errorMessage + expectMessage( expectedValue, bufLength ) ); - } - } - - static int getAndAssertContentSpecificTag( final ByteBuffer buf, final String errorMessage ) throws MalformedChallengeException - { - final byte bufByte = buf.get(); - if ( ( bufByte & 0xe0 ) != 0xa0 ) - { - parseError( buf, errorMessage + ": wrong content-specific tag " + String.format( "%02X", bufByte ) ); - } - final int tag = bufByte & 0x1f; - return tag; - } - - static void parseError( final ByteBuffer buf, final String errorMessage ) throws MalformedChallengeException - { - throw new MalformedChallengeException( - "Error parsing TsRequest (position:" + buf.position() + "): " + errorMessage ); - } - - static byte[] encodeLength( final int length ) - { - if ( length < 128 ) - { - final byte[] encoded = new byte[1]; - encoded[0] = ( byte ) length; - return encoded; - } - - int size = 1; - - int val = length; - while ( ( val >>>= 8 ) != 0 ) - { - size++; - } - - final byte[] encoded = new byte[1 + size]; - encoded[0] = ( byte ) ( size | 0x80 ); - - int shift = ( size - 1 ) * 8; - for ( int i = 0; i < size; i++ ) - { - encoded[i + 1] = ( byte ) ( length >> shift ); - shift -= 8; - } - - return encoded; - } - -} +/* + * ==================================================================== + * 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 + * . + * + */ + +package org.apache.http.impl.auth; + + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLEngineResult.Status; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.Consts; +import org.apache.http.Header; +import org.apache.http.HttpRequest; +import org.apache.http.auth.AUTH; +import org.apache.http.auth.AuthenticationException; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.InvalidCredentialsException; +import org.apache.http.auth.MalformedChallengeException; +import org.apache.http.auth.NTCredentials; +import org.apache.http.message.BufferedHeader; +import org.apache.http.protocol.HttpContext; +import org.apache.http.ssl.SSLContexts; +import org.apache.http.util.CharArrayBuffer; +import org.apache.http.util.CharsetUtils; + +/** + *

+ * Client implementation of the CredSSP protocol specified in [MS-CSSP]. + *

+ *

+ * Note: This is implementation is NOT GSS based. It should be. But there is no Java NTLM + * implementation as GSS module. Maybe the NTLMEngine can be converted to GSS and then this + * can be also switched to GSS. In fact it only works in CredSSP+NTLM case. + *

+ *

+ * Based on [MS-CSSP]: Credential Security Support Provider (CredSSP) Protocol (Revision 13.0, 7/14/2016). + * The implementation was inspired by Python CredSSP and NTLM implementation by Jordan Borean. + *

+ */ +public class CredSspScheme extends AuthSchemeBase +{ + private static final Charset UNICODE_LITTLE_UNMARKED = CharsetUtils.lookup( "UnicodeLittleUnmarked" ); + public static final String SCHEME_NAME = "CredSSP"; + + private final Log log = LogFactory.getLog( CredSspScheme.class ); + + enum State + { + // Nothing sent, nothing received + UNINITIATED, + + // We are handshaking. Several messages are exchanged in this state + TLS_HANDSHAKE, + + // TLS handshake finished. Channel established + TLS_HANDSHAKE_FINISHED, + + // NTLM NEGOTIATE message sent (strictly speaking this should be SPNEGO) + NEGO_TOKEN_SENT, + + // NTLM CHALLENGE message received (strictly speaking this should be SPNEGO) + NEGO_TOKEN_RECEIVED, + + // NTLM AUTHENTICATE message sent together with a server public key + PUB_KEY_AUTH_SENT, + + // Server public key authentication message received + PUB_KEY_AUTH_RECEIVED, + + // Credentials message sent. Protocol exchange finished. + CREDENTIALS_SENT; + } + + private State state; + private SSLEngine sslEngine; + private NTLMEngineImpl.Type1Message type1Message; + private NTLMEngineImpl.Type2Message type2Message; + private NTLMEngineImpl.Type3Message type3Message; + private CredSspTsRequest lastReceivedTsRequest; + private NTLMEngineImpl.Handle ntlmOutgoingHandle; + private NTLMEngineImpl.Handle ntlmIncomingHandle; + private byte[] peerPublicKey; + + + public CredSspScheme() { + state = State.UNINITIATED; + } + + + @Override + public String getSchemeName() + { + return SCHEME_NAME; + } + + + @Override + public String getParameter( final String name ) + { + return null; + } + + + @Override + public String getRealm() + { + return null; + } + + + @Override + public boolean isConnectionBased() + { + return true; + } + + + private SSLEngine getSSLEngine() + { + if ( sslEngine == null ) + { + sslEngine = createSSLEngine(); + } + return sslEngine; + } + + + private SSLEngine createSSLEngine() + { + final SSLContext sslContext; + try + { + sslContext = SSLContexts.custom().build(); + } + catch ( final NoSuchAlgorithmException e ) + { + throw new RuntimeException( "Error creating SSL Context: " + e.getMessage(), e ); + } + catch ( final KeyManagementException e ) + { + throw new RuntimeException( "Error creating SSL Context: " + e.getMessage(), e ); + } + + final X509TrustManager tm = new X509TrustManager() + { + + @Override + public void checkClientTrusted( final X509Certificate[] chain, final String authType ) + throws CertificateException + { + // Nothing to do. + } + + + @Override + public void checkServerTrusted( final X509Certificate[] chain, final String authType ) + throws CertificateException + { + // Nothing to do, accept all. CredSSP server is using its own certificate without any + // binding to the PKI trust chains. The public key is verified as part of the CredSSP + // protocol exchange. + } + + + @Override + public X509Certificate[] getAcceptedIssuers() + { + return null; + } + + }; + try + { + sslContext.init( null, new TrustManager[] + { tm }, null ); + } + catch ( final KeyManagementException e ) + { + throw new RuntimeException( "SSL Context initialization error: " + e.getMessage(), e ); + } + final SSLEngine sslEngine = sslContext.createSSLEngine(); + sslEngine.setUseClientMode( true ); + return sslEngine; + } + + + @Override + protected void parseChallenge( final CharArrayBuffer buffer, final int beginIndex, final int endIndex ) + throws MalformedChallengeException + { + final String inputString = buffer.substringTrimmed( beginIndex, endIndex ); + + if ( inputString.isEmpty() ) + { + if ( state == State.UNINITIATED ) + { + // This is OK, just send out first message. That should start TLS handshake + } + else + { + final String msg = "Received unexpected empty input in state " + state; + log.error( msg ); + throw new MalformedChallengeException( msg ); + } + } + + if ( state == State.TLS_HANDSHAKE ) + { + unwrapHandshake( inputString ); + if ( getSSLEngine().getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING ) + { + log.trace( "TLS handshake finished" ); + state = State.TLS_HANDSHAKE_FINISHED; + } + } + + if ( state == State.NEGO_TOKEN_SENT ) + { + final ByteBuffer buf = unwrap( inputString ); + state = State.NEGO_TOKEN_RECEIVED; + lastReceivedTsRequest = CredSspTsRequest.createDecoded( buf ); + } + + if ( state == State.PUB_KEY_AUTH_SENT ) + { + final ByteBuffer buf = unwrap( inputString ); + state = State.PUB_KEY_AUTH_RECEIVED; + lastReceivedTsRequest = CredSspTsRequest.createDecoded( buf ); + } + } + + + @Override + @Deprecated + public Header authenticate( + final Credentials credentials, + final HttpRequest request ) throws AuthenticationException + { + return authenticate( credentials, request, null ); + } + + + @Override + public Header authenticate( + final Credentials credentials, + final HttpRequest request, + final HttpContext context ) throws AuthenticationException + { + NTCredentials ntcredentials = null; + try + { + ntcredentials = ( NTCredentials ) credentials; + } + catch ( final ClassCastException e ) + { + throw new InvalidCredentialsException( + "Credentials cannot be used for CredSSP authentication: " + + credentials.getClass().getName() ); + } + + String outputString = null; + + if ( state == State.UNINITIATED ) + { + beginTlsHandshake(); + outputString = wrapHandshake(); + state = State.TLS_HANDSHAKE; + + } + else if ( state == State.TLS_HANDSHAKE ) + { + outputString = wrapHandshake(); + + } + else if ( state == State.TLS_HANDSHAKE_FINISHED ) + { + + final int ntlmFlags = getNtlmFlags(); + final ByteBuffer buf = allocateOutBuffer(); + type1Message = new NTLMEngineImpl.Type1Message( + ntcredentials.getDomain(), ntcredentials.getWorkstation(), ntlmFlags); + final byte[] ntlmNegoMessageEncoded = type1Message.getBytes(); + final CredSspTsRequest req = CredSspTsRequest.createNegoToken( ntlmNegoMessageEncoded ); + req.encode( buf ); + buf.flip(); + outputString = wrap( buf ); + state = State.NEGO_TOKEN_SENT; + + } + else if ( state == State.NEGO_TOKEN_RECEIVED ) + { + final ByteBuffer buf = allocateOutBuffer(); + type2Message = new NTLMEngineImpl.Type2Message( + lastReceivedTsRequest.getNegoToken()); + + final Certificate peerServerCertificate = getPeerServerCertificate(); + + type3Message = new NTLMEngineImpl.Type3Message( + ntcredentials.getDomain(), + ntcredentials.getWorkstation(), + ntcredentials.getUserName(), + ntcredentials.getPassword(), + type2Message.getChallenge(), + type2Message.getFlags(), + type2Message.getTarget(), + type2Message.getTargetInfo(), + peerServerCertificate, + type1Message.getBytes(), + type2Message.getBytes()); + + final byte[] ntlmAuthenticateMessageEncoded = type3Message.getBytes(); + + final byte[] exportedSessionKey = type3Message.getExportedSessionKey(); + + ntlmOutgoingHandle = new NTLMEngineImpl.Handle(exportedSessionKey, NTLMEngineImpl.Mode.CLIENT, true); + ntlmIncomingHandle = new NTLMEngineImpl.Handle(exportedSessionKey, NTLMEngineImpl.Mode.SERVER, true); + + final CredSspTsRequest req = CredSspTsRequest.createNegoToken( ntlmAuthenticateMessageEncoded ); + peerPublicKey = getSubjectPublicKeyDer( peerServerCertificate.getPublicKey() ); + final byte[] pubKeyAuth = createPubKeyAuth(); + req.setPubKeyAuth( pubKeyAuth ); + + req.encode( buf ); + buf.flip(); + outputString = wrap( buf ); + state = State.PUB_KEY_AUTH_SENT; + + } + else if ( state == State.PUB_KEY_AUTH_RECEIVED ) + { + verifyPubKeyAuthResponse( lastReceivedTsRequest.getPubKeyAuth() ); + final byte[] authInfo = createAuthInfo( ntcredentials ); + final CredSspTsRequest req = CredSspTsRequest.createAuthInfo( authInfo ); + + final ByteBuffer buf = allocateOutBuffer(); + req.encode( buf ); + buf.flip(); + outputString = wrap( buf ); + state = State.CREDENTIALS_SENT; + } + else + { + throw new AuthenticationException( "Wrong state " + state ); + } + final CharArrayBuffer buffer = new CharArrayBuffer( 32 ); + if ( isProxy() ) + { + buffer.append( AUTH.PROXY_AUTH_RESP ); + } + else + { + buffer.append( AUTH.WWW_AUTH_RESP ); + } + buffer.append( ": CredSSP " ); + buffer.append( outputString ); + return new BufferedHeader( buffer ); + } + + + private int getNtlmFlags() + { + return NTLMEngineImpl.FLAG_REQUEST_OEM_ENCODING | + NTLMEngineImpl.FLAG_REQUEST_SIGN | + NTLMEngineImpl.FLAG_REQUEST_SEAL | + NTLMEngineImpl.FLAG_DOMAIN_PRESENT | + NTLMEngineImpl.FLAG_REQUEST_ALWAYS_SIGN | + NTLMEngineImpl.FLAG_REQUEST_NTLM2_SESSION | + NTLMEngineImpl.FLAG_TARGETINFO_PRESENT | + NTLMEngineImpl.FLAG_REQUEST_VERSION | + NTLMEngineImpl.FLAG_REQUEST_128BIT_KEY_EXCH | + NTLMEngineImpl.FLAG_REQUEST_EXPLICIT_KEY_EXCH | + NTLMEngineImpl.FLAG_REQUEST_56BIT_ENCRYPTION; + } + + + private Certificate getPeerServerCertificate() throws AuthenticationException + { + final Certificate[] peerCertificates; + try + { + peerCertificates = sslEngine.getSession().getPeerCertificates(); + } + catch ( final SSLPeerUnverifiedException e ) + { + throw new AuthenticationException( e.getMessage(), e ); + } + for ( final Certificate peerCertificate : peerCertificates ) + { + if ( !( peerCertificate instanceof X509Certificate ) ) + { + continue; + } + final X509Certificate peerX509Cerificate = ( X509Certificate ) peerCertificate; + if ( peerX509Cerificate.getBasicConstraints() != -1 ) + { + continue; + } + return peerX509Cerificate; + } + return null; + } + + + private byte[] createPubKeyAuth() throws AuthenticationException + { + return ntlmOutgoingHandle.signAndEncryptMessage( peerPublicKey ); + } + + + private void verifyPubKeyAuthResponse( final byte[] pubKeyAuthResponse ) throws AuthenticationException + { + final byte[] pubKeyReceived = ntlmIncomingHandle.decryptAndVerifySignedMessage( pubKeyAuthResponse ); + + // assert: pubKeyReceived = peerPublicKey + 1 + // The following algorithm is a bit simplified. But due to the ASN.1 encoding the first byte + // of the public key will be 0x30 we can pretty much rely on a fact that there will be no carry + if ( peerPublicKey.length != pubKeyReceived.length ) + { + throw new AuthenticationException( "Public key mismatch in pubKeyAuth response" ); + } + if ( ( peerPublicKey[0] + 1 ) != pubKeyReceived[0] ) + { + throw new AuthenticationException( "Public key mismatch in pubKeyAuth response" ); + } + for ( int i = 1; i < peerPublicKey.length; i++ ) + { + if ( peerPublicKey[i] != pubKeyReceived[i] ) + { + throw new AuthenticationException( "Public key mismatch in pubKeyAuth response" ); + } + } + log.trace( "Received public key response is valid" ); + } + + + private byte[] createAuthInfo( final NTCredentials ntcredentials ) throws AuthenticationException + { + + final byte[] domainBytes = encodeUnicode( ntcredentials.getDomain() ); + final byte[] domainOctetStringBytesLengthBytes = encodeLength( domainBytes.length ); + final int domainNameLength = 1 + domainOctetStringBytesLengthBytes.length + domainBytes.length; + final byte[] domainNameLengthBytes = encodeLength( domainNameLength ); + + final byte[] usernameBytes = encodeUnicode( ntcredentials.getUserName() ); + final byte[] usernameOctetStringBytesLengthBytes = encodeLength( usernameBytes.length ); + final int userNameLength = 1 + usernameOctetStringBytesLengthBytes.length + usernameBytes.length; + final byte[] userNameLengthBytes = encodeLength( userNameLength ); + + final byte[] passwordBytes = encodeUnicode( ntcredentials.getPassword() ); + final byte[] passwordOctetStringBytesLengthBytes = encodeLength( passwordBytes.length ); + final int passwordLength = 1 + passwordOctetStringBytesLengthBytes.length + passwordBytes.length; + final byte[] passwordLengthBytes = encodeLength( passwordLength ); + + final int tsPasswordLength = 1 + domainNameLengthBytes.length + domainNameLength + + 1 + userNameLengthBytes.length + userNameLength + + 1 + passwordLengthBytes.length + passwordLength; + final byte[] tsPasswordLengthBytes = encodeLength( tsPasswordLength ); + final int credentialsOctetStringLength = 1 + tsPasswordLengthBytes.length + tsPasswordLength; + final byte[] credentialsOctetStringLengthBytes = encodeLength( credentialsOctetStringLength ); + final int credentialsLength = 1 + credentialsOctetStringLengthBytes.length + credentialsOctetStringLength; + final byte[] credentialsLengthBytes = encodeLength( credentialsLength ); + final int tsCredentialsLength = 5 + 1 + credentialsLengthBytes.length + credentialsLength; + final byte[] tsCredentialsLengthBytes = encodeLength( tsCredentialsLength ); + + final ByteBuffer buf = ByteBuffer.allocate( 1 + tsCredentialsLengthBytes.length + tsCredentialsLength ); + + // TSCredentials structure [MS-CSSP] section 2.2.1.2 + buf.put( ( byte ) 0x30 ); // seq + buf.put( tsCredentialsLengthBytes ); + + buf.put( ( byte ) ( 0x00 | 0xa0 ) ); // credType tag [0] + buf.put( ( byte ) 3 ); // credType length + buf.put( ( byte ) 0x02 ); // type: INTEGER + buf.put( ( byte ) 1 ); // credType inner length + buf.put( ( byte ) 1 ); // credType value: 1 (password) + + buf.put( ( byte ) ( 0x01 | 0xa0 ) ); // credentials tag [1] + buf.put( credentialsLengthBytes ); + buf.put( ( byte ) 0x04 ); // type: OCTET STRING + buf.put( credentialsOctetStringLengthBytes ); + + // TSPasswordCreds structure [MS-CSSP] section 2.2.1.2.1 + buf.put( ( byte ) 0x30 ); // seq + buf.put( tsPasswordLengthBytes ); + + buf.put( ( byte ) ( 0x00 | 0xa0 ) ); // domainName tag [0] + buf.put( domainNameLengthBytes ); + buf.put( ( byte ) 0x04 ); // type: OCTET STRING + buf.put( domainOctetStringBytesLengthBytes ); + buf.put( domainBytes ); + + buf.put( ( byte ) ( 0x01 | 0xa0 ) ); // userName tag [1] + buf.put( userNameLengthBytes ); + buf.put( ( byte ) 0x04 ); // type: OCTET STRING + buf.put( usernameOctetStringBytesLengthBytes ); + buf.put( usernameBytes ); + + buf.put( ( byte ) ( 0x02 | 0xa0 ) ); // password tag [2] + buf.put( passwordLengthBytes ); + buf.put( ( byte ) 0x04 ); // type: OCTET STRING + buf.put( passwordOctetStringBytesLengthBytes ); + buf.put( passwordBytes ); + + final byte[] authInfo = buf.array(); + try + { + return ntlmOutgoingHandle.signAndEncryptMessage( authInfo ); + } + catch ( final NTLMEngineException e ) + { + throw new AuthenticationException( e.getMessage(), e ); + } + } + + private final static byte[] EMPTYBUFFER = new byte[0]; + + private byte[] encodeUnicode( final String string ) + { + if (string == null) { + return EMPTYBUFFER; + } + return string.getBytes( UNICODE_LITTLE_UNMARKED ); + } + + + private byte[] getSubjectPublicKeyDer( final PublicKey publicKey ) throws AuthenticationException + { + // The publicKey.getEncoded() returns encoded SubjectPublicKeyInfo structure. But the CredSSP expects + // SubjectPublicKey subfield. I have found no easy way how to get just the SubjectPublicKey from + // java.security libraries. So let's use a primitive way and parse it out from the DER. + + try + { + final byte[] encodedPubKeyInfo = publicKey.getEncoded(); + + final ByteBuffer buf = ByteBuffer.wrap( encodedPubKeyInfo ); + getByteAndAssert( buf, 0x30, "initial sequence" ); + parseLength( buf ); + getByteAndAssert( buf, 0x30, "AlgorithmIdentifier sequence" ); + final int algIdSeqLength = parseLength( buf ); + buf.position( buf.position() + algIdSeqLength ); + getByteAndAssert( buf, 0x03, "subjectPublicKey type" ); + int subjectPublicKeyLegth = parseLength( buf ); + // There may be leading padding byte ... or whatever that is. Skip that. + final byte b = buf.get(); + if ( b == 0 ) + { + subjectPublicKeyLegth--; + } + else + { + buf.position( buf.position() - 1 ); + } + final byte[] subjectPublicKey = new byte[subjectPublicKeyLegth]; + buf.get( subjectPublicKey ); + return subjectPublicKey; + } + catch ( final MalformedChallengeException e ) + { + throw new AuthenticationException( e.getMessage(), e ); + } + } + + + private void beginTlsHandshake() throws AuthenticationException + { + try + { + getSSLEngine().beginHandshake(); + } + catch ( final SSLException e ) + { + throw new AuthenticationException( "SSL Engine error: " + e.getMessage(), e ); + } + } + + + private ByteBuffer allocateOutBuffer() + { + final SSLEngine sslEngine = getSSLEngine(); + final SSLSession sslSession = sslEngine.getSession(); + return ByteBuffer.allocate( sslSession.getApplicationBufferSize() ); + } + + + private String wrapHandshake() throws AuthenticationException + { + final ByteBuffer src = allocateOutBuffer(); + src.flip(); + final SSLEngine sslEngine = getSSLEngine(); + final SSLSession sslSession = sslEngine.getSession(); + // Needs to be twice the size as there may be two wraps during handshake. + // Primitive and inefficient solution, but it works. + final ByteBuffer dst = ByteBuffer.allocate( sslSession.getPacketBufferSize() * 2 ); + while ( sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP ) + { + wrap( src, dst ); + } + dst.flip(); + return encodeBase64( dst ); + } + + + private String wrap( final ByteBuffer src ) throws AuthenticationException + { + final SSLEngine sslEngine = getSSLEngine(); + final SSLSession sslSession = sslEngine.getSession(); + final ByteBuffer dst = ByteBuffer.allocate( sslSession.getPacketBufferSize() ); + wrap( src, dst ); + dst.flip(); + return encodeBase64( dst ); + } + + + private void wrap( final ByteBuffer src, final ByteBuffer dst ) throws AuthenticationException + { + final SSLEngine sslEngine = getSSLEngine(); + try + { + final SSLEngineResult engineResult = sslEngine.wrap( src, dst ); + if ( engineResult.getStatus() != Status.OK ) + { + throw new AuthenticationException( "SSL Engine error status: " + engineResult.getStatus() ); + } + } + catch ( final SSLException e ) + { + throw new AuthenticationException( "SSL Engine wrap error: " + e.getMessage(), e ); + } + } + + + private void unwrapHandshake( final String inputString ) throws MalformedChallengeException + { + final SSLEngine sslEngine = getSSLEngine(); + final SSLSession sslSession = sslEngine.getSession(); + final ByteBuffer src = decodeBase64( inputString ); + final ByteBuffer dst = ByteBuffer.allocate( sslSession.getApplicationBufferSize() ); + while ( sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) + { + unwrap( src, dst ); + } + } + + + private ByteBuffer unwrap( final String inputString ) throws MalformedChallengeException + { + final SSLEngine sslEngine = getSSLEngine(); + final SSLSession sslSession = sslEngine.getSession(); + final ByteBuffer src = decodeBase64( inputString ); + final ByteBuffer dst = ByteBuffer.allocate( sslSession.getApplicationBufferSize() ); + unwrap( src, dst ); + dst.flip(); + return dst; + } + + + private void unwrap( final ByteBuffer src, final ByteBuffer dst ) throws MalformedChallengeException + { + + try + { + final SSLEngineResult engineResult = sslEngine.unwrap( src, dst ); + if ( engineResult.getStatus() != Status.OK ) + { + throw new MalformedChallengeException( "SSL Engine error status: " + engineResult.getStatus() ); + } + + if ( sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_TASK ) + { + final Runnable task = sslEngine.getDelegatedTask(); + task.run(); + } + + } + catch ( final SSLException e ) + { + throw new MalformedChallengeException( "SSL Engine unwrap error: " + e.getMessage(), e ); + } + } + + + private String encodeBase64( final ByteBuffer buffer ) + { + final int limit = buffer.limit(); + final byte[] bytes = new byte[limit]; + buffer.get( bytes ); + return new String(Base64.encodeBase64(bytes), Consts.ASCII); + } + + + private ByteBuffer decodeBase64( final String inputString ) + { + final byte[] inputBytes = Base64.decodeBase64(inputString.getBytes(Consts.ASCII)); + final ByteBuffer buffer = ByteBuffer.wrap( inputBytes ); + return buffer; + } + + + @Override + public boolean isComplete() + { + return state == State.CREDENTIALS_SENT; + } + + /** + * Implementation of the TsRequest structure used in CredSSP protocol. + * It is specified in [MS-CPPS] section 2.2.1. + */ + static class CredSspTsRequest + { + + private static final int VERSION = 3; + + private byte[] negoToken; + private byte[] authInfo; + private byte[] pubKeyAuth; + + + protected CredSspTsRequest() + { + super(); + } + + + public static CredSspTsRequest createNegoToken( final byte[] negoToken ) + { + final CredSspTsRequest req = new CredSspTsRequest(); + req.negoToken = negoToken; + return req; + } + + + public static CredSspTsRequest createAuthInfo( final byte[] authInfo ) + { + final CredSspTsRequest req = new CredSspTsRequest(); + req.authInfo = authInfo; + return req; + } + + + public static CredSspTsRequest createDecoded( final ByteBuffer buf ) throws MalformedChallengeException + { + final CredSspTsRequest req = new CredSspTsRequest(); + req.decode( buf ); + return req; + } + + + public byte[] getNegoToken() + { + return negoToken; + } + + + public void setNegoToken( final byte[] negoToken ) + { + this.negoToken = negoToken; + } + + + public byte[] getAuthInfo() + { + return authInfo; + } + + + public void setAuthInfo( final byte[] authInfo ) + { + this.authInfo = authInfo; + } + + + public byte[] getPubKeyAuth() + { + return pubKeyAuth; + } + + + public void setPubKeyAuth( final byte[] pubKeyAuth ) + { + this.pubKeyAuth = pubKeyAuth; + } + + + public void decode( final ByteBuffer buf ) throws MalformedChallengeException + { + negoToken = null; + authInfo = null; + pubKeyAuth = null; + + getByteAndAssert( buf, 0x30, "initial sequence" ); + parseLength( buf ); + + while ( buf.hasRemaining() ) + { + final int contentTag = getAndAssertContentSpecificTag( buf, "content tag" ); + parseLength( buf ); + switch ( contentTag ) + { + case 0: + processVersion( buf ); + break; + case 1: + parseNegoTokens( buf ); + break; + case 2: + parseAuthInfo( buf ); + break; + case 3: + parsePubKeyAuth( buf ); + break; + case 4: + processErrorCode( buf ); + break; + default: + parseError( buf, "unexpected content tag " + contentTag ); + } + } + } + + + private void processVersion( final ByteBuffer buf ) throws MalformedChallengeException + { + getByteAndAssert( buf, 0x02, "version type" ); + getLengthAndAssert( buf, 1, "version length" ); + getByteAndAssert( buf, VERSION, "wrong protocol version" ); + } + + + private void parseNegoTokens( final ByteBuffer buf ) throws MalformedChallengeException + { + getByteAndAssert( buf, 0x30, "negoTokens sequence" ); + parseLength( buf ); + // I have seen both 0x30LL encoding and 0x30LL0x30LL encoding. Accept both. + byte bufByte = buf.get(); + if ( bufByte == 0x30 ) + { + parseLength( buf ); + bufByte = buf.get(); + } + if ( ( bufByte & 0xff ) != 0xa0 ) + { + parseError( buf, "negoTokens: wrong content-specific tag " + String.format( "%02X", bufByte ) ); + } + parseLength( buf ); + getByteAndAssert( buf, 0x04, "negoToken type" ); + + final int tokenLength = parseLength( buf ); + negoToken = new byte[tokenLength]; + buf.get( negoToken ); + } + + + private void parseAuthInfo( final ByteBuffer buf ) throws MalformedChallengeException + { + getByteAndAssert( buf, 0x04, "authInfo type" ); + final int length = parseLength( buf ); + authInfo = new byte[length]; + buf.get( authInfo ); + } + + + private void parsePubKeyAuth( final ByteBuffer buf ) throws MalformedChallengeException + { + getByteAndAssert( buf, 0x04, "pubKeyAuth type" ); + final int length = parseLength( buf ); + pubKeyAuth = new byte[length]; + buf.get( pubKeyAuth ); + } + + + private void processErrorCode( final ByteBuffer buf ) throws MalformedChallengeException + { + getLengthAndAssert( buf, 3, "error code length" ); + getByteAndAssert( buf, 0x02, "error code type" ); + getLengthAndAssert( buf, 1, "error code length" ); + final byte errorCode = buf.get(); + parseError( buf, "Error code " + errorCode ); + } + + + public void encode( final ByteBuffer buf ) + { + final ByteBuffer inner = ByteBuffer.allocate( buf.capacity() ); + + // version tag [0] + inner.put( ( byte ) ( 0x00 | 0xa0 ) ); + inner.put( ( byte ) 3 ); // length + + inner.put( ( byte ) ( 0x02 ) ); // INTEGER tag + inner.put( ( byte ) 1 ); // length + inner.put( ( byte ) VERSION ); // value + + if ( negoToken != null ) + { + int len = negoToken.length; + final byte[] negoTokenLengthBytes = encodeLength( len ); + len += 1 + negoTokenLengthBytes.length; + final byte[] negoTokenLength1Bytes = encodeLength( len ); + len += 1 + negoTokenLength1Bytes.length; + final byte[] negoTokenLength2Bytes = encodeLength( len ); + len += 1 + negoTokenLength2Bytes.length; + final byte[] negoTokenLength3Bytes = encodeLength( len ); + len += 1 + negoTokenLength3Bytes.length; + final byte[] negoTokenLength4Bytes = encodeLength( len ); + + inner.put( ( byte ) ( 0x01 | 0xa0 ) ); // negoData tag [1] + inner.put( negoTokenLength4Bytes ); // length + + inner.put( ( byte ) ( 0x30 ) ); // SEQUENCE tag + inner.put( negoTokenLength3Bytes ); // length + + inner.put( ( byte ) ( 0x30 ) ); // .. of SEQUENCE tag + inner.put( negoTokenLength2Bytes ); // length + + inner.put( ( byte ) ( 0x00 | 0xa0 ) ); // negoToken tag [0] + inner.put( negoTokenLength1Bytes ); // length + + inner.put( ( byte ) ( 0x04 ) ); // OCTET STRING tag + inner.put( negoTokenLengthBytes ); // length + + inner.put( negoToken ); + } + + if ( authInfo != null ) + { + final byte[] authInfoEncodedLength = encodeLength( authInfo.length ); + + inner.put( ( byte ) ( 0x02 | 0xa0 ) ); // authInfo tag [2] + inner.put( encodeLength( 1 + authInfoEncodedLength.length + authInfo.length ) ); // length + + inner.put( ( byte ) ( 0x04 ) ); // OCTET STRING tag + inner.put( authInfoEncodedLength ); + inner.put( authInfo ); + } + + if ( pubKeyAuth != null ) + { + final byte[] pubKeyAuthEncodedLength = encodeLength( pubKeyAuth.length ); + + inner.put( ( byte ) ( 0x03 | 0xa0 ) ); // pubKeyAuth tag [3] + inner.put( encodeLength( 1 + pubKeyAuthEncodedLength.length + pubKeyAuth.length ) ); // length + + inner.put( ( byte ) ( 0x04 ) ); // OCTET STRING tag + inner.put( pubKeyAuthEncodedLength ); + inner.put( pubKeyAuth ); + } + + inner.flip(); + + // SEQUENCE tag + buf.put( ( byte ) ( 0x10 | 0x20 ) ); + buf.put( encodeLength( inner.limit() ) ); + buf.put( inner ); + } + + + public String debugDump() + { + final StringBuilder sb = new StringBuilder( "TsRequest\n" ); + sb.append( " negoToken:\n" ); + sb.append( " " ); + DebugUtil.dump( sb, negoToken ); + sb.append( "\n" ); + sb.append( " authInfo:\n" ); + sb.append( " " ); + DebugUtil.dump( sb, authInfo ); + sb.append( "\n" ); + sb.append( " pubKeyAuth:\n" ); + sb.append( " " ); + DebugUtil.dump( sb, pubKeyAuth ); + return sb.toString(); + } + + + @Override + public String toString() + { + return "TsRequest(negoToken=" + Arrays.toString( negoToken ) + ", authInfo=" + + Arrays.toString( authInfo ) + ", pubKeyAuth=" + Arrays.toString( pubKeyAuth ) + ")"; + } + } + + static void getByteAndAssert( final ByteBuffer buf, final int expectedValue, final String errorMessage ) + throws MalformedChallengeException + { + final byte bufByte = buf.get(); + if ( bufByte != expectedValue ) + { + parseError( buf, errorMessage + expectMessage( expectedValue, bufByte ) ); + } + } + + private static String expectMessage( final int expectedValue, final int realValue ) + { + return "(expected " + String.format( "%02X", expectedValue ) + ", got " + String.format( "%02X", realValue ) + + ")"; + } + + static int parseLength( final ByteBuffer buf ) + { + byte bufByte = buf.get(); + if ( bufByte == 0x80 ) + { + return -1; // infinite + } + if ( ( bufByte & 0x80 ) == 0x80 ) + { + final int size = bufByte & 0x7f; + int length = 0; + for ( int i = 0; i < size; i++ ) + { + bufByte = buf.get(); + length = ( length << 8 ) + ( bufByte & 0xff ); + } + return length; + } + else + { + return bufByte; + } + } + + static void getLengthAndAssert( final ByteBuffer buf, final int expectedValue, final String errorMessage ) + throws MalformedChallengeException + { + final int bufLength = parseLength( buf ); + if ( expectedValue != bufLength ) + { + parseError( buf, errorMessage + expectMessage( expectedValue, bufLength ) ); + } + } + + static int getAndAssertContentSpecificTag( final ByteBuffer buf, final String errorMessage ) throws MalformedChallengeException + { + final byte bufByte = buf.get(); + if ( ( bufByte & 0xe0 ) != 0xa0 ) + { + parseError( buf, errorMessage + ": wrong content-specific tag " + String.format( "%02X", bufByte ) ); + } + final int tag = bufByte & 0x1f; + return tag; + } + + static void parseError( final ByteBuffer buf, final String errorMessage ) throws MalformedChallengeException + { + throw new MalformedChallengeException( + "Error parsing TsRequest (position:" + buf.position() + "): " + errorMessage ); + } + + static byte[] encodeLength( final int length ) + { + if ( length < 128 ) + { + final byte[] encoded = new byte[1]; + encoded[0] = ( byte ) length; + return encoded; + } + + int size = 1; + + int val = length; + while ( ( val >>>= 8 ) != 0 ) + { + size++; + } + + final byte[] encoded = new byte[1 + size]; + encoded[0] = ( byte ) ( size | 0x80 ); + + int shift = ( size - 1 ) * 8; + for ( int i = 0; i < size; i++ ) + { + encoded[i + 1] = ( byte ) ( length >> shift ); + shift -= 8; + } + + return encoded; + } + +} diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/DebugUtil.java b/httpclient/src/main/java/org/apache/http/impl/auth/DebugUtil.java index 862ab357f..2c8110e29 100644 --- a/httpclient/src/main/java/org/apache/http/impl/auth/DebugUtil.java +++ b/httpclient/src/main/java/org/apache/http/impl/auth/DebugUtil.java @@ -1,96 +1,96 @@ -/* - * ==================================================================== - * 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 - * . - * - */ - -package org.apache.http.impl.auth; - - -import java.nio.ByteBuffer; - - -/** - * Simple debugging utility class for CredSSP and NTLM implementations. - */ -class DebugUtil -{ - - public static String dump( final ByteBuffer buf ) - { - final ByteBuffer dup = buf.duplicate(); - final StringBuilder sb = new StringBuilder( dup.toString() ); - sb.append( ": " ); - while ( dup.position() < dup.limit() ) - { - sb.append( String.format( "%02X ", dup.get() ) ); - } - return sb.toString(); - } - - - public static void dump( final StringBuilder sb, final byte[] bytes ) - { - if ( bytes == null ) - { - sb.append( "null" ); - return; - } - for ( byte b : bytes ) - { - sb.append( String.format( "%02X ", b ) ); - } - } - - - public static String dump( final byte[] bytes ) - { - final StringBuilder sb = new StringBuilder(); - dump( sb, bytes ); - return sb.toString(); - } - - - public static byte[] fromHex( final String hex ) - { - int i = 0; - final byte[] bytes = new byte[200000]; - int h = 0; - while ( h < hex.length() ) - { - if ( hex.charAt( h ) == ' ' ) - { - h++; - } - final String str = hex.substring( h, h + 2 ); - bytes[i] = ( byte ) Integer.parseInt( str, 16 ); - i++; - h = h + 2; - } - final byte[] outbytes = new byte[i]; - System.arraycopy( bytes, 0, outbytes, 0, i ); - return outbytes; - } - -} +/* + * ==================================================================== + * 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 + * . + * + */ + +package org.apache.http.impl.auth; + + +import java.nio.ByteBuffer; + + +/** + * Simple debugging utility class for CredSSP and NTLM implementations. + */ +class DebugUtil +{ + + public static String dump( final ByteBuffer buf ) + { + final ByteBuffer dup = buf.duplicate(); + final StringBuilder sb = new StringBuilder( dup.toString() ); + sb.append( ": " ); + while ( dup.position() < dup.limit() ) + { + sb.append( String.format( "%02X ", dup.get() ) ); + } + return sb.toString(); + } + + + public static void dump( final StringBuilder sb, final byte[] bytes ) + { + if ( bytes == null ) + { + sb.append( "null" ); + return; + } + for ( final byte b : bytes ) + { + sb.append( String.format( "%02X ", b ) ); + } + } + + + public static String dump( final byte[] bytes ) + { + final StringBuilder sb = new StringBuilder(); + dump( sb, bytes ); + return sb.toString(); + } + + + public static byte[] fromHex( final String hex ) + { + int i = 0; + final byte[] bytes = new byte[200000]; + int h = 0; + while ( h < hex.length() ) + { + if ( hex.charAt( h ) == ' ' ) + { + h++; + } + final String str = hex.substring( h, h + 2 ); + bytes[i] = ( byte ) Integer.parseInt( str, 16 ); + i++; + h = h + 2; + } + final byte[] outbytes = new byte[i]; + System.arraycopy( bytes, 0, outbytes, 0, i ); + return outbytes; + } + +} diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java b/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java index 70cbe50d4..a8d14723c 100644 --- a/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java +++ b/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java @@ -821,7 +821,7 @@ final class NTLMEngineImpl implements NTLMEngine { private Cipher initCipher() throws NTLMEngineException { - Cipher cipher; + final Cipher cipher; try { cipher = Cipher.getInstance( "RC4" ); @@ -834,7 +834,7 @@ final class NTLMEngineImpl implements NTLMEngine { cipher.init( Cipher.DECRYPT_MODE, new SecretKeySpec( sealingKey, "RC4" ) ); } } - catch ( Exception e ) + catch ( final Exception e ) { throw new NTLMEngineException( e.getMessage(), e ); } @@ -1779,7 +1779,7 @@ final class NTLMEngineImpl implements NTLMEngine { writeUShort( newTargetInfo, MSV_AV_CHANNEL_BINDINGS, appendLength + 8 ); writeUShort( newTargetInfo, 16, appendLength + 10 ); - byte[] channelBindingsHash; + final byte[] channelBindingsHash; try { final byte[] certBytes = peerServerCertificate.getEncoded(); @@ -1795,11 +1795,11 @@ final class NTLMEngineImpl implements NTLMEngine { final MessageDigest md5 = getMD5(); channelBindingsHash = md5.digest( channelBindingStruct ); } - catch ( CertificateEncodingException e ) + catch ( final CertificateEncodingException e ) { throw new NTLMEngineException( e.getMessage(), e ); } - catch ( NoSuchAlgorithmException e ) + catch ( final NoSuchAlgorithmException e ) { throw new NTLMEngineException( e.getMessage(), e ); } @@ -1841,7 +1841,7 @@ final class NTLMEngineImpl implements NTLMEngine { static MessageDigest getMD5() { try { return MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException ex) { + } catch (final NoSuchAlgorithmException ex) { throw new RuntimeException("MD5 message digest doesn't seem to exist - fatal error: "+ex.getMessage(), ex); } } diff --git a/httpclient/src/main/java/org/apache/http/impl/client/HttpClientBuilder.java b/httpclient/src/main/java/org/apache/http/impl/client/HttpClientBuilder.java index 4806e387f..68259f3c7 100644 --- a/httpclient/src/main/java/org/apache/http/impl/client/HttpClientBuilder.java +++ b/httpclient/src/main/java/org/apache/http/impl/client/HttpClientBuilder.java @@ -1216,7 +1216,7 @@ public class HttpClientBuilder { connectionEvictor.shutdown(); try { connectionEvictor.awaitTermination(1L, TimeUnit.SECONDS); - } catch (InterruptedException interrupted) { + } catch (final InterruptedException interrupted) { Thread.currentThread().interrupt(); } } diff --git a/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2109Spec.java b/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2109Spec.java index cee6d965f..dbc205e0a 100644 --- a/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2109Spec.java +++ b/httpclient/src/main/java/org/apache/http/impl/cookie/RFC2109Spec.java @@ -135,7 +135,7 @@ public class RFC2109Spec extends CookieSpecBase { @Override public List
formatCookies(final List cookies) { Args.notEmpty(cookies, "List of cookies"); - List cookieList; + final List cookieList; if (cookies.size() > 1) { // Create a mutable copy and sort the copy. cookieList = new ArrayList(cookies); diff --git a/httpclient/src/test/java/org/apache/http/conn/ssl/TestHostnameVerifier.java b/httpclient/src/test/java/org/apache/http/conn/ssl/TestHostnameVerifier.java index 657f8df93..d04b7cab3 100644 --- a/httpclient/src/test/java/org/apache/http/conn/ssl/TestHostnameVerifier.java +++ b/httpclient/src/test/java/org/apache/http/conn/ssl/TestHostnameVerifier.java @@ -316,7 +316,7 @@ public class TestHostnameVerifier { @Test public void testHTTPCLIENT_1097() { - String cns[]; + final String cns[]; final String alt[] = {}; final X509HostnameVerifier bhv = new BrowserCompatHostnameVerifier(); final X509HostnameVerifier shv = new StrictHostnameVerifier(); diff --git a/httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java b/httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java index 8af33b622..3bd3a825b 100644 --- a/httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java +++ b/httpclient/src/test/java/org/apache/http/impl/auth/TestNTLMEngineImpl.java @@ -76,7 +76,7 @@ public class TestNTLMEngineImpl { /* Test suite MD4 helper */ static void checkMD4(final String input, final String hexOutput) throws Exception { - NTLMEngineImpl.MD4 md4; + final NTLMEngineImpl.MD4 md4; md4 = new NTLMEngineImpl.MD4(); md4.update(input.getBytes(Consts.ASCII)); final byte[] answer = md4.getOutput(); diff --git a/httpclient/src/test/java/org/apache/http/localserver/EchoHandler.java b/httpclient/src/test/java/org/apache/http/localserver/EchoHandler.java index 380607e40..298e77b60 100644 --- a/httpclient/src/test/java/org/apache/http/localserver/EchoHandler.java +++ b/httpclient/src/test/java/org/apache/http/localserver/EchoHandler.java @@ -89,7 +89,7 @@ public class EchoHandler // For some reason, just putting the incoming entity into // the response will not work. We have to buffer the message. - byte[] data; + final byte[] data; if (entity == null) { data = new byte [0]; } else {