diff --git a/httpclient-win/src/main/java/org/apache/http/impl/auth/win/WindowsNegotiateScheme.java b/httpclient-win/src/main/java/org/apache/http/impl/auth/win/WindowsNegotiateScheme.java
index b07c75ec5..333458bd5 100644
--- a/httpclient-win/src/main/java/org/apache/http/impl/auth/win/WindowsNegotiateScheme.java
+++ b/httpclient-win/src/main/java/org/apache/http/impl/auth/win/WindowsNegotiateScheme.java
@@ -252,7 +252,8 @@ public class WindowsNegotiateScheme extends AuthSchemeBase {
}
} else {
final HttpClientContext clientContext = HttpClientContext.adapt(context);
- final HttpHost target = clientContext.getTargetHost(); if (target != null) {
+ final HttpHost target = clientContext.getTargetHost();
+ if (target != null) {
spn = "HTTP/" + target.getHostName();
} else {
final RouteInfo route = clientContext.getHttpRoute();
diff --git a/httpclient/src/main/java/org/apache/http/client/config/AuthSchemes.java b/httpclient/src/main/java/org/apache/http/client/config/AuthSchemes.java
index da7e26881..f3a0e1baf 100644
--- a/httpclient/src/main/java/org/apache/http/client/config/AuthSchemes.java
+++ b/httpclient/src/main/java/org/apache/http/client/config/AuthSchemes.java
@@ -65,6 +65,11 @@ public final class AuthSchemes {
*/
public static final String KERBEROS = "Kerberos";
+ /**
+ * CredSSP authentication scheme defined in [MS-CSSP].
+ */
+ public static final String CREDSSP = "CredSSP";
+
private AuthSchemes() {
}
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
new file mode 100644
index 000000000..f8db658c9
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/CredSspScheme.java
@@ -0,0 +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;
+ }
+
+}
diff --git a/httpclient/src/main/java/org/apache/http/impl/auth/CredSspSchemeFactory.java b/httpclient/src/main/java/org/apache/http/impl/auth/CredSspSchemeFactory.java
new file mode 100644
index 000000000..309101bde
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/CredSspSchemeFactory.java
@@ -0,0 +1,44 @@
+/*
+ * ====================================================================
+ * 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 org.apache.http.auth.AuthScheme;
+import org.apache.http.auth.AuthSchemeProvider;
+import org.apache.http.protocol.HttpContext;
+
+
+public class CredSspSchemeFactory implements AuthSchemeProvider
+{
+
+ @Override
+ public AuthScheme create( final HttpContext context )
+ {
+ return new CredSspScheme();
+ }
+}
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
new file mode 100644
index 000000000..862ab357f
--- /dev/null
+++ b/httpclient/src/main/java/org/apache/http/impl/auth/DebugUtil.java
@@ -0,0 +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;
+ }
+
+}
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 1a7dc8b57..216f43f45 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
@@ -26,20 +26,21 @@
*/
package org.apache.http.impl.auth;
-import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
+import org.apache.http.Consts;
import java.security.Key;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Locale;
+import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.Certificate;
import org.apache.commons.codec.binary.Base64;
-import org.apache.http.Consts;
-import org.apache.http.util.CharsetUtils;
-import org.apache.http.util.EncodingUtils;
/**
* Provides an implementation for NTLMv1, NTLMv2, and NTLM2 Session forms of the NTLM
@@ -50,7 +51,7 @@ import org.apache.http.util.EncodingUtils;
final class NTLMEngineImpl implements NTLMEngine {
/** Unicode encoding */
- private static final Charset UNICODE_LITTLE_UNMARKED = CharsetUtils.lookup("UnicodeLittleUnmarked");
+ private static final Charset UNICODE_LITTLE_UNMARKED = Charset.forName("UnicodeLittleUnmarked");
/** Character encoding */
private static final Charset DEFAULT_CHARSET = Consts.ASCII;
@@ -58,22 +59,41 @@ final class NTLMEngineImpl implements NTLMEngine {
// http://davenport.sourceforge.net/ntlm.html
// and
// http://msdn.microsoft.com/en-us/library/cc236650%28v=prot.20%29.aspx
- protected static final int FLAG_REQUEST_UNICODE_ENCODING = 0x00000001; // Unicode string encoding requested
- protected static final int FLAG_REQUEST_TARGET = 0x00000004; // Requests target field
- protected static final int FLAG_REQUEST_SIGN = 0x00000010; // Requests all messages have a signature attached, in NEGOTIATE message.
- protected static final int FLAG_REQUEST_SEAL = 0x00000020; // Request key exchange for message confidentiality in NEGOTIATE message. MUST be used in conjunction with 56BIT.
- protected static final int FLAG_REQUEST_LAN_MANAGER_KEY = 0x00000080; // Request Lan Manager key instead of user session key
- protected static final int FLAG_REQUEST_NTLMv1 = 0x00000200; // Request NTLMv1 security. MUST be set in NEGOTIATE and CHALLENGE both
- protected static final int FLAG_DOMAIN_PRESENT = 0x00001000; // Domain is present in message
- protected static final int FLAG_WORKSTATION_PRESENT = 0x00002000; // Workstation is present in message
- protected static final int FLAG_REQUEST_ALWAYS_SIGN = 0x00008000; // Requests a signature block on all messages. Overridden by REQUEST_SIGN and REQUEST_SEAL.
- protected static final int FLAG_REQUEST_NTLM2_SESSION = 0x00080000; // From server in challenge, requesting NTLM2 session security
- protected static final int FLAG_REQUEST_VERSION = 0x02000000; // Request protocol version
- protected static final int FLAG_TARGETINFO_PRESENT = 0x00800000; // From server in challenge message, indicating targetinfo is present
- protected static final int FLAG_REQUEST_128BIT_KEY_EXCH = 0x20000000; // Request explicit 128-bit key exchange
- protected static final int FLAG_REQUEST_EXPLICIT_KEY_EXCH = 0x40000000; // Request explicit key exchange
- protected static final int FLAG_REQUEST_56BIT_ENCRYPTION = 0x80000000; // Must be used in conjunction with SEAL
+ // [MS-NLMP] section 2.2.2.5
+ static final int FLAG_REQUEST_UNICODE_ENCODING = 0x00000001; // Unicode string encoding requested
+ static final int FLAG_REQUEST_OEM_ENCODING = 0x00000002; // OEM string encoding requested
+ static final int FLAG_REQUEST_TARGET = 0x00000004; // Requests target field
+ static final int FLAG_REQUEST_SIGN = 0x00000010; // Requests all messages have a signature attached, in NEGOTIATE message.
+ static final int FLAG_REQUEST_SEAL = 0x00000020; // Request key exchange for message confidentiality in NEGOTIATE message. MUST be used in conjunction with 56BIT.
+ static final int FLAG_REQUEST_LAN_MANAGER_KEY = 0x00000080; // Request Lan Manager key instead of user session key
+ static final int FLAG_REQUEST_NTLMv1 = 0x00000200; // Request NTLMv1 security. MUST be set in NEGOTIATE and CHALLENGE both
+ static final int FLAG_DOMAIN_PRESENT = 0x00001000; // Domain is present in message
+ static final int FLAG_WORKSTATION_PRESENT = 0x00002000; // Workstation is present in message
+ static final int FLAG_REQUEST_ALWAYS_SIGN = 0x00008000; // Requests a signature block on all messages. Overridden by REQUEST_SIGN and REQUEST_SEAL.
+ static final int FLAG_REQUEST_NTLM2_SESSION = 0x00080000; // From server in challenge, requesting NTLM2 session security
+ static final int FLAG_REQUEST_VERSION = 0x02000000; // Request protocol version
+ static final int FLAG_TARGETINFO_PRESENT = 0x00800000; // From server in challenge message, indicating targetinfo is present
+ static final int FLAG_REQUEST_128BIT_KEY_EXCH = 0x20000000; // Request explicit 128-bit key exchange
+ static final int FLAG_REQUEST_EXPLICIT_KEY_EXCH = 0x40000000; // Request explicit key exchange
+ static final int FLAG_REQUEST_56BIT_ENCRYPTION = 0x80000000; // Must be used in conjunction with SEAL
+ // Attribute-value identifiers (AvId)
+ // according to [MS-NLMP] section 2.2.2.1
+ static final int MSV_AV_EOL = 0x0000; // Indicates that this is the last AV_PAIR in the list.
+ static final int MSV_AV_NB_COMPUTER_NAME = 0x0001; // The server's NetBIOS computer name.
+ static final int MSV_AV_NB_DOMAIN_NAME = 0x0002; // The server's NetBIOS domain name.
+ static final int MSV_AV_DNS_COMPUTER_NAME = 0x0003; // The fully qualified domain name (FQDN) of the computer.
+ static final int MSV_AV_DNS_DOMAIN_NAME = 0x0004; // The FQDN of the domain.
+ static final int MSV_AV_DNS_TREE_NAME = 0x0005; // The FQDN of the forest.
+ static final int MSV_AV_FLAGS = 0x0006; // A 32-bit value indicating server or client configuration.
+ static final int MSV_AV_TIMESTAMP = 0x0007; // server local time
+ static final int MSV_AV_SINGLE_HOST = 0x0008; // A Single_Host_Data structure.
+ static final int MSV_AV_TARGET_NAME = 0x0009; // The SPN of the target server.
+ static final int MSV_AV_CHANNEL_BINDINGS = 0x000A; // A channel bindings hash.
+
+ static final int MSV_AV_FLAGS_ACCOUNT_AUTH_CONSTAINED = 0x00000001; // Indicates to the client that the account authentication is constrained.
+ static final int MSV_AV_FLAGS_MIC = 0x00000002; // Indicates that the client is providing message integrity in the MIC field in the AUTHENTICATE_MESSAGE.
+ static final int MSV_AV_FLAGS_UNTRUSTED_TARGET_SPN = 0x00000004; // Indicates that the client is providing a target SPN generated from an untrusted source.
/** Secure random generator */
private static final java.security.SecureRandom RND_GEN;
@@ -87,46 +107,34 @@ final class NTLMEngineImpl implements NTLMEngine {
}
/** The signature string as bytes in the default encoding */
- private static final byte[] SIGNATURE;
+ private static final byte[] SIGNATURE = getNullTerminatedAsciiString("NTLMSSP");
- static {
- final byte[] bytesWithoutNull = "NTLMSSP".getBytes(Consts.ASCII);
- SIGNATURE = new byte[bytesWithoutNull.length + 1];
- System.arraycopy(bytesWithoutNull, 0, SIGNATURE, 0, bytesWithoutNull.length);
- SIGNATURE[bytesWithoutNull.length] = (byte) 0x00;
+ // Key derivation magic strings for the SIGNKEY algorithm defined in
+ // [MS-NLMP] section 3.4.5.2
+ private static final byte[] SIGN_MAGIC_SERVER = getNullTerminatedAsciiString(
+ "session key to server-to-client signing key magic constant");
+ private static final byte[] SIGN_MAGIC_CLIENT = getNullTerminatedAsciiString(
+ "session key to client-to-server signing key magic constant");
+ private static final byte[] SEAL_MAGIC_SERVER = getNullTerminatedAsciiString(
+ "session key to server-to-client sealing key magic constant");
+ private static final byte[] SEAL_MAGIC_CLIENT = getNullTerminatedAsciiString(
+ "session key to client-to-server sealing key magic constant");
+
+ // prefix for GSS API channel binding
+ private static final byte[] MAGIC_TLS_SERVER_ENDPOINT = "tls-server-end-point:".getBytes(Consts.ASCII);
+
+ private static byte[] getNullTerminatedAsciiString( final String source )
+ {
+ final byte[] bytesWithoutNull = source.getBytes(Consts.ASCII);
+ final byte[] target = new byte[bytesWithoutNull.length + 1];
+ System.arraycopy(bytesWithoutNull, 0, target, 0, bytesWithoutNull.length);
+ target[bytesWithoutNull.length] = (byte) 0x00;
+ return target;
}
private static final String TYPE_1_MESSAGE = new Type1Message().getResponse();
- /**
- * Returns the response for the given message.
- *
- * @param message
- * the message that was received from the server.
- * @param username
- * the username to authenticate with.
- * @param password
- * the password to authenticate with.
- * @param host
- * The host.
- * @param domain
- * the NT domain to authenticate in.
- * @return The response.
- * @throws org.apache.http.HttpException
- * If the messages cannot be retrieved.
- */
- static String getResponseFor(final String message, final String username, final String password,
- final String host, final String domain) throws NTLMEngineException {
-
- final String response;
- if (message == null || message.trim().equals("")) {
- response = getType1Message(host, domain);
- } else {
- final Type2Message t2m = new Type2Message(message);
- response = getType3Message(username, password, host, domain, t2m.getChallenge(), t2m
- .getFlags(), t2m.getTarget(), t2m.getTargetInfo());
- }
- return response;
+ NTLMEngineImpl() {
}
/**
@@ -140,7 +148,7 @@ final class NTLMEngineImpl implements NTLMEngine {
* The domain to authenticate with.
* @return String the message to add to the HTTP request header.
*/
- static String getType1Message(final String host, final String domain) throws NTLMEngineException {
+ static String getType1Message(final String host, final String domain) {
// For compatibility reason do not include domain and host in type 1 message
//return new Type1Message(domain, host).getResponse();
return TYPE_1_MESSAGE;
@@ -173,26 +181,32 @@ final class NTLMEngineImpl implements NTLMEngine {
targetInformation).getResponse();
}
- /** Strip dot suffix from a name */
- private static String stripDotSuffix(final String value) {
- if (value == null) {
- return null;
- }
- final int index = value.indexOf(".");
- if (index != -1) {
- return value.substring(0, index);
- }
- return value;
- }
-
- /** Convert host to standard form */
- private static String convertHost(final String host) {
- return stripDotSuffix(host);
- }
-
- /** Convert domain to standard form */
- private static String convertDomain(final String domain) {
- return stripDotSuffix(domain);
+ /**
+ * Creates the type 3 message using the given server nonce. The type 3
+ * message includes all the information for authentication, host, domain,
+ * username and the result of encrypting the nonce sent by the server using
+ * the user's password as the key.
+ *
+ * @param user
+ * The user name. This should not include the domain name.
+ * @param password
+ * The password.
+ * @param host
+ * The host that is originating the authentication request.
+ * @param domain
+ * The domain to authenticate within.
+ * @param nonce
+ * the 8 byte array the server sent.
+ * @return The type 3 message.
+ * @throws NTLMEngineException
+ * If {@encrypt(byte[],byte[])} fails.
+ */
+ static String getType3Message(final String user, final String password, final String host, final String domain,
+ final byte[] nonce, final int type2Flags, final String target, final byte[] targetInformation,
+ final Certificate peerServerCertificate, final byte[] type1Message, final byte[] type2Message)
+ throws NTLMEngineException {
+ return new Type3Message(domain, host, user, password, nonce, type2Flags, target,
+ targetInformation, peerServerCertificate, type1Message, type2Message).getResponse();
}
private static int readULong(final byte[] src, final int index) throws NTLMEngineException {
@@ -223,31 +237,28 @@ final class NTLMEngineImpl implements NTLMEngine {
}
/** Calculate a challenge block */
- private static byte[] makeRandomChallenge() throws NTLMEngineException {
- if (RND_GEN == null) {
- throw new NTLMEngineException("Random generator not available");
- }
+ private static byte[] makeRandomChallenge(final Random random) throws NTLMEngineException {
final byte[] rval = new byte[8];
- synchronized (RND_GEN) {
- RND_GEN.nextBytes(rval);
+ synchronized (random) {
+ random.nextBytes(rval);
}
return rval;
}
/** Calculate a 16-byte secondary key */
- private static byte[] makeSecondaryKey() throws NTLMEngineException {
- if (RND_GEN == null) {
- throw new NTLMEngineException("Random generator not available");
- }
+ private static byte[] makeSecondaryKey(final Random random) throws NTLMEngineException {
final byte[] rval = new byte[16];
- synchronized (RND_GEN) {
- RND_GEN.nextBytes(rval);
+ synchronized (random) {
+ random.nextBytes(rval);
}
return rval;
}
protected static class CipherGen {
+ protected final Random random;
+ protected final long currentTime;
+
protected final String domain;
protected final String user;
protected final String password;
@@ -279,10 +290,25 @@ final class NTLMEngineImpl implements NTLMEngine {
protected byte[] ntlm2SessionResponseUserSessionKey = null;
protected byte[] lanManagerSessionKey = null;
+ @Deprecated
public CipherGen(final String domain, final String user, final String password,
final byte[] challenge, final String target, final byte[] targetInformation,
final byte[] clientChallenge, final byte[] clientChallenge2,
final byte[] secondaryKey, final byte[] timestamp) {
+ this(RND_GEN, System.currentTimeMillis(),
+ domain, user, password, challenge, target, targetInformation,
+ clientChallenge, clientChallenge2,
+ secondaryKey, timestamp);
+ }
+
+ public CipherGen(final Random random, final long currentTime,
+ final String domain, final String user, final String password,
+ final byte[] challenge, final String target, final byte[] targetInformation,
+ final byte[] clientChallenge, final byte[] clientChallenge2,
+ final byte[] secondaryKey, final byte[] timestamp) {
+ this.random = random;
+ this.currentTime = currentTime;
+
this.domain = domain;
this.target = target;
this.user = user;
@@ -295,16 +321,31 @@ final class NTLMEngineImpl implements NTLMEngine {
this.timestamp = timestamp;
}
- public CipherGen(final String domain, final String user, final String password,
- final byte[] challenge, final String target, final byte[] targetInformation) {
- this(domain, user, password, challenge, target, targetInformation, null, null, null, null);
+ @Deprecated
+ public CipherGen(final String domain,
+ final String user,
+ final String password,
+ final byte[] challenge,
+ final String target,
+ final byte[] targetInformation) {
+ this(RND_GEN, System.currentTimeMillis(), domain, user, password, challenge, target, targetInformation);
+ }
+
+ public CipherGen(final Random random, final long currentTime,
+ final String domain,
+ final String user,
+ final String password,
+ final byte[] challenge,
+ final String target,
+ final byte[] targetInformation) {
+ this(random, currentTime, domain, user, password, challenge, target, targetInformation, null, null, null, null);
}
/** Calculate and return client challenge */
public byte[] getClientChallenge()
throws NTLMEngineException {
if (clientChallenge == null) {
- clientChallenge = makeRandomChallenge();
+ clientChallenge = makeRandomChallenge(random);
}
return clientChallenge;
}
@@ -313,7 +354,7 @@ final class NTLMEngineImpl implements NTLMEngine {
public byte[] getClientChallenge2()
throws NTLMEngineException {
if (clientChallenge2 == null) {
- clientChallenge2 = makeRandomChallenge();
+ clientChallenge2 = makeRandomChallenge(random);
}
return clientChallenge2;
}
@@ -322,7 +363,7 @@ final class NTLMEngineImpl implements NTLMEngine {
public byte[] getSecondaryKey()
throws NTLMEngineException {
if (secondaryKey == null) {
- secondaryKey = makeSecondaryKey();
+ secondaryKey = makeSecondaryKey(random);
}
return secondaryKey;
}
@@ -384,7 +425,7 @@ final class NTLMEngineImpl implements NTLMEngine {
/** Calculate a timestamp */
public byte[] getTimestamp() {
if (timestamp == null) {
- long time = System.currentTimeMillis();
+ long time = this.currentTime;
time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch.
time *= 10000; // tenths of a microsecond.
// convert to little-endian byte array.
@@ -552,7 +593,7 @@ final class NTLMEngineImpl implements NTLMEngine {
static byte[] ntlm2SessionResponse(final byte[] ntlmHash, final byte[] challenge,
final byte[] clientChallenge) throws NTLMEngineException {
try {
- final MessageDigest md5 = MessageDigest.getInstance("MD5");
+ final MessageDigest md5 = getMD5();
md5.update(challenge);
md5.update(clientChallenge);
final byte[] digest = md5.digest();
@@ -719,6 +760,191 @@ final class NTLMEngineImpl implements NTLMEngine {
return lmv2Response;
}
+ static enum Mode
+ {
+ CLIENT, SERVER;
+ }
+
+ static class Handle
+ {
+ final private byte[] exportedSessionKey;
+ private byte[] signingKey;
+ private byte[] sealingKey;
+ private Cipher rc4;
+ final Mode mode;
+ final private boolean isConnection;
+ int sequenceNumber = 0;
+
+
+ Handle( final byte[] exportedSessionKey, final Mode mode, final boolean isConnection )
+ throws NTLMEngineException
+ {
+ this.exportedSessionKey = exportedSessionKey;
+ this.isConnection = isConnection;
+ this.mode = mode;
+ try
+ {
+ final MessageDigest signMd5 = getMD5();
+ final MessageDigest sealMd5 = getMD5();
+ signMd5.update( exportedSessionKey );
+ sealMd5.update( exportedSessionKey );
+ if ( mode == Mode.CLIENT )
+ {
+ signMd5.update( SIGN_MAGIC_CLIENT );
+ sealMd5.update( SEAL_MAGIC_CLIENT );
+ }
+ else
+ {
+ signMd5.update( SIGN_MAGIC_SERVER );
+ sealMd5.update( SEAL_MAGIC_SERVER );
+ }
+ signingKey = signMd5.digest();
+ sealingKey = sealMd5.digest();
+ }
+ catch ( final Exception e )
+ {
+ throw new NTLMEngineException( e.getMessage(), e );
+ }
+ rc4 = initCipher();
+ }
+
+ public byte[] getSigningKey()
+ {
+ return signingKey;
+ }
+
+
+ public byte[] getSealingKey()
+ {
+ return sealingKey;
+ }
+
+ private Cipher initCipher() throws NTLMEngineException
+ {
+ Cipher cipher;
+ try
+ {
+ cipher = Cipher.getInstance( "RC4" );
+ if ( mode == Mode.CLIENT )
+ {
+ cipher.init( Cipher.ENCRYPT_MODE, new SecretKeySpec( sealingKey, "RC4" ) );
+ }
+ else
+ {
+ cipher.init( Cipher.DECRYPT_MODE, new SecretKeySpec( sealingKey, "RC4" ) );
+ }
+ }
+ catch ( Exception e )
+ {
+ throw new NTLMEngineException( e.getMessage(), e );
+ }
+ return cipher;
+ }
+
+
+ private void advanceMessageSequence() throws NTLMEngineException
+ {
+ if ( !isConnection )
+ {
+ final MessageDigest sealMd5 = getMD5();
+ sealMd5.update( sealingKey );
+ final byte[] seqNumBytes = new byte[4];
+ writeULong( seqNumBytes, sequenceNumber, 0 );
+ sealMd5.update( seqNumBytes );
+ sealingKey = sealMd5.digest();
+ initCipher();
+ }
+ sequenceNumber++;
+ }
+
+ private byte[] encrypt( final byte[] data ) throws NTLMEngineException
+ {
+ return rc4.update( data );
+ }
+
+ private byte[] decrypt( final byte[] data ) throws NTLMEngineException
+ {
+ return rc4.update( data );
+ }
+
+ private byte[] computeSignature( final byte[] message ) throws NTLMEngineException
+ {
+ final byte[] sig = new byte[16];
+
+ // version
+ sig[0] = 0x01;
+ sig[1] = 0x00;
+ sig[2] = 0x00;
+ sig[3] = 0x00;
+
+ // HMAC (first 8 bytes)
+ final HMACMD5 hmacMD5 = new HMACMD5( signingKey );
+ hmacMD5.update( encodeLong( sequenceNumber ) );
+ hmacMD5.update( message );
+ final byte[] hmac = hmacMD5.getOutput();
+ final byte[] trimmedHmac = new byte[8];
+ System.arraycopy( hmac, 0, trimmedHmac, 0, 8 );
+ final byte[] encryptedHmac = encrypt( trimmedHmac );
+ System.arraycopy( encryptedHmac, 0, sig, 4, 8 );
+
+ // sequence number
+ encodeLong( sig, 12, sequenceNumber );
+
+ return sig;
+ }
+
+ private boolean validateSignature( final byte[] signature, final byte message[] ) throws NTLMEngineException
+ {
+ final byte[] computedSignature = computeSignature( message );
+ // log.info( "SSSSS validateSignature("+seqNumber+")\n"
+ // + " received: " + DebugUtil.dump( signature ) + "\n"
+ // + " computed: " + DebugUtil.dump( computedSignature ) );
+ return Arrays.equals( signature, computedSignature );
+ }
+
+ public byte[] signAndEncryptMessage( final byte[] cleartextMessage ) throws NTLMEngineException
+ {
+ final byte[] encryptedMessage = encrypt( cleartextMessage );
+ final byte[] signature = computeSignature( cleartextMessage );
+ final byte[] outMessage = new byte[signature.length + encryptedMessage.length];
+ System.arraycopy( signature, 0, outMessage, 0, signature.length );
+ System.arraycopy( encryptedMessage, 0, outMessage, signature.length, encryptedMessage.length );
+ advanceMessageSequence();
+ return outMessage;
+ }
+
+ public byte[] decryptAndVerifySignedMessage( final byte[] inMessage ) throws NTLMEngineException
+ {
+ final byte[] signature = new byte[16];
+ System.arraycopy( inMessage, 0, signature, 0, signature.length );
+ final byte[] encryptedMessage = new byte[inMessage.length - 16];
+ System.arraycopy( inMessage, 16, encryptedMessage, 0, encryptedMessage.length );
+ final byte[] cleartextMessage = decrypt( encryptedMessage );
+ if ( !validateSignature( signature, cleartextMessage ) )
+ {
+ throw new NTLMEngineException( "Wrong signature" );
+ }
+ advanceMessageSequence();
+ return cleartextMessage;
+ }
+
+ }
+
+ private static byte[] encodeLong( final int value )
+ {
+ final byte[] enc = new byte[4];
+ encodeLong( enc, 0, value );
+ return enc;
+ }
+
+ private static void encodeLong( final byte[] buf, final int offset, final int value )
+ {
+ buf[offset + 0] = ( byte ) ( value & 0xff );
+ buf[offset + 1] = ( byte ) ( value >> 8 & 0xff );
+ buf[offset + 2] = ( byte ) ( value >> 16 & 0xff );
+ buf[offset + 3] = ( byte ) ( value >> 24 & 0xff );
+ }
+
/**
* Creates the NTLMv2 blob from the given target information block and
* client challenge.
@@ -802,21 +1028,65 @@ final class NTLMEngineImpl implements NTLMEngine {
}
}
+ /**
+ * Find the character set based on the flags.
+ * @param flags is the flags.
+ * @return the character set.
+ */
+ private static Charset getCharset(final int flags) throws NTLMEngineException
+ {
+ if ((flags & FLAG_REQUEST_UNICODE_ENCODING) == 0) {
+ return DEFAULT_CHARSET;
+ } else {
+ if (UNICODE_LITTLE_UNMARKED == null) {
+ throw new NTLMEngineException( "Unicode not supported" );
+ }
+ return UNICODE_LITTLE_UNMARKED;
+ }
+ }
+
+ /** Strip dot suffix from a name */
+ private static String stripDotSuffix(final String value) {
+ if (value == null) {
+ return null;
+ }
+ final int index = value.indexOf(".");
+ if (index != -1) {
+ return value.substring(0, index);
+ }
+ return value;
+ }
+
+ /** Convert host to standard form */
+ private static String convertHost(final String host) {
+ return stripDotSuffix(host);
+ }
+
+ /** Convert domain to standard form */
+ private static String convertDomain(final String domain) {
+ return stripDotSuffix(domain);
+ }
+
/** NTLM message generation, base class */
static class NTLMMessage {
/** The current response */
- private byte[] messageContents = null;
+ protected byte[] messageContents = null;
/** The current output position */
- private int currentOutputPosition = 0;
+ protected int currentOutputPosition = 0;
/** Constructor to use when message contents are not yet known */
NTLMMessage() {
}
- /** Constructor to use when message contents are known */
+ /** Constructor taking a string */
NTLMMessage(final String messageBody, final int expectedType) throws NTLMEngineException {
- messageContents = Base64.decodeBase64(messageBody.getBytes(DEFAULT_CHARSET));
+ this(Base64.decodeBase64(messageBody.getBytes(DEFAULT_CHARSET)), expectedType);
+ }
+
+ /** Constructor to use when message bytes are known */
+ NTLMMessage(final byte[] message, final int expectedType) throws NTLMEngineException {
+ messageContents = message;
// Look for NTLM message
if (messageContents.length < SIGNATURE.length) {
throw new NTLMEngineException("NTLM message decoding error - packet too short");
@@ -888,7 +1158,7 @@ final class NTLMEngineImpl implements NTLMEngine {
* Prepares the object to create a response of the given length.
*
* @param maxlength
- * the maximum length of the response to prepare, not
+ * the maximum length of the response to prepare,
* including the type and the signature (which this method
* adds).
*/
@@ -946,18 +1216,26 @@ final class NTLMEngineImpl implements NTLMEngine {
*
* @return The response as above.
*/
- String getResponse() {
- final byte[] resp;
- if (messageContents.length > currentOutputPosition) {
- final byte[] tmp = new byte[currentOutputPosition];
- System.arraycopy(messageContents, 0, tmp, 0, currentOutputPosition);
- resp = tmp;
- } else {
- resp = messageContents;
- }
- return EncodingUtils.getAsciiString(Base64.encodeBase64(resp));
+ public String getResponse() {
+ return new String(Base64.encodeBase64(getBytes()), Consts.ASCII);
}
+ public byte[] getBytes() {
+ if (messageContents == null) {
+ buildMessage();
+ }
+ final byte[] resp;
+ if ( messageContents.length > currentOutputPosition ) {
+ final byte[] tmp = new byte[currentOutputPosition];
+ System.arraycopy( messageContents, 0, tmp, 0, currentOutputPosition );
+ messageContents = tmp;
+ }
+ return messageContents;
+ }
+
+ protected void buildMessage() {
+ throw new RuntimeException("Message builder not implemented for "+getClass().getName());
+ }
}
/** Type 1 message assembly class */
@@ -965,12 +1243,16 @@ final class NTLMEngineImpl implements NTLMEngine {
private final byte[] hostBytes;
private final byte[] domainBytes;
+ private final int flags;
Type1Message(final String domain, final String host) throws NTLMEngineException {
+ this(domain, host, null);
+ }
+
+ Type1Message(final String domain, final String host, final Integer flags) throws NTLMEngineException {
super();
- if (UNICODE_LITTLE_UNMARKED == null) {
- throw new NTLMEngineException("Unicode not supported");
- }
+ this.flags = ((flags == null)?getDefaultFlags():flags);
+
// Strip off domain name from the host!
final String unqualifiedHost = convertHost(host);
// Use only the base domain name!
@@ -986,56 +1268,72 @@ final class NTLMEngineImpl implements NTLMEngine {
super();
hostBytes = null;
domainBytes = null;
+ flags = getDefaultFlags();
}
+
+ private int getDefaultFlags() {
+ return
+ //FLAG_WORKSTATION_PRESENT |
+ //FLAG_DOMAIN_PRESENT |
+
+ // Required flags
+ //FLAG_REQUEST_LAN_MANAGER_KEY |
+ FLAG_REQUEST_NTLMv1 |
+ FLAG_REQUEST_NTLM2_SESSION |
+
+ // Protocol version request
+ FLAG_REQUEST_VERSION |
+
+ // Recommended privacy settings
+ FLAG_REQUEST_ALWAYS_SIGN |
+ //FLAG_REQUEST_SEAL |
+ //FLAG_REQUEST_SIGN |
+
+ // These must be set according to documentation, based on use of SEAL above
+ FLAG_REQUEST_128BIT_KEY_EXCH |
+ FLAG_REQUEST_56BIT_ENCRYPTION |
+ //FLAG_REQUEST_EXPLICIT_KEY_EXCH |
+
+ FLAG_REQUEST_UNICODE_ENCODING;
+
+ }
+
/**
* Getting the response involves building the message before returning
* it
*/
@Override
- String getResponse() {
+ protected void buildMessage() {
+ int domainBytesLength = 0;
+ if ( domainBytes != null ) {
+ domainBytesLength = domainBytes.length;
+ }
+ int hostBytesLength = 0;
+ if ( hostBytes != null ) {
+ hostBytesLength = hostBytes.length;
+ }
+
// Now, build the message. Calculate its length first, including
// signature or type.
- final int finalLength = 32 + 8 /*+ hostBytes.length + domainBytes.length */;
+ final int finalLength = 32 + 8 + hostBytesLength + domainBytesLength;
// Set up the response. This will initialize the signature, message
// type, and flags.
prepareResponse(finalLength, 1);
// Flags. These are the complete set of flags we support.
- addULong(
- //FLAG_WORKSTATION_PRESENT |
- //FLAG_DOMAIN_PRESENT |
-
- // Required flags
- //FLAG_REQUEST_LAN_MANAGER_KEY |
- FLAG_REQUEST_NTLMv1 |
- FLAG_REQUEST_NTLM2_SESSION |
-
- // Protocol version request
- FLAG_REQUEST_VERSION |
-
- // Recommended privacy settings
- FLAG_REQUEST_ALWAYS_SIGN |
- //FLAG_REQUEST_SEAL |
- //FLAG_REQUEST_SIGN |
-
- // These must be set according to documentation, based on use of SEAL above
- FLAG_REQUEST_128BIT_KEY_EXCH |
- FLAG_REQUEST_56BIT_ENCRYPTION |
- //FLAG_REQUEST_EXPLICIT_KEY_EXCH |
-
- FLAG_REQUEST_UNICODE_ENCODING);
+ addULong(flags);
// Domain length (two times).
- addUShort(/*domainBytes.length*/0);
- addUShort(/*domainBytes.length*/0);
+ addUShort(domainBytesLength);
+ addUShort(domainBytesLength);
// Domain offset.
- addULong(/*hostBytes.length +*/ 32 + 8);
+ addULong(hostBytesLength + 32 + 8);
// Host length (two times).
- addUShort(/*hostBytes.length*/0);
- addUShort(/*hostBytes.length*/0);
+ addUShort(hostBytesLength);
+ addUShort(hostBytesLength);
// Host offset (always 32 + 8).
addULong(32 + 8);
@@ -1055,20 +1353,22 @@ final class NTLMEngineImpl implements NTLMEngine {
if (domainBytes != null) {
addBytes(domainBytes);
}
-
- return super.getResponse();
}
}
/** Type 2 message class */
static class Type2Message extends NTLMMessage {
- protected byte[] challenge;
+ protected final byte[] challenge;
protected String target;
protected byte[] targetInfo;
- protected int flags;
+ protected final int flags;
- Type2Message(final String message) throws NTLMEngineException {
+ Type2Message(final String messageBody) throws NTLMEngineException {
+ this(Base64.decodeBase64(messageBody.getBytes(DEFAULT_CHARSET)));
+ }
+
+ Type2Message(final byte[] message) throws NTLMEngineException {
super(message, 2);
// Type 2 message is laid out as follows:
@@ -1091,12 +1391,6 @@ final class NTLMEngineImpl implements NTLMEngine {
flags = readULong(20);
- if ((flags & FLAG_REQUEST_UNICODE_ENCODING) == 0) {
- throw new NTLMEngineException(
- "NTLM type 2 message indicates no support for Unicode. Flags are: "
- + Integer.toString(flags));
- }
-
// Do the target!
target = null;
// The TARGET_DESIRED flag is said to not have understood semantics
@@ -1105,11 +1399,7 @@ final class NTLMEngineImpl implements NTLMEngine {
if (getMessageLength() >= 12 + 8) {
final byte[] bytes = readSecurityBuffer(12);
if (bytes.length != 0) {
- try {
- target = new String(bytes, "UnicodeLittleUnmarked");
- } catch (final UnsupportedEncodingException e) {
- throw new NTLMEngineException(e.getMessage(), e);
- }
+ target = new String(bytes, getCharset(flags));
}
}
@@ -1148,32 +1438,113 @@ final class NTLMEngineImpl implements NTLMEngine {
/** Type 3 message assembly class */
static class Type3Message extends NTLMMessage {
+ // For mic computation
+ protected final byte[] type1Message;
+ protected final byte[] type2Message;
// Response flags from the type2 message
- protected int type2Flags;
+ protected final int type2Flags;
- protected byte[] domainBytes;
- protected byte[] hostBytes;
- protected byte[] userBytes;
+ protected final byte[] domainBytes;
+ protected final byte[] hostBytes;
+ protected final byte[] userBytes;
protected byte[] lmResp;
protected byte[] ntResp;
- protected byte[] sessionKey;
+ protected final byte[] sessionKey;
+ protected final byte[] exportedSessionKey;
+ protected final boolean computeMic;
+
+ /** More primitive constructor: don't include cert or previous messages.
+ */
+ Type3Message(final String domain,
+ final String host,
+ final String user,
+ final String password,
+ final byte[] nonce,
+ final int type2Flags,
+ final String target,
+ final byte[] targetInformation)
+ throws NTLMEngineException {
+ this(domain, host, user, password, nonce, type2Flags, target, targetInformation, null, null, null);
+ }
+
+ /** More primitive constructor: don't include cert or previous messages.
+ */
+ Type3Message(final Random random, final long currentTime,
+ final String domain,
+ final String host,
+ final String user,
+ final String password,
+ final byte[] nonce,
+ final int type2Flags,
+ final String target,
+ final byte[] targetInformation)
+ throws NTLMEngineException {
+ this(random, currentTime, domain, host, user, password, nonce, type2Flags, target, targetInformation, null, null, null);
+ }
/** Constructor. Pass the arguments we will need */
- Type3Message(final String domain, final String host, final String user, final String password, final byte[] nonce,
- final int type2Flags, final String target, final byte[] targetInformation)
- throws NTLMEngineException {
+ Type3Message(final String domain,
+ final String host,
+ final String user,
+ final String password,
+ final byte[] nonce,
+ final int type2Flags,
+ final String target,
+ final byte[] targetInformation,
+ final Certificate peerServerCertificate,
+ final byte[] type1Message,
+ final byte[] type2Message)
+ throws NTLMEngineException {
+ this(RND_GEN, System.currentTimeMillis(), domain, host, user, password, nonce, type2Flags, target, targetInformation, peerServerCertificate, type1Message, type2Message);
+ }
+
+ /** Constructor. Pass the arguments we will need */
+ Type3Message(final Random random, final long currentTime,
+ final String domain,
+ final String host,
+ final String user,
+ final String password,
+ final byte[] nonce,
+ final int type2Flags,
+ final String target,
+ final byte[] targetInformation,
+ final Certificate peerServerCertificate,
+ final byte[] type1Message,
+ final byte[] type2Message)
+ throws NTLMEngineException {
+
+ if (random == null) {
+ throw new NTLMEngineException("Random generator not available");
+ }
+
// Save the flags
this.type2Flags = type2Flags;
+ this.type1Message = type1Message;
+ this.type2Message = type2Message;
// Strip off domain name from the host!
final String unqualifiedHost = convertHost(host);
// Use only the base domain name!
final String unqualifiedDomain = convertDomain(domain);
- // Create a cipher generator class. Use domain BEFORE it gets modified!
- final CipherGen gen = new CipherGen(unqualifiedDomain, user, password, nonce, target, targetInformation);
+ byte[] responseTargetInformation = targetInformation;
+ if (peerServerCertificate != null) {
+ responseTargetInformation = addGssMicAvsToTargetInfo(targetInformation, peerServerCertificate);
+ computeMic = true;
+ } else {
+ computeMic = false;
+ }
+
+ // Create a cipher generator class. Use domain BEFORE it gets modified!
+ final CipherGen gen = new CipherGen(random, currentTime,
+ unqualifiedDomain,
+ user,
+ password,
+ nonce,
+ target,
+ responseTargetInformation);
// Use the new code to calculate the responses, including v2 if that
// seems warranted.
@@ -1226,25 +1597,37 @@ final class NTLMEngineImpl implements NTLMEngine {
if ((type2Flags & FLAG_REQUEST_SIGN) != 0) {
if ((type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) != 0) {
- sessionKey = RC4(gen.getSecondaryKey(), userSessionKey);
+ exportedSessionKey = gen.getSecondaryKey();
+ sessionKey = RC4(exportedSessionKey, userSessionKey);
} else {
sessionKey = userSessionKey;
+ exportedSessionKey = sessionKey;
}
} else {
+ if (computeMic) {
+ throw new NTLMEngineException("Cannot sign/seal: no exported session key");
+ }
sessionKey = null;
+ exportedSessionKey = null;
}
- if (UNICODE_LITTLE_UNMARKED == null) {
- throw new NTLMEngineException("Unicode not supported");
- }
- hostBytes = unqualifiedHost != null ? unqualifiedHost.getBytes(UNICODE_LITTLE_UNMARKED) : null;
- domainBytes = unqualifiedDomain != null ? unqualifiedDomain
- .toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED) : null;
- userBytes = user.getBytes(UNICODE_LITTLE_UNMARKED);
+ final Charset charset = getCharset(type2Flags);
+ hostBytes = unqualifiedHost != null ? unqualifiedHost.getBytes(charset) : null;
+ domainBytes = unqualifiedDomain != null ? unqualifiedDomain
+ .toUpperCase(Locale.ROOT).getBytes(charset) : null;
+ userBytes = user.getBytes(charset);
+ }
+
+ public byte[] getEncryptedRandomSessionKey() {
+ return sessionKey;
+ }
+
+ public byte[] getExportedSessionKey() {
+ return exportedSessionKey;
}
/** Assemble the response */
@Override
- String getResponse() {
+ protected void buildMessage() {
final int ntRespLen = ntResp.length;
final int lmRespLen = lmResp.length;
@@ -1259,7 +1642,8 @@ final class NTLMEngineImpl implements NTLMEngine {
}
// Calculate the layout within the packet
- final int lmRespOffset = 72; // allocate space for the version
+ final int lmRespOffset = 72 + // allocate space for the version
+ ( computeMic ? 16 : 0 ); // and MIC
final int ntRespOffset = lmRespOffset + lmRespLen;
final int domainOffset = ntRespOffset + ntRespLen;
final int userOffset = domainOffset + domainLen;
@@ -1314,6 +1698,7 @@ final class NTLMEngineImpl implements NTLMEngine {
// Flags.
addULong(
+ /*
//FLAG_WORKSTATION_PRESENT |
//FLAG_DOMAIN_PRESENT |
@@ -1338,6 +1723,8 @@ final class NTLMEngineImpl implements NTLMEngine {
(type2Flags & FLAG_TARGETINFO_PRESENT) |
(type2Flags & FLAG_REQUEST_UNICODE_ENCODING) |
(type2Flags & FLAG_REQUEST_TARGET)
+ */
+ type2Flags
);
// Version
@@ -1347,6 +1734,12 @@ final class NTLMEngineImpl implements NTLMEngine {
// NTLM revision
addUShort(0x0f00);
+ int micPosition = -1;
+ if ( computeMic ) {
+ micPosition = currentOutputPosition;
+ currentOutputPosition += 16;
+ }
+
// Add the actual data
addBytes(lmResp);
addBytes(ntResp);
@@ -1357,8 +1750,69 @@ final class NTLMEngineImpl implements NTLMEngine {
addBytes(sessionKey);
}
- return super.getResponse();
+ // Write the mic back into its slot in the message
+
+ if (computeMic) {
+ // Computation of message integrity code (MIC) as specified in [MS-NLMP] section 3.2.5.1.2.
+ final HMACMD5 hmacMD5 = new HMACMD5( exportedSessionKey );
+ hmacMD5.update( type1Message );
+ hmacMD5.update( type2Message );
+ hmacMD5.update( messageContents );
+ final byte[] mic = hmacMD5.getOutput();
+ System.arraycopy( mic, 0, messageContents, micPosition, mic.length );
+ }
}
+
+ /**
+ * Add GSS channel binding hash and MIC flag to the targetInfo.
+ * Looks like this is needed if we want to use exported session key for GSS wrapping.
+ */
+ private byte[] addGssMicAvsToTargetInfo( final byte[] originalTargetInfo,
+ final Certificate peerServerCertificate ) throws NTLMEngineException
+ {
+ final byte[] newTargetInfo = new byte[originalTargetInfo.length + 8 + 20];
+ final int appendLength = originalTargetInfo.length - 4; // last tag is MSV_AV_EOL, do not copy that
+ System.arraycopy( originalTargetInfo, 0, newTargetInfo, 0, appendLength );
+ writeUShort( newTargetInfo, MSV_AV_FLAGS, appendLength );
+ writeUShort( newTargetInfo, 4, appendLength + 2 );
+ writeULong( newTargetInfo, MSV_AV_FLAGS_MIC, appendLength + 4 );
+ writeUShort( newTargetInfo, MSV_AV_CHANNEL_BINDINGS, appendLength + 8 );
+ writeUShort( newTargetInfo, 16, appendLength + 10 );
+
+ byte[] channelBindingsHash;
+ try
+ {
+ final byte[] certBytes = peerServerCertificate.getEncoded();
+ final MessageDigest sha256 = MessageDigest.getInstance( "SHA-256" );
+ final byte[] certHashBytes = sha256.digest( certBytes );
+ final byte[] channelBindingStruct = new byte[16 + 4 + MAGIC_TLS_SERVER_ENDPOINT.length
+ + certHashBytes.length];
+ writeULong( channelBindingStruct, 0x00000035, 16 );
+ System.arraycopy( MAGIC_TLS_SERVER_ENDPOINT, 0, channelBindingStruct, 20,
+ MAGIC_TLS_SERVER_ENDPOINT.length );
+ System.arraycopy( certHashBytes, 0, channelBindingStruct, 20 + MAGIC_TLS_SERVER_ENDPOINT.length,
+ certHashBytes.length );
+ final MessageDigest md5 = getMD5();
+ channelBindingsHash = md5.digest( channelBindingStruct );
+ }
+ catch ( CertificateEncodingException e )
+ {
+ throw new NTLMEngineException( e.getMessage(), e );
+ }
+ catch ( NoSuchAlgorithmException e )
+ {
+ throw new NTLMEngineException( e.getMessage(), e );
+ }
+
+ System.arraycopy( channelBindingsHash, 0, newTargetInfo, appendLength + 12, 16 );
+ return newTargetInfo;
+ }
+
+ }
+
+ static void writeUShort(final byte[] buffer, final int value, final int offset) {
+ buffer[offset] = ( byte ) ( value & 0xff );
+ buffer[offset + 1] = ( byte ) ( value >> 8 & 0xff );
}
static void writeULong(final byte[] buffer, final int value, final int offset) {
@@ -1384,6 +1838,14 @@ final class NTLMEngineImpl implements NTLMEngine {
return ((val << numbits) | (val >>> (32 - numbits)));
}
+ static MessageDigest getMD5() {
+ try {
+ return MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException ex) {
+ throw new RuntimeException("MD5 message digest doesn't seem to exist - fatal error: "+ex.getMessage(), ex);
+ }
+ }
+
/**
* Cryptography support - MD4. The following class was based loosely on the
* RFC and on code found at http://www.cs.umd.edu/~harry/jotp/src/md.java.
@@ -1397,7 +1859,7 @@ final class NTLMEngineImpl implements NTLMEngine {
protected int C = 0x98badcfe;
protected int D = 0x10325476;
protected long count = 0L;
- protected byte[] dataBuffer = new byte[64];
+ protected final byte[] dataBuffer = new byte[64];
MD4() {
}
@@ -1556,20 +2018,13 @@ final class NTLMEngineImpl implements NTLMEngine {
* resources by Karl Wright
*/
static class HMACMD5 {
- protected byte[] ipad;
- protected byte[] opad;
- protected MessageDigest md5;
+ protected final byte[] ipad;
+ protected final byte[] opad;
+ protected final MessageDigest md5;
- HMACMD5(final byte[] input) throws NTLMEngineException {
+ HMACMD5(final byte[] input) {
byte[] key = input;
- try {
- md5 = MessageDigest.getInstance("MD5");
- } catch (final Exception ex) {
- // Umm, the algorithm doesn't exist - throw an
- // NTLMEngineException!
- throw new NTLMEngineException(
- "Error getting md5 message digest implementation: " + ex.getMessage(), ex);
- }
+ md5 = getMD5();
// Initialize the pad buffers with the key
ipad = new byte[64];
@@ -1594,7 +2049,7 @@ final class NTLMEngineImpl implements NTLMEngine {
i++;
}
- // Very important: update the digest with the ipad buffer
+ // Very important: processChallenge the digest with the ipad buffer
md5.reset();
md5.update(ipad);
diff --git a/httpclient/src/main/java/org/apache/http/impl/client/AuthenticationStrategyImpl.java b/httpclient/src/main/java/org/apache/http/impl/client/AuthenticationStrategyImpl.java
index 57df6141f..1f35e61c5 100644
--- a/httpclient/src/main/java/org/apache/http/impl/client/AuthenticationStrategyImpl.java
+++ b/httpclient/src/main/java/org/apache/http/impl/client/AuthenticationStrategyImpl.java
@@ -73,9 +73,9 @@ abstract class AuthenticationStrategyImpl implements AuthenticationStrategy {
AuthSchemes.SPNEGO,
AuthSchemes.KERBEROS,
AuthSchemes.NTLM,
+ AuthSchemes.CREDSSP,
AuthSchemes.DIGEST,
AuthSchemes.BASIC));
-
private final int challengeCode;
private final String headerName;
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 03900e24f..8af33b622 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
@@ -29,6 +29,11 @@ package org.apache.http.impl.auth;
import org.apache.http.Consts;
import org.junit.Assert;
import org.junit.Test;
+import java.util.Random;
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.CertificateFactory;
+import java.security.cert.Certificate;
public class TestNTLMEngineImpl {
@@ -51,6 +56,9 @@ public class TestNTLMEngineImpl {
if (c >= 'a' && c <= 'f') {
return (byte) (c - 'a' + 0x0a);
}
+ if (c >= 'A' && c <= 'F') {
+ return (byte) (c - 'A' + 0x0a);
+ }
return (byte) (c - '0');
}
@@ -89,6 +97,8 @@ public class TestNTLMEngineImpl {
@Test
public void testLMResponse() throws Exception {
final NTLMEngineImpl.CipherGen gen = new NTLMEngineImpl.CipherGen(
+ new Random(1234),
+ 1234L,
null,
null,
"SecREt01",
@@ -107,6 +117,8 @@ public class TestNTLMEngineImpl {
@Test
public void testNTLMResponse() throws Exception {
final NTLMEngineImpl.CipherGen gen = new NTLMEngineImpl.CipherGen(
+ new Random(1234),
+ 1234L,
null,
null,
"SecREt01",
@@ -125,6 +137,8 @@ public class TestNTLMEngineImpl {
@Test
public void testLMv2Response() throws Exception {
final NTLMEngineImpl.CipherGen gen = new NTLMEngineImpl.CipherGen(
+ new Random(1234),
+ 1234L,
"DOMAIN",
"user",
"SecREt01",
@@ -143,6 +157,8 @@ public class TestNTLMEngineImpl {
@Test
public void testNTLMv2Response() throws Exception {
final NTLMEngineImpl.CipherGen gen = new NTLMEngineImpl.CipherGen(
+ new Random(1234),
+ 1234L,
"DOMAIN",
"user",
"SecREt01",
@@ -163,6 +179,8 @@ public class TestNTLMEngineImpl {
@Test
public void testLM2SessionResponse() throws Exception {
final NTLMEngineImpl.CipherGen gen = new NTLMEngineImpl.CipherGen(
+ new Random(1234),
+ 1234L,
"DOMAIN",
"user",
"SecREt01",
@@ -181,6 +199,8 @@ public class TestNTLMEngineImpl {
@Test
public void testNTLM2SessionResponse() throws Exception {
final NTLMEngineImpl.CipherGen gen = new NTLMEngineImpl.CipherGen(
+ new Random(1234),
+ 1234L,
"DOMAIN",
"user",
"SecREt01",
@@ -199,6 +219,8 @@ public class TestNTLMEngineImpl {
@Test
public void testNTLMUserSessionKey() throws Exception {
final NTLMEngineImpl.CipherGen gen = new NTLMEngineImpl.CipherGen(
+ new Random(1234),
+ 1234L,
"DOMAIN",
"user",
"SecREt01",
@@ -216,20 +238,74 @@ public class TestNTLMEngineImpl {
@Test
public void testType1Message() throws Exception {
- new NTLMEngineImpl().getType1Message("myhost", "mydomain");
+ final byte[] bytes = new NTLMEngineImpl.Type1Message("myhost", "mydomain").getBytes();
+ final byte[] bytes2 = toBytes("4E544C4D5353500001000000018208A20C000C003800000010001000280000000501280A0000000F6D00790064006F006D00610069006E004D00590048004F0053005400");
+ checkArraysMatch(bytes2, bytes);
}
@Test
public void testType3Message() throws Exception {
- new NTLMEngineImpl().getType3Message("me", "mypassword", "myhost", "mydomain",
+ final byte[] bytes = new NTLMEngineImpl.Type3Message(
+ new Random(1234),
+ 1234L,
+ "me", "mypassword", "myhost", "mydomain",
toBytes("0001020304050607"),
0xffffffff,
- null,null);
- new NTLMEngineImpl().getType3Message("me", "mypassword", "myhost", "mydomain",
+ null,null).getBytes();
+ checkArraysMatch(toBytes("4E544C4D53535000030000001800180048000000180018006000000004000400780000000C000C007C0000001400140088000000100010009C000000FFFFFFFF0501280A0000000FA86886A5D297814200000000000000000000000000000000EEC7568E00798491244959B9C942F4F367C5CBABEEF546F74D0045006D00790068006F00730074006D007900700061007300730077006F007200640094DDAB1EBB82C9A1AB914CAE6F199644"),
+ bytes);
+ final byte[] bytes2 = new NTLMEngineImpl.Type3Message(
+ new Random(1234),
+ 1234L,
+ "me", "mypassword", "myhost", "mydomain",
toBytes("0001020304050607"),
0xffffffff,
"mytarget",
- toBytes("02000c0044004f004d00410049004e0001000c005300450052005600450052000400140064006f006d00610069006e002e0063006f006d00030022007300650072007600650072002e0064006f006d00610069006e002e0063006f006d0000000000"));
+ toBytes("02000c0044004f004d00410049004e0001000c005300450052005600450052000400140064006f006d00610069006e002e0063006f006d00030022007300650072007600650072002e0064006f006d00610069006e002e0063006f006d0000000000")).getBytes();
+ checkArraysMatch(toBytes("4E544C4D53535000030000001800180048000000920092006000000004000400F20000000C000C00F600000014001400020100001000100016010000FFFFFFFF0501280A0000000F3695F1EA7B164788A437892FA7504320DA2D8CF378EBC83CE856A8FB985BF7783545828A91A13AE8010100000000000020CBFAD5DEB19D01A86886A5D29781420000000002000C0044004F004D00410049004E0001000C005300450052005600450052000400140064006F006D00610069006E002E0063006F006D00030022007300650072007600650072002E0064006F006D00610069006E002E0063006F006D0000000000000000004D0045006D00790068006F00730074006D007900700061007300730077006F0072006400BB1AAD36F11631CC7CBC8800CEEE1C99"),
+ bytes2);
+ }
+
+ private static String cannedCert =
+ "-----BEGIN CERTIFICATE-----\n"+
+ "MIIDIDCCAgigAwIBAgIEOqKaWTANBgkqhkiG9w0BAQsFADBSMQswCQYDVQQGEwJVUzEQMA4GA1UEBxMH\n"+
+ "TXkgQ2l0eTEYMBYGA1UEChMPTXkgT3JnYW5pemF0aW9uMRcwFQYDVQQDEw5NeSBBcHBsaWNhdGlvbjAe\n"+
+ "Fw0xNzAzMTcxNDAyMzRaFw0yNzAzMTUxNDAyMzRaMFIxCzAJBgNVBAYTAlVTMRAwDgYDVQQHEwdNeSBD\n"+
+ "aXR5MRgwFgYDVQQKEw9NeSBPcmdhbml6YXRpb24xFzAVBgNVBAMTDk15IEFwcGxpY2F0aW9uMIIBIjAN\n"+
+ "BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArc+mbViBaHeRSt82KrJ5IF+62b/Qob95Lca4DJIislTY\n"+
+ "vLPIo0R1faBV8BkEeUQwo01srkf3RaGLCHNZnFal4KEzbtiUy6W+n08G5E9w9YG+WSwW2dmjvEI7k2a2\n"+
+ "xqlaM4NdMKL4ONPXcxfZsMDqxDgpdkaNPKpZ10NDq6rmBTkQw/OSG0z1KLtwLkF1ZQ/3mXdjVzvP83V2\n"+
+ "g17AqBazb0Z1YHsVKmkGjPqnq3niJH/6Oke4N+5k/1cE5lSJcQNGP0nqeGdJfvqQZ+gk6gH/sOngZL9X\n"+
+ "hPVkpseAwHa+xuPneDSjibLgLmMt3XGDK6jGfjdp5FWqFvAD5E3LHbW9gwIDAQABMA0GCSqGSIb3DQEB\n"+
+ "CwUAA4IBAQCpUXUHhl5LyMSO5Q0OktEc9AaFjZtVfknpPde6Zeh35Pqd2354ErvJSBWgzFAphda0oh2s\n"+
+ "OIAFkM6LJQEnVDTbXDXN+YY8e3gb9ryfh85hkhC0XI9qp17WPSkmw8XgDfvRd6YQgKm1AnLxjOCwG2jg\n"+
+ "i09iZBIWkW3ZeRAMvWPHHjvq44iZB5ZrEl0apgumS6MxpUzKOr5Pcq0jxJDw2UCj5YloFMNl+UINv2vV\n"+
+ "aL/DR6ivc61dOfN1E/VNBGkkCk/AogNyucGiFMCq9hd25Y9EbkBBqObYTH1XMX+ufsJh+6hG7KDQ1e/F\n"+
+ "nRrlhKwM2uRe+aSH0D6/erjDBT7tXvwn\n"+
+ "-----END CERTIFICATE-----";
+
+ @Test
+ public void testType3MessageWithCert() throws Exception {
+ final ByteArrayInputStream fis = new ByteArrayInputStream(cannedCert.getBytes(Consts.ASCII));
+
+ final CertificateFactory cf = CertificateFactory.getInstance("X.509");
+
+ final Certificate cert = cf.generateCertificate(fis);
+
+ final byte[] bytes = new NTLMEngineImpl.Type3Message(
+ new Random(1234),
+ 1234L,
+ "me", "mypassword", "myhost", "mydomain",
+ toBytes("0001020304050607"),
+ 0xffffffff,
+ "mytarget",
+ toBytes("02000c0044004f004d00410049004e0001000c005300450052005600450052000400140064006f006d00610069006e002e0063006f006d00030022007300650072007600650072002e0064006f006d00610069006e002e0063006f006d0000000000"),
+ cert,
+ toBytes("4E544C4D5353500001000000018208A20C000C003800000010001000280000000501280A0000000F6D00790064006F006D00610069006E004D00590048004F0053005400"),
+ toBytes("4E544C4D5353500001000000018208A20C000C003800000010001000280000000501280A0000000F6D00790064006F006D00610069006E004D00590048004F0053005400FFFEFDFCFBFA")).getBytes();
+
+ checkArraysMatch(toBytes("4E544C4D53535000030000001800180058000000AE00AE0070000000040004001E0100000C000C0022010000140014002E0100001000100042010000FFFFFFFF0501280A0000000FEEFCCE4187D6CDF1F91C686C4E571D943695F1EA7B164788A437892FA7504320DA2D8CF378EBC83C59D7A3B2951929079B66621D7CF4326B010100000000000020CBFAD5DEB19D01A86886A5D29781420000000002000C0044004F004D00410049004E0001000C005300450052005600450052000400140064006F006D00610069006E002E0063006F006D00030022007300650072007600650072002E0064006F006D00610069006E002E0063006F006D0006000400020000000A00100038EDC0B7EF8D8FE9E1E6A83F6DFEB8FF00000000000000004D0045006D00790068006F00730074006D007900700061007300730077006F0072006400BB1AAD36F11631CC7CBC8800CEEE1C99"),
+ bytes);
}
@Test
@@ -247,4 +323,5 @@ public class TestNTLMEngineImpl {
Assert.assertEquals(a1[i],a2[i]);
}
}
+
}