You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by gg...@apache.org on 2017/07/13 00:29:21 UTC
[2/3] httpcomponents-client git commit: Checkstyle fixes: Use final
and tab police.
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/61529d1f/httpclient/src/main/java/org/apache/http/impl/auth/CredSspScheme.java
----------------------------------------------------------------------
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 f8db658..9cc5fc8 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
- * <http://www.apache.org/>.
- *
- */
-
-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;
-
-/**
- * <p>
- * Client implementation of the CredSSP protocol specified in [MS-CSSP].
- * </p>
- * <p>
- * 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.
- * </p>
- * <p>
- * 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.
- * </p>
- */
-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
+ * <http://www.apache.org/>.
+ *
+ */
+
+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;
+
+/**
+ * <p>
+ * Client implementation of the CredSSP protocol specified in [MS-CSSP].
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ */
+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;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/61529d1f/httpclient/src/main/java/org/apache/http/impl/auth/DebugUtil.java
----------------------------------------------------------------------
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 862ab35..2c8110e 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
- * <http://www.apache.org/>.
- *
- */
-
-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
+ * <http://www.apache.org/>.
+ *
+ */
+
+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;
+ }
+
+}