diff -Nru httpcomponents-client-4.5.4/debian/changelog httpcomponents-client-4.5.5/debian/changelog --- httpcomponents-client-4.5.4/debian/changelog 2018-01-12 08:57:35.000000000 +0000 +++ httpcomponents-client-4.5.5/debian/changelog 2018-02-13 16:26:39.000000000 +0000 @@ -1,3 +1,12 @@ +httpcomponents-client (4.5.5-1) unstable; urgency=medium + + * Team upload. + * New upstream release + * Removed Damien Raude-Morvan from the uploaders (Closes: #889356) + * Switch to debhelper level 11 + + -- Emmanuel Bourg Tue, 13 Feb 2018 17:26:39 +0100 + httpcomponents-client (4.5.4-1) unstable; urgency=medium * Team upload. diff -Nru httpcomponents-client-4.5.4/debian/compat httpcomponents-client-4.5.5/debian/compat --- httpcomponents-client-4.5.4/debian/compat 2018-01-12 08:50:09.000000000 +0000 +++ httpcomponents-client-4.5.5/debian/compat 2018-02-13 15:28:57.000000000 +0000 @@ -1 +1 @@ -10 +11 diff -Nru httpcomponents-client-4.5.4/debian/control httpcomponents-client-4.5.5/debian/control --- httpcomponents-client-4.5.4/debian/control 2018-01-12 08:50:09.000000000 +0000 +++ httpcomponents-client-4.5.5/debian/control 2018-02-13 15:28:57.000000000 +0000 @@ -4,10 +4,9 @@ Maintainer: Debian Java Maintainers Uploaders: Jakub Adam , - Damien Raude-Morvan , Emmanuel Bourg Build-Depends: - debhelper (>= 10), + debhelper (>= 11), default-jdk, javahelper, maven-debian-helper diff -Nru httpcomponents-client-4.5.4/fluent-hc/pom.xml httpcomponents-client-4.5.5/fluent-hc/pom.xml --- httpcomponents-client-4.5.4/fluent-hc/pom.xml 2017-11-27 09:36:12.000000000 +0000 +++ httpcomponents-client-4.5.5/fluent-hc/pom.xml 2018-01-18 11:51:14.000000000 +0000 @@ -28,7 +28,7 @@ org.apache.httpcomponents httpcomponents-client - 4.5.4 + 4.5.5 fluent-hc Apache HttpClient Fluent API diff -Nru httpcomponents-client-4.5.4/httpclient/pom.xml httpcomponents-client-4.5.5/httpclient/pom.xml --- httpcomponents-client-4.5.4/httpclient/pom.xml 2017-11-27 09:36:12.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient/pom.xml 2018-01-18 11:51:14.000000000 +0000 @@ -28,7 +28,7 @@ org.apache.httpcomponents httpcomponents-client - 4.5.4 + 4.5.5 httpclient Apache HttpClient diff -Nru httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/client/entity/DeflateInputStreamFactory.java httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/client/entity/DeflateInputStreamFactory.java --- httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/client/entity/DeflateInputStreamFactory.java 2017-11-27 08:54:33.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/client/entity/DeflateInputStreamFactory.java 2018-01-12 14:49:12.000000000 +0000 @@ -1,63 +1,63 @@ -/* - * ==================================================================== - * 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.client.entity; - -import java.io.IOException; -import java.io.InputStream; - -import org.apache.http.annotation.Contract; -import org.apache.http.annotation.ThreadingBehavior; - -/** - * {@link InputStreamFactory} for handling Deflate Content Coded responses. - * - * @since 4.5.4 - */ -@Contract(threading = ThreadingBehavior.IMMUTABLE) -public class DeflateInputStreamFactory implements InputStreamFactory { - - /** - * Singleton instance. - */ - private static final DeflateInputStreamFactory INSTANCE = new DeflateInputStreamFactory(); - - /** - * Gets the singleton instance. - * - * @return the singleton instance. - */ - public static DeflateInputStreamFactory getInstance() { - return INSTANCE; - } - - @Override - public InputStream create(final InputStream inputStream) throws IOException { - return new DeflateInputStream(inputStream); - } - -} +/* + * ==================================================================== + * 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.client.entity; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.http.annotation.Contract; +import org.apache.http.annotation.ThreadingBehavior; + +/** + * {@link InputStreamFactory} for handling Deflate Content Coded responses. + * + * @since 4.5.4 + */ +@Contract(threading = ThreadingBehavior.IMMUTABLE) +public class DeflateInputStreamFactory implements InputStreamFactory { + + /** + * Singleton instance. + */ + private static final DeflateInputStreamFactory INSTANCE = new DeflateInputStreamFactory(); + + /** + * Gets the singleton instance. + * + * @return the singleton instance. + */ + public static DeflateInputStreamFactory getInstance() { + return INSTANCE; + } + + @Override + public InputStream create(final InputStream inputStream) throws IOException { + return new DeflateInputStream(inputStream); + } + +} diff -Nru httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/client/entity/GZIPInputStreamFactory.java httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/client/entity/GZIPInputStreamFactory.java --- httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/client/entity/GZIPInputStreamFactory.java 2017-11-27 08:54:33.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/client/entity/GZIPInputStreamFactory.java 2018-01-12 14:49:12.000000000 +0000 @@ -1,64 +1,64 @@ -/* - * ==================================================================== - * 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.client.entity; - -import java.io.IOException; -import java.io.InputStream; -import java.util.zip.GZIPInputStream; - -import org.apache.http.annotation.Contract; -import org.apache.http.annotation.ThreadingBehavior; - -/** - * {@link InputStreamFactory} for handling GZIPContent Coded responses. - * - * @since 4.5.4 - */ -@Contract(threading = ThreadingBehavior.IMMUTABLE) -public class GZIPInputStreamFactory implements InputStreamFactory { - - /** - * Singleton instance. - */ - private static final GZIPInputStreamFactory INSTANCE = new GZIPInputStreamFactory(); - - /** - * Gets the singleton instance. - * - * @return the singleton instance. - */ - public static GZIPInputStreamFactory getInstance() { - return INSTANCE; - } - - @Override - public InputStream create(final InputStream inputStream) throws IOException { - return new GZIPInputStream(inputStream); - } - -} +/* + * ==================================================================== + * 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.client.entity; + +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.GZIPInputStream; + +import org.apache.http.annotation.Contract; +import org.apache.http.annotation.ThreadingBehavior; + +/** + * {@link InputStreamFactory} for handling GZIPContent Coded responses. + * + * @since 4.5.4 + */ +@Contract(threading = ThreadingBehavior.IMMUTABLE) +public class GZIPInputStreamFactory implements InputStreamFactory { + + /** + * Singleton instance. + */ + private static final GZIPInputStreamFactory INSTANCE = new GZIPInputStreamFactory(); + + /** + * Gets the singleton instance. + * + * @return the singleton instance. + */ + public static GZIPInputStreamFactory getInstance() { + return INSTANCE; + } + + @Override + public InputStream create(final InputStream inputStream) throws IOException { + return new GZIPInputStream(inputStream); + } + +} diff -Nru httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/client/utils/URLEncodedUtils.java httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/client/utils/URLEncodedUtils.java --- httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/client/utils/URLEncodedUtils.java 2017-09-30 10:33:06.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/client/utils/URLEncodedUtils.java 2018-01-18 10:44:19.000000000 +0000 @@ -24,7 +24,7 @@ * . * */ - + package org.apache.http.client.utils; import java.io.IOException; @@ -37,7 +37,6 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.BitSet; -import java.util.Collections; import java.util.List; import java.util.Scanner; @@ -100,7 +99,7 @@ if (query != null && !query.isEmpty()) { return parse(query, charset); } - return Collections.emptyList(); + return createEmptyList(); } /** @@ -120,14 +119,14 @@ Args.notNull(entity, "HTTP entity"); final ContentType contentType = ContentType.get(entity); if (contentType == null || !contentType.getMimeType().equalsIgnoreCase(CONTENT_TYPE)) { - return Collections.emptyList(); + return createEmptyList(); } final long len = entity.getContentLength(); Args.check(len <= Integer.MAX_VALUE, "HTTP entity is too large"); final Charset charset = contentType.getCharset() != null ? contentType.getCharset() : HTTP.DEF_CONTENT_CHARSET; final InputStream instream = entity.getContent(); if (instream == null) { - return Collections.emptyList(); + return createEmptyList(); } final CharArrayBuffer buf; try { @@ -143,7 +142,7 @@ instream.close(); } if (buf.length() == 0) { - return Collections.emptyList(); + return createEmptyList(); } return parse(buf, charset, QP_SEP_A); } @@ -243,7 +242,7 @@ */ public static List parse(final String s, final Charset charset) { if (s == null) { - return Collections.emptyList(); + return createEmptyList(); } final CharArrayBuffer buffer = new CharArrayBuffer(s.length()); buffer.append(s); @@ -266,7 +265,7 @@ */ public static List parse(final String s, final Charset charset, final char... separators) { if (s == null) { - return Collections.emptyList(); + return createEmptyList(); } final CharArrayBuffer buffer = new CharArrayBuffer(s.length()); buffer.append(s); @@ -520,6 +519,10 @@ private static final int RADIX = 16; + private static List createEmptyList() { + return new ArrayList(0); + } + private static String urlEncode( final String content, final Charset charset, diff -Nru httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/conn/ssl/TrustAllStrategy.java httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/conn/ssl/TrustAllStrategy.java --- httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/conn/ssl/TrustAllStrategy.java 2017-11-27 08:54:33.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/conn/ssl/TrustAllStrategy.java 2018-01-12 14:49:12.000000000 +0000 @@ -1,48 +1,48 @@ -/* - * ==================================================================== - * 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.conn.ssl; - -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - -/** - * A trust strategy that accepts all certificates as trusted. Verification of - * all other certificates is done by the trust manager configured in the SSL - * context. - * - * @since 4.5.4 - */ -public class TrustAllStrategy implements TrustStrategy { - - public static final TrustAllStrategy INSTANCE = new TrustAllStrategy(); - - @Override - public boolean isTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { - return true; - } - -} +/* + * ==================================================================== + * 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.conn.ssl; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** + * A trust strategy that accepts all certificates as trusted. Verification of + * all other certificates is done by the trust manager configured in the SSL + * context. + * + * @since 4.5.4 + */ +public class TrustAllStrategy implements TrustStrategy { + + public static final TrustAllStrategy INSTANCE = new TrustAllStrategy(); + + @Override + public boolean isTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { + return true; + } + +} diff -Nru httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/impl/auth/CredSspSchemeFactory.java httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/impl/auth/CredSspSchemeFactory.java --- httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/impl/auth/CredSspSchemeFactory.java 2017-11-27 08:54:33.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/impl/auth/CredSspSchemeFactory.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,44 +0,0 @@ -/* - * ==================================================================== - * 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 -Nru httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/impl/auth/CredSspScheme.java httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/impl/auth/CredSspScheme.java --- httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/impl/auth/CredSspScheme.java 2017-11-27 08:54:33.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/impl/auth/CredSspScheme.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,1126 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.apache.http.impl.auth; - - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Arrays; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLEngineResult; -import javax.net.ssl.SSLEngineResult.HandshakeStatus; -import javax.net.ssl.SSLEngineResult.Status; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.http.Consts; -import org.apache.http.Header; -import org.apache.http.HttpRequest; -import org.apache.http.auth.AUTH; -import org.apache.http.auth.AuthenticationException; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.InvalidCredentialsException; -import org.apache.http.auth.MalformedChallengeException; -import org.apache.http.auth.NTCredentials; -import org.apache.http.message.BufferedHeader; -import org.apache.http.protocol.HttpContext; -import org.apache.http.ssl.SSLContexts; -import org.apache.http.util.CharArrayBuffer; -import org.apache.http.util.CharsetUtils; - -/** - *

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

- *

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

- *

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

- */ -public class CredSspScheme extends AuthSchemeBase -{ - private static final Charset UNICODE_LITTLE_UNMARKED = CharsetUtils.lookup( "UnicodeLittleUnmarked" ); - public static final String SCHEME_NAME = "CredSSP"; - - private final Log log = LogFactory.getLog( CredSspScheme.class ); - - enum State - { - // Nothing sent, nothing received - UNINITIATED, - - // We are handshaking. Several messages are exchanged in this state - TLS_HANDSHAKE, - - // TLS handshake finished. Channel established - TLS_HANDSHAKE_FINISHED, - - // NTLM NEGOTIATE message sent (strictly speaking this should be SPNEGO) - NEGO_TOKEN_SENT, - - // NTLM CHALLENGE message received (strictly speaking this should be SPNEGO) - NEGO_TOKEN_RECEIVED, - - // NTLM AUTHENTICATE message sent together with a server public key - PUB_KEY_AUTH_SENT, - - // Server public key authentication message received - PUB_KEY_AUTH_RECEIVED, - - // Credentials message sent. Protocol exchange finished. - CREDENTIALS_SENT; - } - - private State state; - private SSLEngine sslEngine; - private NTLMEngineImpl.Type1Message type1Message; - private NTLMEngineImpl.Type2Message type2Message; - private NTLMEngineImpl.Type3Message type3Message; - private CredSspTsRequest lastReceivedTsRequest; - private NTLMEngineImpl.Handle ntlmOutgoingHandle; - private NTLMEngineImpl.Handle ntlmIncomingHandle; - private byte[] peerPublicKey; - - - public CredSspScheme() { - state = State.UNINITIATED; - } - - - @Override - public String getSchemeName() - { - return SCHEME_NAME; - } - - - @Override - public String getParameter( final String name ) - { - return null; - } - - - @Override - public String getRealm() - { - return null; - } - - - @Override - public boolean isConnectionBased() - { - return true; - } - - - private SSLEngine getSSLEngine() - { - if ( sslEngine == null ) - { - sslEngine = createSSLEngine(); - } - return sslEngine; - } - - - private SSLEngine createSSLEngine() - { - final SSLContext sslContext; - try - { - sslContext = SSLContexts.custom().build(); - } - catch ( final NoSuchAlgorithmException e ) - { - throw new RuntimeException( "Error creating SSL Context: " + e.getMessage(), e ); - } - catch ( final KeyManagementException e ) - { - throw new RuntimeException( "Error creating SSL Context: " + e.getMessage(), e ); - } - - final X509TrustManager tm = new X509TrustManager() - { - - @Override - public void checkClientTrusted( final X509Certificate[] chain, final String authType ) - throws CertificateException - { - // Nothing to do. - } - - - @Override - public void checkServerTrusted( final X509Certificate[] chain, final String authType ) - throws CertificateException - { - // Nothing to do, accept all. CredSSP server is using its own certificate without any - // binding to the PKI trust chains. The public key is verified as part of the CredSSP - // protocol exchange. - } - - - @Override - public X509Certificate[] getAcceptedIssuers() - { - return null; - } - - }; - try - { - sslContext.init( null, new TrustManager[] - { tm }, null ); - } - catch ( final KeyManagementException e ) - { - throw new RuntimeException( "SSL Context initialization error: " + e.getMessage(), e ); - } - final SSLEngine sslEngine = sslContext.createSSLEngine(); - sslEngine.setUseClientMode( true ); - return sslEngine; - } - - - @Override - protected void parseChallenge( final CharArrayBuffer buffer, final int beginIndex, final int endIndex ) - throws MalformedChallengeException - { - final String inputString = buffer.substringTrimmed( beginIndex, endIndex ); - - if ( inputString.isEmpty() ) - { - if ( state == State.UNINITIATED ) - { - // This is OK, just send out first message. That should start TLS handshake - } - else - { - final String msg = "Received unexpected empty input in state " + state; - log.error( msg ); - throw new MalformedChallengeException( msg ); - } - } - - if ( state == State.TLS_HANDSHAKE ) - { - unwrapHandshake( inputString ); - if ( getSSLEngine().getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING ) - { - log.trace( "TLS handshake finished" ); - state = State.TLS_HANDSHAKE_FINISHED; - } - } - - if ( state == State.NEGO_TOKEN_SENT ) - { - final ByteBuffer buf = unwrap( inputString ); - state = State.NEGO_TOKEN_RECEIVED; - lastReceivedTsRequest = CredSspTsRequest.createDecoded( buf ); - } - - if ( state == State.PUB_KEY_AUTH_SENT ) - { - final ByteBuffer buf = unwrap( inputString ); - state = State.PUB_KEY_AUTH_RECEIVED; - lastReceivedTsRequest = CredSspTsRequest.createDecoded( buf ); - } - } - - - @Override - @Deprecated - public Header authenticate( - final Credentials credentials, - final HttpRequest request ) throws AuthenticationException - { - return authenticate( credentials, request, null ); - } - - - @Override - public Header authenticate( - final Credentials credentials, - final HttpRequest request, - final HttpContext context ) throws AuthenticationException - { - NTCredentials ntcredentials = null; - try - { - ntcredentials = ( NTCredentials ) credentials; - } - catch ( final ClassCastException e ) - { - throw new InvalidCredentialsException( - "Credentials cannot be used for CredSSP authentication: " - + credentials.getClass().getName() ); - } - - String outputString = null; - - if ( state == State.UNINITIATED ) - { - beginTlsHandshake(); - outputString = wrapHandshake(); - state = State.TLS_HANDSHAKE; - - } - else if ( state == State.TLS_HANDSHAKE ) - { - outputString = wrapHandshake(); - - } - else if ( state == State.TLS_HANDSHAKE_FINISHED ) - { - - final int ntlmFlags = getNtlmFlags(); - final ByteBuffer buf = allocateOutBuffer(); - type1Message = new NTLMEngineImpl.Type1Message( - ntcredentials.getDomain(), ntcredentials.getWorkstation(), ntlmFlags); - final byte[] ntlmNegoMessageEncoded = type1Message.getBytes(); - final CredSspTsRequest req = CredSspTsRequest.createNegoToken( ntlmNegoMessageEncoded ); - req.encode( buf ); - buf.flip(); - outputString = wrap( buf ); - state = State.NEGO_TOKEN_SENT; - - } - else if ( state == State.NEGO_TOKEN_RECEIVED ) - { - final ByteBuffer buf = allocateOutBuffer(); - type2Message = new NTLMEngineImpl.Type2Message( - lastReceivedTsRequest.getNegoToken()); - - final Certificate peerServerCertificate = getPeerServerCertificate(); - - type3Message = new NTLMEngineImpl.Type3Message( - ntcredentials.getDomain(), - ntcredentials.getWorkstation(), - ntcredentials.getUserName(), - ntcredentials.getPassword(), - type2Message.getChallenge(), - type2Message.getFlags(), - type2Message.getTarget(), - type2Message.getTargetInfo(), - peerServerCertificate, - type1Message.getBytes(), - type2Message.getBytes()); - - final byte[] ntlmAuthenticateMessageEncoded = type3Message.getBytes(); - - final byte[] exportedSessionKey = type3Message.getExportedSessionKey(); - - ntlmOutgoingHandle = new NTLMEngineImpl.Handle(exportedSessionKey, NTLMEngineImpl.Mode.CLIENT, true); - ntlmIncomingHandle = new NTLMEngineImpl.Handle(exportedSessionKey, NTLMEngineImpl.Mode.SERVER, true); - - final CredSspTsRequest req = CredSspTsRequest.createNegoToken( ntlmAuthenticateMessageEncoded ); - peerPublicKey = getSubjectPublicKeyDer( peerServerCertificate.getPublicKey() ); - final byte[] pubKeyAuth = createPubKeyAuth(); - req.setPubKeyAuth( pubKeyAuth ); - - req.encode( buf ); - buf.flip(); - outputString = wrap( buf ); - state = State.PUB_KEY_AUTH_SENT; - - } - else if ( state == State.PUB_KEY_AUTH_RECEIVED ) - { - verifyPubKeyAuthResponse( lastReceivedTsRequest.getPubKeyAuth() ); - final byte[] authInfo = createAuthInfo( ntcredentials ); - final CredSspTsRequest req = CredSspTsRequest.createAuthInfo( authInfo ); - - final ByteBuffer buf = allocateOutBuffer(); - req.encode( buf ); - buf.flip(); - outputString = wrap( buf ); - state = State.CREDENTIALS_SENT; - } - else - { - throw new AuthenticationException( "Wrong state " + state ); - } - final CharArrayBuffer buffer = new CharArrayBuffer( 32 ); - if ( isProxy() ) - { - buffer.append( AUTH.PROXY_AUTH_RESP ); - } - else - { - buffer.append( AUTH.WWW_AUTH_RESP ); - } - buffer.append( ": CredSSP " ); - buffer.append( outputString ); - return new BufferedHeader( buffer ); - } - - - private int getNtlmFlags() - { - return NTLMEngineImpl.FLAG_REQUEST_OEM_ENCODING | - NTLMEngineImpl.FLAG_REQUEST_SIGN | - NTLMEngineImpl.FLAG_REQUEST_SEAL | - NTLMEngineImpl.FLAG_DOMAIN_PRESENT | - NTLMEngineImpl.FLAG_REQUEST_ALWAYS_SIGN | - NTLMEngineImpl.FLAG_REQUEST_NTLM2_SESSION | - NTLMEngineImpl.FLAG_TARGETINFO_PRESENT | - NTLMEngineImpl.FLAG_REQUEST_VERSION | - NTLMEngineImpl.FLAG_REQUEST_128BIT_KEY_EXCH | - NTLMEngineImpl.FLAG_REQUEST_EXPLICIT_KEY_EXCH | - NTLMEngineImpl.FLAG_REQUEST_56BIT_ENCRYPTION; - } - - - private Certificate getPeerServerCertificate() throws AuthenticationException - { - final Certificate[] peerCertificates; - try - { - peerCertificates = sslEngine.getSession().getPeerCertificates(); - } - catch ( final SSLPeerUnverifiedException e ) - { - throw new AuthenticationException( e.getMessage(), e ); - } - for ( final Certificate peerCertificate : peerCertificates ) - { - if ( !( peerCertificate instanceof X509Certificate ) ) - { - continue; - } - final X509Certificate peerX509Cerificate = ( X509Certificate ) peerCertificate; - if ( peerX509Cerificate.getBasicConstraints() != -1 ) - { - continue; - } - return peerX509Cerificate; - } - return null; - } - - - private byte[] createPubKeyAuth() throws AuthenticationException - { - return ntlmOutgoingHandle.signAndEncryptMessage( peerPublicKey ); - } - - - private void verifyPubKeyAuthResponse( final byte[] pubKeyAuthResponse ) throws AuthenticationException - { - final byte[] pubKeyReceived = ntlmIncomingHandle.decryptAndVerifySignedMessage( pubKeyAuthResponse ); - - // assert: pubKeyReceived = peerPublicKey + 1 - // The following algorithm is a bit simplified. But due to the ASN.1 encoding the first byte - // of the public key will be 0x30 we can pretty much rely on a fact that there will be no carry - if ( peerPublicKey.length != pubKeyReceived.length ) - { - throw new AuthenticationException( "Public key mismatch in pubKeyAuth response" ); - } - if ( ( peerPublicKey[0] + 1 ) != pubKeyReceived[0] ) - { - throw new AuthenticationException( "Public key mismatch in pubKeyAuth response" ); - } - for ( int i = 1; i < peerPublicKey.length; i++ ) - { - if ( peerPublicKey[i] != pubKeyReceived[i] ) - { - throw new AuthenticationException( "Public key mismatch in pubKeyAuth response" ); - } - } - log.trace( "Received public key response is valid" ); - } - - - private byte[] createAuthInfo( final NTCredentials ntcredentials ) throws AuthenticationException - { - - final byte[] domainBytes = encodeUnicode( ntcredentials.getDomain() ); - final byte[] domainOctetStringBytesLengthBytes = encodeLength( domainBytes.length ); - final int domainNameLength = 1 + domainOctetStringBytesLengthBytes.length + domainBytes.length; - final byte[] domainNameLengthBytes = encodeLength( domainNameLength ); - - final byte[] usernameBytes = encodeUnicode( ntcredentials.getUserName() ); - final byte[] usernameOctetStringBytesLengthBytes = encodeLength( usernameBytes.length ); - final int userNameLength = 1 + usernameOctetStringBytesLengthBytes.length + usernameBytes.length; - final byte[] userNameLengthBytes = encodeLength( userNameLength ); - - final byte[] passwordBytes = encodeUnicode( ntcredentials.getPassword() ); - final byte[] passwordOctetStringBytesLengthBytes = encodeLength( passwordBytes.length ); - final int passwordLength = 1 + passwordOctetStringBytesLengthBytes.length + passwordBytes.length; - final byte[] passwordLengthBytes = encodeLength( passwordLength ); - - final int tsPasswordLength = 1 + domainNameLengthBytes.length + domainNameLength + - 1 + userNameLengthBytes.length + userNameLength + - 1 + passwordLengthBytes.length + passwordLength; - final byte[] tsPasswordLengthBytes = encodeLength( tsPasswordLength ); - final int credentialsOctetStringLength = 1 + tsPasswordLengthBytes.length + tsPasswordLength; - final byte[] credentialsOctetStringLengthBytes = encodeLength( credentialsOctetStringLength ); - final int credentialsLength = 1 + credentialsOctetStringLengthBytes.length + credentialsOctetStringLength; - final byte[] credentialsLengthBytes = encodeLength( credentialsLength ); - final int tsCredentialsLength = 5 + 1 + credentialsLengthBytes.length + credentialsLength; - final byte[] tsCredentialsLengthBytes = encodeLength( tsCredentialsLength ); - - final ByteBuffer buf = ByteBuffer.allocate( 1 + tsCredentialsLengthBytes.length + tsCredentialsLength ); - - // TSCredentials structure [MS-CSSP] section 2.2.1.2 - buf.put( ( byte ) 0x30 ); // seq - buf.put( tsCredentialsLengthBytes ); - - buf.put( ( byte ) ( 0x00 | 0xa0 ) ); // credType tag [0] - buf.put( ( byte ) 3 ); // credType length - buf.put( ( byte ) 0x02 ); // type: INTEGER - buf.put( ( byte ) 1 ); // credType inner length - buf.put( ( byte ) 1 ); // credType value: 1 (password) - - buf.put( ( byte ) ( 0x01 | 0xa0 ) ); // credentials tag [1] - buf.put( credentialsLengthBytes ); - buf.put( ( byte ) 0x04 ); // type: OCTET STRING - buf.put( credentialsOctetStringLengthBytes ); - - // TSPasswordCreds structure [MS-CSSP] section 2.2.1.2.1 - buf.put( ( byte ) 0x30 ); // seq - buf.put( tsPasswordLengthBytes ); - - buf.put( ( byte ) ( 0x00 | 0xa0 ) ); // domainName tag [0] - buf.put( domainNameLengthBytes ); - buf.put( ( byte ) 0x04 ); // type: OCTET STRING - buf.put( domainOctetStringBytesLengthBytes ); - buf.put( domainBytes ); - - buf.put( ( byte ) ( 0x01 | 0xa0 ) ); // userName tag [1] - buf.put( userNameLengthBytes ); - buf.put( ( byte ) 0x04 ); // type: OCTET STRING - buf.put( usernameOctetStringBytesLengthBytes ); - buf.put( usernameBytes ); - - buf.put( ( byte ) ( 0x02 | 0xa0 ) ); // password tag [2] - buf.put( passwordLengthBytes ); - buf.put( ( byte ) 0x04 ); // type: OCTET STRING - buf.put( passwordOctetStringBytesLengthBytes ); - buf.put( passwordBytes ); - - final byte[] authInfo = buf.array(); - try - { - return ntlmOutgoingHandle.signAndEncryptMessage( authInfo ); - } - catch ( final NTLMEngineException e ) - { - throw new AuthenticationException( e.getMessage(), e ); - } - } - - private final static byte[] EMPTYBUFFER = new byte[0]; - - private byte[] encodeUnicode( final String string ) - { - if (string == null) { - return EMPTYBUFFER; - } - return string.getBytes( UNICODE_LITTLE_UNMARKED ); - } - - - private byte[] getSubjectPublicKeyDer( final PublicKey publicKey ) throws AuthenticationException - { - // The publicKey.getEncoded() returns encoded SubjectPublicKeyInfo structure. But the CredSSP expects - // SubjectPublicKey subfield. I have found no easy way how to get just the SubjectPublicKey from - // java.security libraries. So let's use a primitive way and parse it out from the DER. - - try - { - final byte[] encodedPubKeyInfo = publicKey.getEncoded(); - - final ByteBuffer buf = ByteBuffer.wrap( encodedPubKeyInfo ); - getByteAndAssert( buf, 0x30, "initial sequence" ); - parseLength( buf ); - getByteAndAssert( buf, 0x30, "AlgorithmIdentifier sequence" ); - final int algIdSeqLength = parseLength( buf ); - buf.position( buf.position() + algIdSeqLength ); - getByteAndAssert( buf, 0x03, "subjectPublicKey type" ); - int subjectPublicKeyLegth = parseLength( buf ); - // There may be leading padding byte ... or whatever that is. Skip that. - final byte b = buf.get(); - if ( b == 0 ) - { - subjectPublicKeyLegth--; - } - else - { - buf.position( buf.position() - 1 ); - } - final byte[] subjectPublicKey = new byte[subjectPublicKeyLegth]; - buf.get( subjectPublicKey ); - return subjectPublicKey; - } - catch ( final MalformedChallengeException e ) - { - throw new AuthenticationException( e.getMessage(), e ); - } - } - - - private void beginTlsHandshake() throws AuthenticationException - { - try - { - getSSLEngine().beginHandshake(); - } - catch ( final SSLException e ) - { - throw new AuthenticationException( "SSL Engine error: " + e.getMessage(), e ); - } - } - - - private ByteBuffer allocateOutBuffer() - { - final SSLEngine sslEngine = getSSLEngine(); - final SSLSession sslSession = sslEngine.getSession(); - return ByteBuffer.allocate( sslSession.getApplicationBufferSize() ); - } - - - private String wrapHandshake() throws AuthenticationException - { - final ByteBuffer src = allocateOutBuffer(); - src.flip(); - final SSLEngine sslEngine = getSSLEngine(); - final SSLSession sslSession = sslEngine.getSession(); - // Needs to be twice the size as there may be two wraps during handshake. - // Primitive and inefficient solution, but it works. - final ByteBuffer dst = ByteBuffer.allocate( sslSession.getPacketBufferSize() * 2 ); - while ( sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP ) - { - wrap( src, dst ); - } - dst.flip(); - return encodeBase64( dst ); - } - - - private String wrap( final ByteBuffer src ) throws AuthenticationException - { - final SSLEngine sslEngine = getSSLEngine(); - final SSLSession sslSession = sslEngine.getSession(); - final ByteBuffer dst = ByteBuffer.allocate( sslSession.getPacketBufferSize() ); - wrap( src, dst ); - dst.flip(); - return encodeBase64( dst ); - } - - - private void wrap( final ByteBuffer src, final ByteBuffer dst ) throws AuthenticationException - { - final SSLEngine sslEngine = getSSLEngine(); - try - { - final SSLEngineResult engineResult = sslEngine.wrap( src, dst ); - if ( engineResult.getStatus() != Status.OK ) - { - throw new AuthenticationException( "SSL Engine error status: " + engineResult.getStatus() ); - } - } - catch ( final SSLException e ) - { - throw new AuthenticationException( "SSL Engine wrap error: " + e.getMessage(), e ); - } - } - - - private void unwrapHandshake( final String inputString ) throws MalformedChallengeException - { - final SSLEngine sslEngine = getSSLEngine(); - final SSLSession sslSession = sslEngine.getSession(); - final ByteBuffer src = decodeBase64( inputString ); - final ByteBuffer dst = ByteBuffer.allocate( sslSession.getApplicationBufferSize() ); - while ( sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) - { - unwrap( src, dst ); - } - } - - - private ByteBuffer unwrap( final String inputString ) throws MalformedChallengeException - { - final SSLEngine sslEngine = getSSLEngine(); - final SSLSession sslSession = sslEngine.getSession(); - final ByteBuffer src = decodeBase64( inputString ); - final ByteBuffer dst = ByteBuffer.allocate( sslSession.getApplicationBufferSize() ); - unwrap( src, dst ); - dst.flip(); - return dst; - } - - - private void unwrap( final ByteBuffer src, final ByteBuffer dst ) throws MalformedChallengeException - { - - try - { - final SSLEngineResult engineResult = sslEngine.unwrap( src, dst ); - if ( engineResult.getStatus() != Status.OK ) - { - throw new MalformedChallengeException( "SSL Engine error status: " + engineResult.getStatus() ); - } - - if ( sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_TASK ) - { - final Runnable task = sslEngine.getDelegatedTask(); - task.run(); - } - - } - catch ( final SSLException e ) - { - throw new MalformedChallengeException( "SSL Engine unwrap error: " + e.getMessage(), e ); - } - } - - - private String encodeBase64( final ByteBuffer buffer ) - { - final int limit = buffer.limit(); - final byte[] bytes = new byte[limit]; - buffer.get( bytes ); - return new String(Base64.encodeBase64(bytes), Consts.ASCII); - } - - - private ByteBuffer decodeBase64( final String inputString ) - { - final byte[] inputBytes = Base64.decodeBase64(inputString.getBytes(Consts.ASCII)); - final ByteBuffer buffer = ByteBuffer.wrap( inputBytes ); - return buffer; - } - - - @Override - public boolean isComplete() - { - return state == State.CREDENTIALS_SENT; - } - - /** - * Implementation of the TsRequest structure used in CredSSP protocol. - * It is specified in [MS-CPPS] section 2.2.1. - */ - static class CredSspTsRequest - { - - private static final int VERSION = 3; - - private byte[] negoToken; - private byte[] authInfo; - private byte[] pubKeyAuth; - - - protected CredSspTsRequest() - { - super(); - } - - - public static CredSspTsRequest createNegoToken( final byte[] negoToken ) - { - final CredSspTsRequest req = new CredSspTsRequest(); - req.negoToken = negoToken; - return req; - } - - - public static CredSspTsRequest createAuthInfo( final byte[] authInfo ) - { - final CredSspTsRequest req = new CredSspTsRequest(); - req.authInfo = authInfo; - return req; - } - - - public static CredSspTsRequest createDecoded( final ByteBuffer buf ) throws MalformedChallengeException - { - final CredSspTsRequest req = new CredSspTsRequest(); - req.decode( buf ); - return req; - } - - - public byte[] getNegoToken() - { - return negoToken; - } - - - public void setNegoToken( final byte[] negoToken ) - { - this.negoToken = negoToken; - } - - - public byte[] getAuthInfo() - { - return authInfo; - } - - - public void setAuthInfo( final byte[] authInfo ) - { - this.authInfo = authInfo; - } - - - public byte[] getPubKeyAuth() - { - return pubKeyAuth; - } - - - public void setPubKeyAuth( final byte[] pubKeyAuth ) - { - this.pubKeyAuth = pubKeyAuth; - } - - - public void decode( final ByteBuffer buf ) throws MalformedChallengeException - { - negoToken = null; - authInfo = null; - pubKeyAuth = null; - - getByteAndAssert( buf, 0x30, "initial sequence" ); - parseLength( buf ); - - while ( buf.hasRemaining() ) - { - final int contentTag = getAndAssertContentSpecificTag( buf, "content tag" ); - parseLength( buf ); - switch ( contentTag ) - { - case 0: - processVersion( buf ); - break; - case 1: - parseNegoTokens( buf ); - break; - case 2: - parseAuthInfo( buf ); - break; - case 3: - parsePubKeyAuth( buf ); - break; - case 4: - processErrorCode( buf ); - break; - default: - parseError( buf, "unexpected content tag " + contentTag ); - } - } - } - - - private void processVersion( final ByteBuffer buf ) throws MalformedChallengeException - { - getByteAndAssert( buf, 0x02, "version type" ); - getLengthAndAssert( buf, 1, "version length" ); - getByteAndAssert( buf, VERSION, "wrong protocol version" ); - } - - - private void parseNegoTokens( final ByteBuffer buf ) throws MalformedChallengeException - { - getByteAndAssert( buf, 0x30, "negoTokens sequence" ); - parseLength( buf ); - // I have seen both 0x30LL encoding and 0x30LL0x30LL encoding. Accept both. - byte bufByte = buf.get(); - if ( bufByte == 0x30 ) - { - parseLength( buf ); - bufByte = buf.get(); - } - if ( ( bufByte & 0xff ) != 0xa0 ) - { - parseError( buf, "negoTokens: wrong content-specific tag " + String.format( "%02X", bufByte ) ); - } - parseLength( buf ); - getByteAndAssert( buf, 0x04, "negoToken type" ); - - final int tokenLength = parseLength( buf ); - negoToken = new byte[tokenLength]; - buf.get( negoToken ); - } - - - private void parseAuthInfo( final ByteBuffer buf ) throws MalformedChallengeException - { - getByteAndAssert( buf, 0x04, "authInfo type" ); - final int length = parseLength( buf ); - authInfo = new byte[length]; - buf.get( authInfo ); - } - - - private void parsePubKeyAuth( final ByteBuffer buf ) throws MalformedChallengeException - { - getByteAndAssert( buf, 0x04, "pubKeyAuth type" ); - final int length = parseLength( buf ); - pubKeyAuth = new byte[length]; - buf.get( pubKeyAuth ); - } - - - private void processErrorCode( final ByteBuffer buf ) throws MalformedChallengeException - { - getLengthAndAssert( buf, 3, "error code length" ); - getByteAndAssert( buf, 0x02, "error code type" ); - getLengthAndAssert( buf, 1, "error code length" ); - final byte errorCode = buf.get(); - parseError( buf, "Error code " + errorCode ); - } - - - public void encode( final ByteBuffer buf ) - { - final ByteBuffer inner = ByteBuffer.allocate( buf.capacity() ); - - // version tag [0] - inner.put( ( byte ) ( 0x00 | 0xa0 ) ); - inner.put( ( byte ) 3 ); // length - - inner.put( ( byte ) ( 0x02 ) ); // INTEGER tag - inner.put( ( byte ) 1 ); // length - inner.put( ( byte ) VERSION ); // value - - if ( negoToken != null ) - { - int len = negoToken.length; - final byte[] negoTokenLengthBytes = encodeLength( len ); - len += 1 + negoTokenLengthBytes.length; - final byte[] negoTokenLength1Bytes = encodeLength( len ); - len += 1 + negoTokenLength1Bytes.length; - final byte[] negoTokenLength2Bytes = encodeLength( len ); - len += 1 + negoTokenLength2Bytes.length; - final byte[] negoTokenLength3Bytes = encodeLength( len ); - len += 1 + negoTokenLength3Bytes.length; - final byte[] negoTokenLength4Bytes = encodeLength( len ); - - inner.put( ( byte ) ( 0x01 | 0xa0 ) ); // negoData tag [1] - inner.put( negoTokenLength4Bytes ); // length - - inner.put( ( byte ) ( 0x30 ) ); // SEQUENCE tag - inner.put( negoTokenLength3Bytes ); // length - - inner.put( ( byte ) ( 0x30 ) ); // .. of SEQUENCE tag - inner.put( negoTokenLength2Bytes ); // length - - inner.put( ( byte ) ( 0x00 | 0xa0 ) ); // negoToken tag [0] - inner.put( negoTokenLength1Bytes ); // length - - inner.put( ( byte ) ( 0x04 ) ); // OCTET STRING tag - inner.put( negoTokenLengthBytes ); // length - - inner.put( negoToken ); - } - - if ( authInfo != null ) - { - final byte[] authInfoEncodedLength = encodeLength( authInfo.length ); - - inner.put( ( byte ) ( 0x02 | 0xa0 ) ); // authInfo tag [2] - inner.put( encodeLength( 1 + authInfoEncodedLength.length + authInfo.length ) ); // length - - inner.put( ( byte ) ( 0x04 ) ); // OCTET STRING tag - inner.put( authInfoEncodedLength ); - inner.put( authInfo ); - } - - if ( pubKeyAuth != null ) - { - final byte[] pubKeyAuthEncodedLength = encodeLength( pubKeyAuth.length ); - - inner.put( ( byte ) ( 0x03 | 0xa0 ) ); // pubKeyAuth tag [3] - inner.put( encodeLength( 1 + pubKeyAuthEncodedLength.length + pubKeyAuth.length ) ); // length - - inner.put( ( byte ) ( 0x04 ) ); // OCTET STRING tag - inner.put( pubKeyAuthEncodedLength ); - inner.put( pubKeyAuth ); - } - - inner.flip(); - - // SEQUENCE tag - buf.put( ( byte ) ( 0x10 | 0x20 ) ); - buf.put( encodeLength( inner.limit() ) ); - buf.put( inner ); - } - - - public String debugDump() - { - final StringBuilder sb = new StringBuilder( "TsRequest\n" ); - sb.append( " negoToken:\n" ); - sb.append( " " ); - DebugUtil.dump( sb, negoToken ); - sb.append( "\n" ); - sb.append( " authInfo:\n" ); - sb.append( " " ); - DebugUtil.dump( sb, authInfo ); - sb.append( "\n" ); - sb.append( " pubKeyAuth:\n" ); - sb.append( " " ); - DebugUtil.dump( sb, pubKeyAuth ); - return sb.toString(); - } - - - @Override - public String toString() - { - return "TsRequest(negoToken=" + Arrays.toString( negoToken ) + ", authInfo=" - + Arrays.toString( authInfo ) + ", pubKeyAuth=" + Arrays.toString( pubKeyAuth ) + ")"; - } - } - - static void getByteAndAssert( final ByteBuffer buf, final int expectedValue, final String errorMessage ) - throws MalformedChallengeException - { - final byte bufByte = buf.get(); - if ( bufByte != expectedValue ) - { - parseError( buf, errorMessage + expectMessage( expectedValue, bufByte ) ); - } - } - - private static String expectMessage( final int expectedValue, final int realValue ) - { - return "(expected " + String.format( "%02X", expectedValue ) + ", got " + String.format( "%02X", realValue ) - + ")"; - } - - static int parseLength( final ByteBuffer buf ) - { - byte bufByte = buf.get(); - if ( bufByte == 0x80 ) - { - return -1; // infinite - } - if ( ( bufByte & 0x80 ) == 0x80 ) - { - final int size = bufByte & 0x7f; - int length = 0; - for ( int i = 0; i < size; i++ ) - { - bufByte = buf.get(); - length = ( length << 8 ) + ( bufByte & 0xff ); - } - return length; - } - else - { - return bufByte; - } - } - - static void getLengthAndAssert( final ByteBuffer buf, final int expectedValue, final String errorMessage ) - throws MalformedChallengeException - { - final int bufLength = parseLength( buf ); - if ( expectedValue != bufLength ) - { - parseError( buf, errorMessage + expectMessage( expectedValue, bufLength ) ); - } - } - - static int getAndAssertContentSpecificTag( final ByteBuffer buf, final String errorMessage ) throws MalformedChallengeException - { - final byte bufByte = buf.get(); - if ( ( bufByte & 0xe0 ) != 0xa0 ) - { - parseError( buf, errorMessage + ": wrong content-specific tag " + String.format( "%02X", bufByte ) ); - } - final int tag = bufByte & 0x1f; - return tag; - } - - static void parseError( final ByteBuffer buf, final String errorMessage ) throws MalformedChallengeException - { - throw new MalformedChallengeException( - "Error parsing TsRequest (position:" + buf.position() + "): " + errorMessage ); - } - - static byte[] encodeLength( final int length ) - { - if ( length < 128 ) - { - final byte[] encoded = new byte[1]; - encoded[0] = ( byte ) length; - return encoded; - } - - int size = 1; - - int val = length; - while ( ( val >>>= 8 ) != 0 ) - { - size++; - } - - final byte[] encoded = new byte[1 + size]; - encoded[0] = ( byte ) ( size | 0x80 ); - - int shift = ( size - 1 ) * 8; - for ( int i = 0; i < size; i++ ) - { - encoded[i + 1] = ( byte ) ( length >> shift ); - shift -= 8; - } - - return encoded; - } - -} diff -Nru httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/impl/auth/DebugUtil.java httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/impl/auth/DebugUtil.java --- httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/impl/auth/DebugUtil.java 2017-11-27 08:54:33.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/impl/auth/DebugUtil.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,96 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.apache.http.impl.auth; - - -import java.nio.ByteBuffer; - - -/** - * Simple debugging utility class for CredSSP and NTLM implementations. - */ -class DebugUtil -{ - - public static String dump( final ByteBuffer buf ) - { - final ByteBuffer dup = buf.duplicate(); - final StringBuilder sb = new StringBuilder( dup.toString() ); - sb.append( ": " ); - while ( dup.position() < dup.limit() ) - { - sb.append( String.format( "%02X ", dup.get() ) ); - } - return sb.toString(); - } - - - public static void dump( final StringBuilder sb, final byte[] bytes ) - { - if ( bytes == null ) - { - sb.append( "null" ); - return; - } - for ( final byte b : bytes ) - { - sb.append( String.format( "%02X ", b ) ); - } - } - - - public static String dump( final byte[] bytes ) - { - final StringBuilder sb = new StringBuilder(); - dump( sb, bytes ); - return sb.toString(); - } - - - public static byte[] fromHex( final String hex ) - { - int i = 0; - final byte[] bytes = new byte[200000]; - int h = 0; - while ( h < hex.length() ) - { - if ( hex.charAt( h ) == ' ' ) - { - h++; - } - final String str = hex.substring( h, h + 2 ); - bytes[i] = ( byte ) Integer.parseInt( str, 16 ); - i++; - h = h + 2; - } - final byte[] outbytes = new byte[i]; - System.arraycopy( bytes, 0, outbytes, 0, i ); - return outbytes; - } - -} diff -Nru httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java --- httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java 2017-11-25 10:09:18.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/impl/auth/NTLMEngineImpl.java 2018-01-18 10:44:19.000000000 +0000 @@ -1,2102 +1,2102 @@ -/* - * ==================================================================== - * 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.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; - -/** - * Provides an implementation for NTLMv1, NTLMv2, and NTLM2 Session forms of the NTLM - * authentication protocol. - * - * @since 4.1 - */ -final class NTLMEngineImpl implements NTLMEngine { - - /** Unicode encoding */ - private static final Charset UNICODE_LITTLE_UNMARKED = Charset.forName("UnicodeLittleUnmarked"); - /** Character encoding */ - private static final Charset DEFAULT_CHARSET = Consts.ASCII; - - // Flags we use; descriptions according to: - // http://davenport.sourceforge.net/ntlm.html - // and - // http://msdn.microsoft.com/en-us/library/cc236650%28v=prot.20%29.aspx - // [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; - static { - java.security.SecureRandom rnd = null; - try { - rnd = java.security.SecureRandom.getInstance("SHA1PRNG"); - } catch (final Exception ignore) { - } - RND_GEN = rnd; - } - - /** The signature string as bytes in the default encoding */ - private static final byte[] SIGNATURE = getNullTerminatedAsciiString("NTLMSSP"); - - // 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(); - - NTLMEngineImpl() { - } - - /** - * Creates the first message (type 1 message) in the NTLM authentication - * sequence. This message includes the user name, domain and host for the - * authentication session. - * - * @param host - * the computer name of the host requesting authentication. - * @param domain - * 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) { - // For compatibility reason do not include domain and host in type 1 message - //return new Type1Message(domain, host).getResponse(); - return TYPE_1_MESSAGE; - } - - /** - * 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) - throws NTLMEngineException { - return new Type3Message(domain, host, user, password, nonce, type2Flags, target, - targetInformation).getResponse(); - } - - /** - * 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 { - if (src.length < index + 4) { - return 0; - } - return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8) - | ((src[index + 2] & 0xff) << 16) | ((src[index + 3] & 0xff) << 24); - } - - private static int readUShort(final byte[] src, final int index) throws NTLMEngineException { - if (src.length < index + 2) { - return 0; - } - return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8); - } - - private static byte[] readSecurityBuffer(final byte[] src, final int index) throws NTLMEngineException { - final int length = readUShort(src, index); - final int offset = readULong(src, index + 4); - if (src.length < offset + length) { - return new byte[length]; - } - final byte[] buffer = new byte[length]; - System.arraycopy(src, offset, buffer, 0, length); - return buffer; - } - - /** Calculate a challenge block */ - private static byte[] makeRandomChallenge(final Random random) throws NTLMEngineException { - final byte[] rval = new byte[8]; - synchronized (random) { - random.nextBytes(rval); - } - return rval; - } - - /** Calculate a 16-byte secondary key */ - private static byte[] makeSecondaryKey(final Random random) throws NTLMEngineException { - final byte[] rval = new byte[16]; - 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; - protected final byte[] challenge; - protected final String target; - protected final byte[] targetInformation; - - // Information we can generate but may be passed in (for testing) - protected byte[] clientChallenge; - protected byte[] clientChallenge2; - protected byte[] secondaryKey; - protected byte[] timestamp; - - // Stuff we always generate - protected byte[] lmHash = null; - protected byte[] lmResponse = null; - protected byte[] ntlmHash = null; - protected byte[] ntlmResponse = null; - protected byte[] ntlmv2Hash = null; - protected byte[] lmv2Hash = null; - protected byte[] lmv2Response = null; - protected byte[] ntlmv2Blob = null; - protected byte[] ntlmv2Response = null; - protected byte[] ntlm2SessionResponse = null; - protected byte[] lm2SessionResponse = null; - protected byte[] lmUserSessionKey = null; - protected byte[] ntlmUserSessionKey = null; - protected byte[] ntlmv2UserSessionKey = null; - 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; - this.password = password; - this.challenge = challenge; - this.targetInformation = targetInformation; - this.clientChallenge = clientChallenge; - this.clientChallenge2 = clientChallenge2; - this.secondaryKey = secondaryKey; - this.timestamp = timestamp; - } - - @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(random); - } - return clientChallenge; - } - - /** Calculate and return second client challenge */ - public byte[] getClientChallenge2() - throws NTLMEngineException { - if (clientChallenge2 == null) { - clientChallenge2 = makeRandomChallenge(random); - } - return clientChallenge2; - } - - /** Calculate and return random secondary key */ - public byte[] getSecondaryKey() - throws NTLMEngineException { - if (secondaryKey == null) { - secondaryKey = makeSecondaryKey(random); - } - return secondaryKey; - } - - /** Calculate and return the LMHash */ - public byte[] getLMHash() - throws NTLMEngineException { - if (lmHash == null) { - lmHash = lmHash(password); - } - return lmHash; - } - - /** Calculate and return the LMResponse */ - public byte[] getLMResponse() - throws NTLMEngineException { - if (lmResponse == null) { - lmResponse = lmResponse(getLMHash(),challenge); - } - return lmResponse; - } - - /** Calculate and return the NTLMHash */ - public byte[] getNTLMHash() - throws NTLMEngineException { - if (ntlmHash == null) { - ntlmHash = ntlmHash(password); - } - return ntlmHash; - } - - /** Calculate and return the NTLMResponse */ - public byte[] getNTLMResponse() - throws NTLMEngineException { - if (ntlmResponse == null) { - ntlmResponse = lmResponse(getNTLMHash(),challenge); - } - return ntlmResponse; - } - - /** Calculate the LMv2 hash */ - public byte[] getLMv2Hash() - throws NTLMEngineException { - if (lmv2Hash == null) { - lmv2Hash = lmv2Hash(domain, user, getNTLMHash()); - } - return lmv2Hash; - } - - /** Calculate the NTLMv2 hash */ - public byte[] getNTLMv2Hash() - throws NTLMEngineException { - if (ntlmv2Hash == null) { - ntlmv2Hash = ntlmv2Hash(domain, user, getNTLMHash()); - } - return ntlmv2Hash; - } - - /** Calculate a timestamp */ - public byte[] getTimestamp() { - if (timestamp == null) { - long time = this.currentTime; - time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch. - time *= 10000; // tenths of a microsecond. - // convert to little-endian byte array. - timestamp = new byte[8]; - for (int i = 0; i < 8; i++) { - timestamp[i] = (byte) time; - time >>>= 8; - } - } - return timestamp; - } - - /** Calculate the NTLMv2Blob */ - public byte[] getNTLMv2Blob() - throws NTLMEngineException { - if (ntlmv2Blob == null) { - ntlmv2Blob = createBlob(getClientChallenge2(), targetInformation, getTimestamp()); - } - return ntlmv2Blob; - } - - /** Calculate the NTLMv2Response */ - public byte[] getNTLMv2Response() - throws NTLMEngineException { - if (ntlmv2Response == null) { - ntlmv2Response = lmv2Response(getNTLMv2Hash(),challenge,getNTLMv2Blob()); - } - return ntlmv2Response; - } - - /** Calculate the LMv2Response */ - public byte[] getLMv2Response() - throws NTLMEngineException { - if (lmv2Response == null) { - lmv2Response = lmv2Response(getLMv2Hash(),challenge,getClientChallenge()); - } - return lmv2Response; - } - - /** Get NTLM2SessionResponse */ - public byte[] getNTLM2SessionResponse() - throws NTLMEngineException { - if (ntlm2SessionResponse == null) { - ntlm2SessionResponse = ntlm2SessionResponse(getNTLMHash(),challenge,getClientChallenge()); - } - return ntlm2SessionResponse; - } - - /** Calculate and return LM2 session response */ - public byte[] getLM2SessionResponse() - throws NTLMEngineException { - if (lm2SessionResponse == null) { - final byte[] clntChallenge = getClientChallenge(); - lm2SessionResponse = new byte[24]; - System.arraycopy(clntChallenge, 0, lm2SessionResponse, 0, clntChallenge.length); - Arrays.fill(lm2SessionResponse, clntChallenge.length, lm2SessionResponse.length, (byte) 0x00); - } - return lm2SessionResponse; - } - - /** Get LMUserSessionKey */ - public byte[] getLMUserSessionKey() - throws NTLMEngineException { - if (lmUserSessionKey == null) { - lmUserSessionKey = new byte[16]; - System.arraycopy(getLMHash(), 0, lmUserSessionKey, 0, 8); - Arrays.fill(lmUserSessionKey, 8, 16, (byte) 0x00); - } - return lmUserSessionKey; - } - - /** Get NTLMUserSessionKey */ - public byte[] getNTLMUserSessionKey() - throws NTLMEngineException { - if (ntlmUserSessionKey == null) { - final MD4 md4 = new MD4(); - md4.update(getNTLMHash()); - ntlmUserSessionKey = md4.getOutput(); - } - return ntlmUserSessionKey; - } - - /** GetNTLMv2UserSessionKey */ - public byte[] getNTLMv2UserSessionKey() - throws NTLMEngineException { - if (ntlmv2UserSessionKey == null) { - final byte[] ntlmv2hash = getNTLMv2Hash(); - final byte[] truncatedResponse = new byte[16]; - System.arraycopy(getNTLMv2Response(), 0, truncatedResponse, 0, 16); - ntlmv2UserSessionKey = hmacMD5(truncatedResponse, ntlmv2hash); - } - return ntlmv2UserSessionKey; - } - - /** Get NTLM2SessionResponseUserSessionKey */ - public byte[] getNTLM2SessionResponseUserSessionKey() - throws NTLMEngineException { - if (ntlm2SessionResponseUserSessionKey == null) { - final byte[] ntlm2SessionResponseNonce = getLM2SessionResponse(); - final byte[] sessionNonce = new byte[challenge.length + ntlm2SessionResponseNonce.length]; - System.arraycopy(challenge, 0, sessionNonce, 0, challenge.length); - System.arraycopy(ntlm2SessionResponseNonce, 0, sessionNonce, challenge.length, ntlm2SessionResponseNonce.length); - ntlm2SessionResponseUserSessionKey = hmacMD5(sessionNonce,getNTLMUserSessionKey()); - } - return ntlm2SessionResponseUserSessionKey; - } - - /** Get LAN Manager session key */ - public byte[] getLanManagerSessionKey() - throws NTLMEngineException { - if (lanManagerSessionKey == null) { - try { - final byte[] keyBytes = new byte[14]; - System.arraycopy(getLMHash(), 0, keyBytes, 0, 8); - Arrays.fill(keyBytes, 8, keyBytes.length, (byte)0xbd); - final Key lowKey = createDESKey(keyBytes, 0); - final Key highKey = createDESKey(keyBytes, 7); - final byte[] truncatedResponse = new byte[8]; - System.arraycopy(getLMResponse(), 0, truncatedResponse, 0, truncatedResponse.length); - Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); - des.init(Cipher.ENCRYPT_MODE, lowKey); - final byte[] lowPart = des.doFinal(truncatedResponse); - des = Cipher.getInstance("DES/ECB/NoPadding"); - des.init(Cipher.ENCRYPT_MODE, highKey); - final byte[] highPart = des.doFinal(truncatedResponse); - lanManagerSessionKey = new byte[16]; - System.arraycopy(lowPart, 0, lanManagerSessionKey, 0, lowPart.length); - System.arraycopy(highPart, 0, lanManagerSessionKey, lowPart.length, highPart.length); - } catch (final Exception e) { - throw new NTLMEngineException(e.getMessage(), e); - } - } - return lanManagerSessionKey; - } - } - - /** Calculates HMAC-MD5 */ - static byte[] hmacMD5(final byte[] value, final byte[] key) - throws NTLMEngineException { - final HMACMD5 hmacMD5 = new HMACMD5(key); - hmacMD5.update(value); - return hmacMD5.getOutput(); - } - - /** Calculates RC4 */ - static byte[] RC4(final byte[] value, final byte[] key) - throws NTLMEngineException { - try { - final Cipher rc4 = Cipher.getInstance("RC4"); - rc4.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "RC4")); - return rc4.doFinal(value); - } catch (final Exception e) { - throw new NTLMEngineException(e.getMessage(), e); - } - } - - /** - * Calculates the NTLM2 Session Response for the given challenge, using the - * specified password and client challenge. - * - * @return The NTLM2 Session Response. This is placed in the NTLM response - * field of the Type 3 message; the LM response field contains the - * client challenge, null-padded to 24 bytes. - */ - static byte[] ntlm2SessionResponse(final byte[] ntlmHash, final byte[] challenge, - final byte[] clientChallenge) throws NTLMEngineException { - try { - final MessageDigest md5 = getMD5(); - md5.update(challenge); - md5.update(clientChallenge); - final byte[] digest = md5.digest(); - - final byte[] sessionHash = new byte[8]; - System.arraycopy(digest, 0, sessionHash, 0, 8); - return lmResponse(ntlmHash, sessionHash); - } catch (final Exception e) { - if (e instanceof NTLMEngineException) { - throw (NTLMEngineException) e; - } - throw new NTLMEngineException(e.getMessage(), e); - } - } - - /** - * Creates the LM Hash of the user's password. - * - * @param password - * The password. - * - * @return The LM Hash of the given password, used in the calculation of the - * LM Response. - */ - private static byte[] lmHash(final String password) throws NTLMEngineException { - try { - final byte[] oemPassword = password.toUpperCase(Locale.ROOT).getBytes(Consts.ASCII); - final int length = Math.min(oemPassword.length, 14); - final byte[] keyBytes = new byte[14]; - System.arraycopy(oemPassword, 0, keyBytes, 0, length); - final Key lowKey = createDESKey(keyBytes, 0); - final Key highKey = createDESKey(keyBytes, 7); - final byte[] magicConstant = "KGS!@#$%".getBytes(Consts.ASCII); - final Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); - des.init(Cipher.ENCRYPT_MODE, lowKey); - final byte[] lowHash = des.doFinal(magicConstant); - des.init(Cipher.ENCRYPT_MODE, highKey); - final byte[] highHash = des.doFinal(magicConstant); - final byte[] lmHash = new byte[16]; - System.arraycopy(lowHash, 0, lmHash, 0, 8); - System.arraycopy(highHash, 0, lmHash, 8, 8); - return lmHash; - } catch (final Exception e) { - throw new NTLMEngineException(e.getMessage(), e); - } - } - - /** - * Creates the NTLM Hash of the user's password. - * - * @param password - * The password. - * - * @return The NTLM Hash of the given password, used in the calculation of - * the NTLM Response and the NTLMv2 and LMv2 Hashes. - */ - private static byte[] ntlmHash(final String password) throws NTLMEngineException { - if (UNICODE_LITTLE_UNMARKED == null) { - throw new NTLMEngineException("Unicode not supported"); - } - final byte[] unicodePassword = password.getBytes(UNICODE_LITTLE_UNMARKED); - final MD4 md4 = new MD4(); - md4.update(unicodePassword); - return md4.getOutput(); - } - - /** - * Creates the LMv2 Hash of the user's password. - * - * @return The LMv2 Hash, used in the calculation of the NTLMv2 and LMv2 - * Responses. - */ - private static byte[] lmv2Hash(final String domain, final String user, final byte[] ntlmHash) - throws NTLMEngineException { - if (UNICODE_LITTLE_UNMARKED == null) { - throw new NTLMEngineException("Unicode not supported"); - } - final HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); - // Upper case username, upper case domain! - hmacMD5.update(user.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED)); - if (domain != null) { - hmacMD5.update(domain.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED)); - } - return hmacMD5.getOutput(); - } - - /** - * Creates the NTLMv2 Hash of the user's password. - * - * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 and LMv2 - * Responses. - */ - private static byte[] ntlmv2Hash(final String domain, final String user, final byte[] ntlmHash) - throws NTLMEngineException { - if (UNICODE_LITTLE_UNMARKED == null) { - throw new NTLMEngineException("Unicode not supported"); - } - final HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); - // Upper case username, mixed case target!! - hmacMD5.update(user.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED)); - if (domain != null) { - hmacMD5.update(domain.getBytes(UNICODE_LITTLE_UNMARKED)); - } - return hmacMD5.getOutput(); - } - - /** - * Creates the LM Response from the given hash and Type 2 challenge. - * - * @param hash - * The LM or NTLM Hash. - * @param challenge - * The server challenge from the Type 2 message. - * - * @return The response (either LM or NTLM, depending on the provided hash). - */ - private static byte[] lmResponse(final byte[] hash, final byte[] challenge) throws NTLMEngineException { - try { - final byte[] keyBytes = new byte[21]; - System.arraycopy(hash, 0, keyBytes, 0, 16); - final Key lowKey = createDESKey(keyBytes, 0); - final Key middleKey = createDESKey(keyBytes, 7); - final Key highKey = createDESKey(keyBytes, 14); - final Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); - des.init(Cipher.ENCRYPT_MODE, lowKey); - final byte[] lowResponse = des.doFinal(challenge); - des.init(Cipher.ENCRYPT_MODE, middleKey); - final byte[] middleResponse = des.doFinal(challenge); - des.init(Cipher.ENCRYPT_MODE, highKey); - final byte[] highResponse = des.doFinal(challenge); - final byte[] lmResponse = new byte[24]; - System.arraycopy(lowResponse, 0, lmResponse, 0, 8); - System.arraycopy(middleResponse, 0, lmResponse, 8, 8); - System.arraycopy(highResponse, 0, lmResponse, 16, 8); - return lmResponse; - } catch (final Exception e) { - throw new NTLMEngineException(e.getMessage(), e); - } - } - - /** - * Creates the LMv2 Response from the given hash, client data, and Type 2 - * challenge. - * - * @param hash - * The NTLMv2 Hash. - * @param clientData - * The client data (blob or client challenge). - * @param challenge - * The server challenge from the Type 2 message. - * - * @return The response (either NTLMv2 or LMv2, depending on the client - * data). - */ - private static byte[] lmv2Response(final byte[] hash, final byte[] challenge, final byte[] clientData) - throws NTLMEngineException { - final HMACMD5 hmacMD5 = new HMACMD5(hash); - hmacMD5.update(challenge); - hmacMD5.update(clientData); - final byte[] mac = hmacMD5.getOutput(); - final byte[] lmv2Response = new byte[mac.length + clientData.length]; - System.arraycopy(mac, 0, lmv2Response, 0, mac.length); - System.arraycopy(clientData, 0, lmv2Response, mac.length, clientData.length); - return lmv2Response; - } - - static enum Mode - { - CLIENT, SERVER; - } - - static class Handle - { - final private byte[] exportedSessionKey; - private byte[] signingKey; - private byte[] sealingKey; - private final 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 - { - final 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 ( final 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. - * - * @param targetInformation - * The target information block from the Type 2 message. - * @param clientChallenge - * The random 8-byte client challenge. - * - * @return The blob, used in the calculation of the NTLMv2 Response. - */ - private static byte[] createBlob(final byte[] clientChallenge, final byte[] targetInformation, final byte[] timestamp) { - final byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00 }; - final byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; - final byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; - final byte[] unknown2 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; - final byte[] blob = new byte[blobSignature.length + reserved.length + timestamp.length + 8 - + unknown1.length + targetInformation.length + unknown2.length]; - int offset = 0; - System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length); - offset += blobSignature.length; - System.arraycopy(reserved, 0, blob, offset, reserved.length); - offset += reserved.length; - System.arraycopy(timestamp, 0, blob, offset, timestamp.length); - offset += timestamp.length; - System.arraycopy(clientChallenge, 0, blob, offset, 8); - offset += 8; - System.arraycopy(unknown1, 0, blob, offset, unknown1.length); - offset += unknown1.length; - System.arraycopy(targetInformation, 0, blob, offset, targetInformation.length); - offset += targetInformation.length; - System.arraycopy(unknown2, 0, blob, offset, unknown2.length); - offset += unknown2.length; - return blob; - } - - /** - * Creates a DES encryption key from the given key material. - * - * @param bytes - * A byte array containing the DES key material. - * @param offset - * The offset in the given byte array at which the 7-byte key - * material starts. - * - * @return A DES encryption key created from the key material starting at - * the specified offset in the given byte array. - */ - private static Key createDESKey(final byte[] bytes, final int offset) { - final byte[] keyBytes = new byte[7]; - System.arraycopy(bytes, offset, keyBytes, 0, 7); - final byte[] material = new byte[8]; - material[0] = keyBytes[0]; - material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >>> 1); - material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >>> 2); - material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >>> 3); - material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >>> 4); - material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >>> 5); - material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6); - material[7] = (byte) (keyBytes[6] << 1); - oddParity(material); - return new SecretKeySpec(material, "DES"); - } - - /** - * Applies odd parity to the given byte array. - * - * @param bytes - * The data whose parity bits are to be adjusted for odd parity. - */ - private static void oddParity(final byte[] bytes) { - for (int i = 0; i < bytes.length; i++) { - final byte b = bytes[i]; - final boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ (b >>> 4) ^ (b >>> 3) - ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0; - if (needsParity) { - bytes[i] |= (byte) 0x01; - } else { - bytes[i] &= (byte) 0xfe; - } - } - } - - /** - * 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 */ - protected byte[] messageContents = null; - - /** The current output position */ - protected int currentOutputPosition = 0; - - /** Constructor to use when message contents are not yet known */ - NTLMMessage() { - } - - /** Constructor taking a string */ - NTLMMessage(final String messageBody, final int expectedType) throws NTLMEngineException { - 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"); - } - int i = 0; - while (i < SIGNATURE.length) { - if (messageContents[i] != SIGNATURE[i]) { - throw new NTLMEngineException( - "NTLM message expected - instead got unrecognized bytes"); - } - i++; - } - - // Check to be sure there's a type 2 message indicator next - final int type = readULong(SIGNATURE.length); - if (type != expectedType) { - throw new NTLMEngineException("NTLM type " + Integer.toString(expectedType) - + " message expected - instead got type " + Integer.toString(type)); - } - - currentOutputPosition = messageContents.length; - } - - /** - * Get the length of the signature and flags, so calculations can adjust - * offsets accordingly. - */ - protected int getPreambleLength() { - return SIGNATURE.length + 4; - } - - /** Get the message length */ - protected int getMessageLength() { - return currentOutputPosition; - } - - /** Read a byte from a position within the message buffer */ - protected byte readByte(final int position) throws NTLMEngineException { - if (messageContents.length < position + 1) { - throw new NTLMEngineException("NTLM: Message too short"); - } - return messageContents[position]; - } - - /** Read a bunch of bytes from a position in the message buffer */ - protected void readBytes(final byte[] buffer, final int position) throws NTLMEngineException { - if (messageContents.length < position + buffer.length) { - throw new NTLMEngineException("NTLM: Message too short"); - } - System.arraycopy(messageContents, position, buffer, 0, buffer.length); - } - - /** Read a ushort from a position within the message buffer */ - protected int readUShort(final int position) throws NTLMEngineException { - return NTLMEngineImpl.readUShort(messageContents, position); - } - - /** Read a ulong from a position within the message buffer */ - protected int readULong(final int position) throws NTLMEngineException { - return NTLMEngineImpl.readULong(messageContents, position); - } - - /** Read a security buffer from a position within the message buffer */ - protected byte[] readSecurityBuffer(final int position) throws NTLMEngineException { - return NTLMEngineImpl.readSecurityBuffer(messageContents, position); - } - - /** - * Prepares the object to create a response of the given length. - * - * @param maxlength - * the maximum length of the response to prepare, - * including the type and the signature (which this method - * adds). - */ - protected void prepareResponse(final int maxlength, final int messageType) { - messageContents = new byte[maxlength]; - currentOutputPosition = 0; - addBytes(SIGNATURE); - addULong(messageType); - } - - /** - * Adds the given byte to the response. - * - * @param b - * the byte to add. - */ - protected void addByte(final byte b) { - messageContents[currentOutputPosition] = b; - currentOutputPosition++; - } - - /** - * Adds the given bytes to the response. - * - * @param bytes - * the bytes to add. - */ - protected void addBytes(final byte[] bytes) { - if (bytes == null) { - return; - } - for (final byte b : bytes) { - messageContents[currentOutputPosition] = b; - currentOutputPosition++; - } - } - - /** Adds a USHORT to the response */ - protected void addUShort(final int value) { - addByte((byte) (value & 0xff)); - addByte((byte) (value >> 8 & 0xff)); - } - - /** Adds a ULong to the response */ - protected void addULong(final int value) { - addByte((byte) (value & 0xff)); - addByte((byte) (value >> 8 & 0xff)); - addByte((byte) (value >> 16 & 0xff)); - addByte((byte) (value >> 24 & 0xff)); - } - - /** - * Returns the response that has been generated after shrinking the - * array if required and base64 encodes the response. - * - * @return The response as above. - */ - 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 */ - static class Type1Message extends NTLMMessage { - - 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(); - this.flags = ((flags == null)?getDefaultFlags():flags); - - // Strip off domain name from the host! - final String unqualifiedHost = convertHost(host); - // Use only the base domain name! - final String unqualifiedDomain = convertDomain(domain); - - hostBytes = unqualifiedHost != null ? - unqualifiedHost.getBytes(UNICODE_LITTLE_UNMARKED) : null; - domainBytes = unqualifiedDomain != null ? - unqualifiedDomain.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED) : null; - } - - Type1Message() { - 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 - 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 + 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(flags); - - // Domain length (two times). - addUShort(domainBytesLength); - addUShort(domainBytesLength); - - // Domain offset. - addULong(hostBytesLength + 32 + 8); - - // Host length (two times). - addUShort(hostBytesLength); - addUShort(hostBytesLength); - - // Host offset (always 32 + 8). - addULong(32 + 8); - - // Version - addUShort(0x0105); - // Build - addULong(2600); - // NTLM revision - addUShort(0x0f00); - - // Host (workstation) String. - if (hostBytes != null) { - addBytes(hostBytes); - } - // Domain String. - if (domainBytes != null) { - addBytes(domainBytes); - } - } - - } - - /** Type 2 message class */ - static class Type2Message extends NTLMMessage { - protected final byte[] challenge; - protected String target; - protected byte[] targetInfo; - protected final int flags; - - 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: - // First 8 bytes: NTLMSSP[0] - // Next 4 bytes: Ulong, value 2 - // Next 8 bytes, starting at offset 12: target field (2 ushort lengths, 1 ulong offset) - // Next 4 bytes, starting at offset 20: Flags, e.g. 0x22890235 - // Next 8 bytes, starting at offset 24: Challenge - // Next 8 bytes, starting at offset 32: ??? (8 bytes of zeros) - // Next 8 bytes, starting at offset 40: targetinfo field (2 ushort lengths, 1 ulong offset) - // Next 2 bytes, major/minor version number (e.g. 0x05 0x02) - // Next 8 bytes, build number - // Next 2 bytes, protocol version number (e.g. 0x00 0x0f) - // Next, various text fields, and a ushort of value 0 at the end - - // Parse out the rest of the info we need from the message - // The nonce is the 8 bytes starting from the byte in position 24. - challenge = new byte[8]; - readBytes(challenge, 24); - - flags = readULong(20); - - // Do the target! - target = null; - // The TARGET_DESIRED flag is said to not have understood semantics - // in Type2 messages, so use the length of the packet to decide - // how to proceed instead - if (getMessageLength() >= 12 + 8) { - final byte[] bytes = readSecurityBuffer(12); - if (bytes.length != 0) { - target = new String(bytes, getCharset(flags)); - } - } - - // Do the target info! - targetInfo = null; - // TARGET_DESIRED flag cannot be relied on, so use packet length - if (getMessageLength() >= 40 + 8) { - final byte[] bytes = readSecurityBuffer(40); - if (bytes.length != 0) { - targetInfo = bytes; - } - } - } - - /** Retrieve the challenge */ - byte[] getChallenge() { - return challenge; - } - - /** Retrieve the target */ - String getTarget() { - return target; - } - - /** Retrieve the target info */ - byte[] getTargetInfo() { - return targetInfo; - } - - /** Retrieve the response flags */ - int getFlags() { - return flags; - } - - } - - /** 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 final int type2Flags; - - protected final byte[] domainBytes; - protected final byte[] hostBytes; - protected final byte[] userBytes; - - protected byte[] lmResp; - protected byte[] ntResp; - 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, - 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); - - 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. - byte[] userSessionKey; - try { - // This conditional may not work on Windows Server 2008 R2 and above, where it has not yet - // been tested - if (((type2Flags & FLAG_TARGETINFO_PRESENT) != 0) && - targetInformation != null && target != null) { - // NTLMv2 - ntResp = gen.getNTLMv2Response(); - lmResp = gen.getLMv2Response(); - if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { - userSessionKey = gen.getLanManagerSessionKey(); - } else { - userSessionKey = gen.getNTLMv2UserSessionKey(); - } - } else { - // NTLMv1 - if ((type2Flags & FLAG_REQUEST_NTLM2_SESSION) != 0) { - // NTLM2 session stuff is requested - ntResp = gen.getNTLM2SessionResponse(); - lmResp = gen.getLM2SessionResponse(); - if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { - userSessionKey = gen.getLanManagerSessionKey(); - } else { - userSessionKey = gen.getNTLM2SessionResponseUserSessionKey(); - } - } else { - ntResp = gen.getNTLMResponse(); - lmResp = gen.getLMResponse(); - if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { - userSessionKey = gen.getLanManagerSessionKey(); - } else { - userSessionKey = gen.getNTLMUserSessionKey(); - } - } - } - } catch (final NTLMEngineException e) { - // This likely means we couldn't find the MD4 hash algorithm - - // fail back to just using LM - ntResp = new byte[0]; - lmResp = gen.getLMResponse(); - if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { - userSessionKey = gen.getLanManagerSessionKey(); - } else { - userSessionKey = gen.getLMUserSessionKey(); - } - } - - if ((type2Flags & FLAG_REQUEST_SIGN) != 0) { - if ((type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) != 0) { - 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; - } - 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 - protected void buildMessage() { - final int ntRespLen = ntResp.length; - final int lmRespLen = lmResp.length; - - final int domainLen = domainBytes != null ? domainBytes.length : 0; - final int hostLen = hostBytes != null ? hostBytes.length: 0; - final int userLen = userBytes.length; - final int sessionKeyLen; - if (sessionKey != null) { - sessionKeyLen = sessionKey.length; - } else { - sessionKeyLen = 0; - } - - // Calculate the layout within the packet - 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; - final int hostOffset = userOffset + userLen; - final int sessionKeyOffset = hostOffset + hostLen; - final int finalLength = sessionKeyOffset + sessionKeyLen; - - // Start the response. Length includes signature and type - prepareResponse(finalLength, 3); - - // LM Resp Length (twice) - addUShort(lmRespLen); - addUShort(lmRespLen); - - // LM Resp Offset - addULong(lmRespOffset); - - // NT Resp Length (twice) - addUShort(ntRespLen); - addUShort(ntRespLen); - - // NT Resp Offset - addULong(ntRespOffset); - - // Domain length (twice) - addUShort(domainLen); - addUShort(domainLen); - - // Domain offset. - addULong(domainOffset); - - // User Length (twice) - addUShort(userLen); - addUShort(userLen); - - // User offset - addULong(userOffset); - - // Host length (twice) - addUShort(hostLen); - addUShort(hostLen); - - // Host offset - addULong(hostOffset); - - // Session key length (twice) - addUShort(sessionKeyLen); - addUShort(sessionKeyLen); - - // Session key offset - addULong(sessionKeyOffset); - - // Flags. - addULong( - /* - //FLAG_WORKSTATION_PRESENT | - //FLAG_DOMAIN_PRESENT | - - // Required flags - (type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) | - (type2Flags & FLAG_REQUEST_NTLMv1) | - (type2Flags & FLAG_REQUEST_NTLM2_SESSION) | - - // Protocol version request - FLAG_REQUEST_VERSION | - - // Recommended privacy settings - (type2Flags & FLAG_REQUEST_ALWAYS_SIGN) | - (type2Flags & FLAG_REQUEST_SEAL) | - (type2Flags & FLAG_REQUEST_SIGN) | - - // These must be set according to documentation, based on use of SEAL above - (type2Flags & FLAG_REQUEST_128BIT_KEY_EXCH) | - (type2Flags & FLAG_REQUEST_56BIT_ENCRYPTION) | - (type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) | - - (type2Flags & FLAG_TARGETINFO_PRESENT) | - (type2Flags & FLAG_REQUEST_UNICODE_ENCODING) | - (type2Flags & FLAG_REQUEST_TARGET) - */ - type2Flags - ); - - // Version - addUShort(0x0105); - // Build - addULong(2600); - // NTLM revision - addUShort(0x0f00); - - int micPosition = -1; - if ( computeMic ) { - micPosition = currentOutputPosition; - currentOutputPosition += 16; - } - - // Add the actual data - addBytes(lmResp); - addBytes(ntResp); - addBytes(domainBytes); - addBytes(userBytes); - addBytes(hostBytes); - if (sessionKey != null) { - addBytes(sessionKey); - } - - // 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 ); - - final 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 ( final CertificateEncodingException e ) - { - throw new NTLMEngineException( e.getMessage(), e ); - } - catch ( final 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) { - buffer[offset] = (byte) (value & 0xff); - buffer[offset + 1] = (byte) (value >> 8 & 0xff); - buffer[offset + 2] = (byte) (value >> 16 & 0xff); - buffer[offset + 3] = (byte) (value >> 24 & 0xff); - } - - static int F(final int x, final int y, final int z) { - return ((x & y) | (~x & z)); - } - - static int G(final int x, final int y, final int z) { - return ((x & y) | (x & z) | (y & z)); - } - - static int H(final int x, final int y, final int z) { - return (x ^ y ^ z); - } - - static int rotintlft(final int val, final int numbits) { - return ((val << numbits) | (val >>> (32 - numbits))); - } - - static MessageDigest getMD5() { - try { - return MessageDigest.getInstance("MD5"); - } catch (final 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. - * Code correctness was verified by looking at MD4.java from the jcifs - * library (http://jcifs.samba.org). It was massaged extensively to the - * final form found here by Karl Wright (kwright@metacarta.com). - */ - static class MD4 { - protected int A = 0x67452301; - protected int B = 0xefcdab89; - protected int C = 0x98badcfe; - protected int D = 0x10325476; - protected long count = 0L; - protected final byte[] dataBuffer = new byte[64]; - - MD4() { - } - - void update(final byte[] input) { - // We always deal with 512 bits at a time. Correspondingly, there is - // a buffer 64 bytes long that we write data into until it gets - // full. - int curBufferPos = (int) (count & 63L); - int inputIndex = 0; - while (input.length - inputIndex + curBufferPos >= dataBuffer.length) { - // We have enough data to do the next step. Do a partial copy - // and a transform, updating inputIndex and curBufferPos - // accordingly - final int transferAmt = dataBuffer.length - curBufferPos; - System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); - count += transferAmt; - curBufferPos = 0; - inputIndex += transferAmt; - processBuffer(); - } - - // If there's anything left, copy it into the buffer and leave it. - // We know there's not enough left to process. - if (inputIndex < input.length) { - final int transferAmt = input.length - inputIndex; - System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); - count += transferAmt; - curBufferPos += transferAmt; - } - } - - byte[] getOutput() { - // Feed pad/length data into engine. This must round out the input - // to a multiple of 512 bits. - final int bufferIndex = (int) (count & 63L); - final int padLen = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex); - final byte[] postBytes = new byte[padLen + 8]; - // Leading 0x80, specified amount of zero padding, then length in - // bits. - postBytes[0] = (byte) 0x80; - // Fill out the last 8 bytes with the length - for (int i = 0; i < 8; i++) { - postBytes[padLen + i] = (byte) ((count * 8) >>> (8 * i)); - } - - // Update the engine - update(postBytes); - - // Calculate final result - final byte[] result = new byte[16]; - writeULong(result, A, 0); - writeULong(result, B, 4); - writeULong(result, C, 8); - writeULong(result, D, 12); - return result; - } - - protected void processBuffer() { - // Convert current buffer to 16 ulongs - final int[] d = new int[16]; - - for (int i = 0; i < 16; i++) { - d[i] = (dataBuffer[i * 4] & 0xff) + ((dataBuffer[i * 4 + 1] & 0xff) << 8) - + ((dataBuffer[i * 4 + 2] & 0xff) << 16) - + ((dataBuffer[i * 4 + 3] & 0xff) << 24); - } - - // Do a round of processing - final int AA = A; - final int BB = B; - final int CC = C; - final int DD = D; - round1(d); - round2(d); - round3(d); - A += AA; - B += BB; - C += CC; - D += DD; - - } - - protected void round1(final int[] d) { - A = rotintlft((A + F(B, C, D) + d[0]), 3); - D = rotintlft((D + F(A, B, C) + d[1]), 7); - C = rotintlft((C + F(D, A, B) + d[2]), 11); - B = rotintlft((B + F(C, D, A) + d[3]), 19); - - A = rotintlft((A + F(B, C, D) + d[4]), 3); - D = rotintlft((D + F(A, B, C) + d[5]), 7); - C = rotintlft((C + F(D, A, B) + d[6]), 11); - B = rotintlft((B + F(C, D, A) + d[7]), 19); - - A = rotintlft((A + F(B, C, D) + d[8]), 3); - D = rotintlft((D + F(A, B, C) + d[9]), 7); - C = rotintlft((C + F(D, A, B) + d[10]), 11); - B = rotintlft((B + F(C, D, A) + d[11]), 19); - - A = rotintlft((A + F(B, C, D) + d[12]), 3); - D = rotintlft((D + F(A, B, C) + d[13]), 7); - C = rotintlft((C + F(D, A, B) + d[14]), 11); - B = rotintlft((B + F(C, D, A) + d[15]), 19); - } - - protected void round2(final int[] d) { - A = rotintlft((A + G(B, C, D) + d[0] + 0x5a827999), 3); - D = rotintlft((D + G(A, B, C) + d[4] + 0x5a827999), 5); - C = rotintlft((C + G(D, A, B) + d[8] + 0x5a827999), 9); - B = rotintlft((B + G(C, D, A) + d[12] + 0x5a827999), 13); - - A = rotintlft((A + G(B, C, D) + d[1] + 0x5a827999), 3); - D = rotintlft((D + G(A, B, C) + d[5] + 0x5a827999), 5); - C = rotintlft((C + G(D, A, B) + d[9] + 0x5a827999), 9); - B = rotintlft((B + G(C, D, A) + d[13] + 0x5a827999), 13); - - A = rotintlft((A + G(B, C, D) + d[2] + 0x5a827999), 3); - D = rotintlft((D + G(A, B, C) + d[6] + 0x5a827999), 5); - C = rotintlft((C + G(D, A, B) + d[10] + 0x5a827999), 9); - B = rotintlft((B + G(C, D, A) + d[14] + 0x5a827999), 13); - - A = rotintlft((A + G(B, C, D) + d[3] + 0x5a827999), 3); - D = rotintlft((D + G(A, B, C) + d[7] + 0x5a827999), 5); - C = rotintlft((C + G(D, A, B) + d[11] + 0x5a827999), 9); - B = rotintlft((B + G(C, D, A) + d[15] + 0x5a827999), 13); - - } - - protected void round3(final int[] d) { - A = rotintlft((A + H(B, C, D) + d[0] + 0x6ed9eba1), 3); - D = rotintlft((D + H(A, B, C) + d[8] + 0x6ed9eba1), 9); - C = rotintlft((C + H(D, A, B) + d[4] + 0x6ed9eba1), 11); - B = rotintlft((B + H(C, D, A) + d[12] + 0x6ed9eba1), 15); - - A = rotintlft((A + H(B, C, D) + d[2] + 0x6ed9eba1), 3); - D = rotintlft((D + H(A, B, C) + d[10] + 0x6ed9eba1), 9); - C = rotintlft((C + H(D, A, B) + d[6] + 0x6ed9eba1), 11); - B = rotintlft((B + H(C, D, A) + d[14] + 0x6ed9eba1), 15); - - A = rotintlft((A + H(B, C, D) + d[1] + 0x6ed9eba1), 3); - D = rotintlft((D + H(A, B, C) + d[9] + 0x6ed9eba1), 9); - C = rotintlft((C + H(D, A, B) + d[5] + 0x6ed9eba1), 11); - B = rotintlft((B + H(C, D, A) + d[13] + 0x6ed9eba1), 15); - - A = rotintlft((A + H(B, C, D) + d[3] + 0x6ed9eba1), 3); - D = rotintlft((D + H(A, B, C) + d[11] + 0x6ed9eba1), 9); - C = rotintlft((C + H(D, A, B) + d[7] + 0x6ed9eba1), 11); - B = rotintlft((B + H(C, D, A) + d[15] + 0x6ed9eba1), 15); - - } - - } - - /** - * Cryptography support - HMACMD5 - algorithmically based on various web - * resources by Karl Wright - */ - static class HMACMD5 { - protected final byte[] ipad; - protected final byte[] opad; - protected final MessageDigest md5; - - HMACMD5(final byte[] input) { - byte[] key = input; - md5 = getMD5(); - - // Initialize the pad buffers with the key - ipad = new byte[64]; - opad = new byte[64]; - - int keyLength = key.length; - if (keyLength > 64) { - // Use MD5 of the key instead, as described in RFC 2104 - md5.update(key); - key = md5.digest(); - keyLength = key.length; - } - int i = 0; - while (i < keyLength) { - ipad[i] = (byte) (key[i] ^ (byte) 0x36); - opad[i] = (byte) (key[i] ^ (byte) 0x5c); - i++; - } - while (i < 64) { - ipad[i] = (byte) 0x36; - opad[i] = (byte) 0x5c; - i++; - } - - // Very important: processChallenge the digest with the ipad buffer - md5.reset(); - md5.update(ipad); - - } - - /** Grab the current digest. This is the "answer". */ - byte[] getOutput() { - final byte[] digest = md5.digest(); - md5.update(opad); - return md5.digest(digest); - } - - /** Update by adding a complete array */ - void update(final byte[] input) { - md5.update(input); - } - - /** Update the algorithm */ - void update(final byte[] input, final int offset, final int length) { - md5.update(input, offset, length); - } - - } - - @Override - public String generateType1Msg( - final String domain, - final String workstation) throws NTLMEngineException { - return getType1Message(workstation, domain); - } - - @Override - public String generateType3Msg( - final String username, - final String password, - final String domain, - final String workstation, - final String challenge) throws NTLMEngineException { - final Type2Message t2m = new Type2Message(challenge); - return getType3Message( - username, - password, - workstation, - domain, - t2m.getChallenge(), - t2m.getFlags(), - t2m.getTarget(), - t2m.getTargetInfo()); - } - -} +/* + * ==================================================================== + * 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.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; + +/** + * Provides an implementation for NTLMv1, NTLMv2, and NTLM2 Session forms of the NTLM + * authentication protocol. + * + * @since 4.1 + */ +final class NTLMEngineImpl implements NTLMEngine { + + /** Unicode encoding */ + private static final Charset UNICODE_LITTLE_UNMARKED = Charset.forName("UnicodeLittleUnmarked"); + /** Character encoding */ + private static final Charset DEFAULT_CHARSET = Consts.ASCII; + + // Flags we use; descriptions according to: + // http://davenport.sourceforge.net/ntlm.html + // and + // http://msdn.microsoft.com/en-us/library/cc236650%28v=prot.20%29.aspx + // [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; + static { + java.security.SecureRandom rnd = null; + try { + rnd = java.security.SecureRandom.getInstance("SHA1PRNG"); + } catch (final Exception ignore) { + } + RND_GEN = rnd; + } + + /** The signature string as bytes in the default encoding */ + private static final byte[] SIGNATURE = getNullTerminatedAsciiString("NTLMSSP"); + + // 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(); + + NTLMEngineImpl() { + } + + /** + * Creates the first message (type 1 message) in the NTLM authentication + * sequence. This message includes the user name, domain and host for the + * authentication session. + * + * @param host + * the computer name of the host requesting authentication. + * @param domain + * 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) { + // For compatibility reason do not include domain and host in type 1 message + //return new Type1Message(domain, host).getResponse(); + return TYPE_1_MESSAGE; + } + + /** + * 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) + throws NTLMEngineException { + return new Type3Message(domain, host, user, password, nonce, type2Flags, target, + targetInformation).getResponse(); + } + + /** + * 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 { + if (src.length < index + 4) { + return 0; + } + return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8) + | ((src[index + 2] & 0xff) << 16) | ((src[index + 3] & 0xff) << 24); + } + + private static int readUShort(final byte[] src, final int index) throws NTLMEngineException { + if (src.length < index + 2) { + return 0; + } + return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8); + } + + private static byte[] readSecurityBuffer(final byte[] src, final int index) throws NTLMEngineException { + final int length = readUShort(src, index); + final int offset = readULong(src, index + 4); + if (src.length < offset + length) { + return new byte[length]; + } + final byte[] buffer = new byte[length]; + System.arraycopy(src, offset, buffer, 0, length); + return buffer; + } + + /** Calculate a challenge block */ + private static byte[] makeRandomChallenge(final Random random) throws NTLMEngineException { + final byte[] rval = new byte[8]; + synchronized (random) { + random.nextBytes(rval); + } + return rval; + } + + /** Calculate a 16-byte secondary key */ + private static byte[] makeSecondaryKey(final Random random) throws NTLMEngineException { + final byte[] rval = new byte[16]; + 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; + protected final byte[] challenge; + protected final String target; + protected final byte[] targetInformation; + + // Information we can generate but may be passed in (for testing) + protected byte[] clientChallenge; + protected byte[] clientChallenge2; + protected byte[] secondaryKey; + protected byte[] timestamp; + + // Stuff we always generate + protected byte[] lmHash = null; + protected byte[] lmResponse = null; + protected byte[] ntlmHash = null; + protected byte[] ntlmResponse = null; + protected byte[] ntlmv2Hash = null; + protected byte[] lmv2Hash = null; + protected byte[] lmv2Response = null; + protected byte[] ntlmv2Blob = null; + protected byte[] ntlmv2Response = null; + protected byte[] ntlm2SessionResponse = null; + protected byte[] lm2SessionResponse = null; + protected byte[] lmUserSessionKey = null; + protected byte[] ntlmUserSessionKey = null; + protected byte[] ntlmv2UserSessionKey = null; + 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; + this.password = password; + this.challenge = challenge; + this.targetInformation = targetInformation; + this.clientChallenge = clientChallenge; + this.clientChallenge2 = clientChallenge2; + this.secondaryKey = secondaryKey; + this.timestamp = timestamp; + } + + @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(random); + } + return clientChallenge; + } + + /** Calculate and return second client challenge */ + public byte[] getClientChallenge2() + throws NTLMEngineException { + if (clientChallenge2 == null) { + clientChallenge2 = makeRandomChallenge(random); + } + return clientChallenge2; + } + + /** Calculate and return random secondary key */ + public byte[] getSecondaryKey() + throws NTLMEngineException { + if (secondaryKey == null) { + secondaryKey = makeSecondaryKey(random); + } + return secondaryKey; + } + + /** Calculate and return the LMHash */ + public byte[] getLMHash() + throws NTLMEngineException { + if (lmHash == null) { + lmHash = lmHash(password); + } + return lmHash; + } + + /** Calculate and return the LMResponse */ + public byte[] getLMResponse() + throws NTLMEngineException { + if (lmResponse == null) { + lmResponse = lmResponse(getLMHash(),challenge); + } + return lmResponse; + } + + /** Calculate and return the NTLMHash */ + public byte[] getNTLMHash() + throws NTLMEngineException { + if (ntlmHash == null) { + ntlmHash = ntlmHash(password); + } + return ntlmHash; + } + + /** Calculate and return the NTLMResponse */ + public byte[] getNTLMResponse() + throws NTLMEngineException { + if (ntlmResponse == null) { + ntlmResponse = lmResponse(getNTLMHash(),challenge); + } + return ntlmResponse; + } + + /** Calculate the LMv2 hash */ + public byte[] getLMv2Hash() + throws NTLMEngineException { + if (lmv2Hash == null) { + lmv2Hash = lmv2Hash(domain, user, getNTLMHash()); + } + return lmv2Hash; + } + + /** Calculate the NTLMv2 hash */ + public byte[] getNTLMv2Hash() + throws NTLMEngineException { + if (ntlmv2Hash == null) { + ntlmv2Hash = ntlmv2Hash(domain, user, getNTLMHash()); + } + return ntlmv2Hash; + } + + /** Calculate a timestamp */ + public byte[] getTimestamp() { + if (timestamp == null) { + long time = this.currentTime; + time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch. + time *= 10000; // tenths of a microsecond. + // convert to little-endian byte array. + timestamp = new byte[8]; + for (int i = 0; i < 8; i++) { + timestamp[i] = (byte) time; + time >>>= 8; + } + } + return timestamp; + } + + /** Calculate the NTLMv2Blob */ + public byte[] getNTLMv2Blob() + throws NTLMEngineException { + if (ntlmv2Blob == null) { + ntlmv2Blob = createBlob(getClientChallenge2(), targetInformation, getTimestamp()); + } + return ntlmv2Blob; + } + + /** Calculate the NTLMv2Response */ + public byte[] getNTLMv2Response() + throws NTLMEngineException { + if (ntlmv2Response == null) { + ntlmv2Response = lmv2Response(getNTLMv2Hash(),challenge,getNTLMv2Blob()); + } + return ntlmv2Response; + } + + /** Calculate the LMv2Response */ + public byte[] getLMv2Response() + throws NTLMEngineException { + if (lmv2Response == null) { + lmv2Response = lmv2Response(getLMv2Hash(),challenge,getClientChallenge()); + } + return lmv2Response; + } + + /** Get NTLM2SessionResponse */ + public byte[] getNTLM2SessionResponse() + throws NTLMEngineException { + if (ntlm2SessionResponse == null) { + ntlm2SessionResponse = ntlm2SessionResponse(getNTLMHash(),challenge,getClientChallenge()); + } + return ntlm2SessionResponse; + } + + /** Calculate and return LM2 session response */ + public byte[] getLM2SessionResponse() + throws NTLMEngineException { + if (lm2SessionResponse == null) { + final byte[] clntChallenge = getClientChallenge(); + lm2SessionResponse = new byte[24]; + System.arraycopy(clntChallenge, 0, lm2SessionResponse, 0, clntChallenge.length); + Arrays.fill(lm2SessionResponse, clntChallenge.length, lm2SessionResponse.length, (byte) 0x00); + } + return lm2SessionResponse; + } + + /** Get LMUserSessionKey */ + public byte[] getLMUserSessionKey() + throws NTLMEngineException { + if (lmUserSessionKey == null) { + lmUserSessionKey = new byte[16]; + System.arraycopy(getLMHash(), 0, lmUserSessionKey, 0, 8); + Arrays.fill(lmUserSessionKey, 8, 16, (byte) 0x00); + } + return lmUserSessionKey; + } + + /** Get NTLMUserSessionKey */ + public byte[] getNTLMUserSessionKey() + throws NTLMEngineException { + if (ntlmUserSessionKey == null) { + final MD4 md4 = new MD4(); + md4.update(getNTLMHash()); + ntlmUserSessionKey = md4.getOutput(); + } + return ntlmUserSessionKey; + } + + /** GetNTLMv2UserSessionKey */ + public byte[] getNTLMv2UserSessionKey() + throws NTLMEngineException { + if (ntlmv2UserSessionKey == null) { + final byte[] ntlmv2hash = getNTLMv2Hash(); + final byte[] truncatedResponse = new byte[16]; + System.arraycopy(getNTLMv2Response(), 0, truncatedResponse, 0, 16); + ntlmv2UserSessionKey = hmacMD5(truncatedResponse, ntlmv2hash); + } + return ntlmv2UserSessionKey; + } + + /** Get NTLM2SessionResponseUserSessionKey */ + public byte[] getNTLM2SessionResponseUserSessionKey() + throws NTLMEngineException { + if (ntlm2SessionResponseUserSessionKey == null) { + final byte[] ntlm2SessionResponseNonce = getLM2SessionResponse(); + final byte[] sessionNonce = new byte[challenge.length + ntlm2SessionResponseNonce.length]; + System.arraycopy(challenge, 0, sessionNonce, 0, challenge.length); + System.arraycopy(ntlm2SessionResponseNonce, 0, sessionNonce, challenge.length, ntlm2SessionResponseNonce.length); + ntlm2SessionResponseUserSessionKey = hmacMD5(sessionNonce,getNTLMUserSessionKey()); + } + return ntlm2SessionResponseUserSessionKey; + } + + /** Get LAN Manager session key */ + public byte[] getLanManagerSessionKey() + throws NTLMEngineException { + if (lanManagerSessionKey == null) { + try { + final byte[] keyBytes = new byte[14]; + System.arraycopy(getLMHash(), 0, keyBytes, 0, 8); + Arrays.fill(keyBytes, 8, keyBytes.length, (byte)0xbd); + final Key lowKey = createDESKey(keyBytes, 0); + final Key highKey = createDESKey(keyBytes, 7); + final byte[] truncatedResponse = new byte[8]; + System.arraycopy(getLMResponse(), 0, truncatedResponse, 0, truncatedResponse.length); + Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, lowKey); + final byte[] lowPart = des.doFinal(truncatedResponse); + des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, highKey); + final byte[] highPart = des.doFinal(truncatedResponse); + lanManagerSessionKey = new byte[16]; + System.arraycopy(lowPart, 0, lanManagerSessionKey, 0, lowPart.length); + System.arraycopy(highPart, 0, lanManagerSessionKey, lowPart.length, highPart.length); + } catch (final Exception e) { + throw new NTLMEngineException(e.getMessage(), e); + } + } + return lanManagerSessionKey; + } + } + + /** Calculates HMAC-MD5 */ + static byte[] hmacMD5(final byte[] value, final byte[] key) + throws NTLMEngineException { + final HMACMD5 hmacMD5 = new HMACMD5(key); + hmacMD5.update(value); + return hmacMD5.getOutput(); + } + + /** Calculates RC4 */ + static byte[] RC4(final byte[] value, final byte[] key) + throws NTLMEngineException { + try { + final Cipher rc4 = Cipher.getInstance("RC4"); + rc4.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "RC4")); + return rc4.doFinal(value); + } catch (final Exception e) { + throw new NTLMEngineException(e.getMessage(), e); + } + } + + /** + * Calculates the NTLM2 Session Response for the given challenge, using the + * specified password and client challenge. + * + * @return The NTLM2 Session Response. This is placed in the NTLM response + * field of the Type 3 message; the LM response field contains the + * client challenge, null-padded to 24 bytes. + */ + static byte[] ntlm2SessionResponse(final byte[] ntlmHash, final byte[] challenge, + final byte[] clientChallenge) throws NTLMEngineException { + try { + final MessageDigest md5 = getMD5(); + md5.update(challenge); + md5.update(clientChallenge); + final byte[] digest = md5.digest(); + + final byte[] sessionHash = new byte[8]; + System.arraycopy(digest, 0, sessionHash, 0, 8); + return lmResponse(ntlmHash, sessionHash); + } catch (final Exception e) { + if (e instanceof NTLMEngineException) { + throw (NTLMEngineException) e; + } + throw new NTLMEngineException(e.getMessage(), e); + } + } + + /** + * Creates the LM Hash of the user's password. + * + * @param password + * The password. + * + * @return The LM Hash of the given password, used in the calculation of the + * LM Response. + */ + private static byte[] lmHash(final String password) throws NTLMEngineException { + try { + final byte[] oemPassword = password.toUpperCase(Locale.ROOT).getBytes(Consts.ASCII); + final int length = Math.min(oemPassword.length, 14); + final byte[] keyBytes = new byte[14]; + System.arraycopy(oemPassword, 0, keyBytes, 0, length); + final Key lowKey = createDESKey(keyBytes, 0); + final Key highKey = createDESKey(keyBytes, 7); + final byte[] magicConstant = "KGS!@#$%".getBytes(Consts.ASCII); + final Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, lowKey); + final byte[] lowHash = des.doFinal(magicConstant); + des.init(Cipher.ENCRYPT_MODE, highKey); + final byte[] highHash = des.doFinal(magicConstant); + final byte[] lmHash = new byte[16]; + System.arraycopy(lowHash, 0, lmHash, 0, 8); + System.arraycopy(highHash, 0, lmHash, 8, 8); + return lmHash; + } catch (final Exception e) { + throw new NTLMEngineException(e.getMessage(), e); + } + } + + /** + * Creates the NTLM Hash of the user's password. + * + * @param password + * The password. + * + * @return The NTLM Hash of the given password, used in the calculation of + * the NTLM Response and the NTLMv2 and LMv2 Hashes. + */ + private static byte[] ntlmHash(final String password) throws NTLMEngineException { + if (UNICODE_LITTLE_UNMARKED == null) { + throw new NTLMEngineException("Unicode not supported"); + } + final byte[] unicodePassword = password.getBytes(UNICODE_LITTLE_UNMARKED); + final MD4 md4 = new MD4(); + md4.update(unicodePassword); + return md4.getOutput(); + } + + /** + * Creates the LMv2 Hash of the user's password. + * + * @return The LMv2 Hash, used in the calculation of the NTLMv2 and LMv2 + * Responses. + */ + private static byte[] lmv2Hash(final String domain, final String user, final byte[] ntlmHash) + throws NTLMEngineException { + if (UNICODE_LITTLE_UNMARKED == null) { + throw new NTLMEngineException("Unicode not supported"); + } + final HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); + // Upper case username, upper case domain! + hmacMD5.update(user.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED)); + if (domain != null) { + hmacMD5.update(domain.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED)); + } + return hmacMD5.getOutput(); + } + + /** + * Creates the NTLMv2 Hash of the user's password. + * + * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 and LMv2 + * Responses. + */ + private static byte[] ntlmv2Hash(final String domain, final String user, final byte[] ntlmHash) + throws NTLMEngineException { + if (UNICODE_LITTLE_UNMARKED == null) { + throw new NTLMEngineException("Unicode not supported"); + } + final HMACMD5 hmacMD5 = new HMACMD5(ntlmHash); + // Upper case username, mixed case target!! + hmacMD5.update(user.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED)); + if (domain != null) { + hmacMD5.update(domain.getBytes(UNICODE_LITTLE_UNMARKED)); + } + return hmacMD5.getOutput(); + } + + /** + * Creates the LM Response from the given hash and Type 2 challenge. + * + * @param hash + * The LM or NTLM Hash. + * @param challenge + * The server challenge from the Type 2 message. + * + * @return The response (either LM or NTLM, depending on the provided hash). + */ + private static byte[] lmResponse(final byte[] hash, final byte[] challenge) throws NTLMEngineException { + try { + final byte[] keyBytes = new byte[21]; + System.arraycopy(hash, 0, keyBytes, 0, 16); + final Key lowKey = createDESKey(keyBytes, 0); + final Key middleKey = createDESKey(keyBytes, 7); + final Key highKey = createDESKey(keyBytes, 14); + final Cipher des = Cipher.getInstance("DES/ECB/NoPadding"); + des.init(Cipher.ENCRYPT_MODE, lowKey); + final byte[] lowResponse = des.doFinal(challenge); + des.init(Cipher.ENCRYPT_MODE, middleKey); + final byte[] middleResponse = des.doFinal(challenge); + des.init(Cipher.ENCRYPT_MODE, highKey); + final byte[] highResponse = des.doFinal(challenge); + final byte[] lmResponse = new byte[24]; + System.arraycopy(lowResponse, 0, lmResponse, 0, 8); + System.arraycopy(middleResponse, 0, lmResponse, 8, 8); + System.arraycopy(highResponse, 0, lmResponse, 16, 8); + return lmResponse; + } catch (final Exception e) { + throw new NTLMEngineException(e.getMessage(), e); + } + } + + /** + * Creates the LMv2 Response from the given hash, client data, and Type 2 + * challenge. + * + * @param hash + * The NTLMv2 Hash. + * @param clientData + * The client data (blob or client challenge). + * @param challenge + * The server challenge from the Type 2 message. + * + * @return The response (either NTLMv2 or LMv2, depending on the client + * data). + */ + private static byte[] lmv2Response(final byte[] hash, final byte[] challenge, final byte[] clientData) + throws NTLMEngineException { + final HMACMD5 hmacMD5 = new HMACMD5(hash); + hmacMD5.update(challenge); + hmacMD5.update(clientData); + final byte[] mac = hmacMD5.getOutput(); + final byte[] lmv2Response = new byte[mac.length + clientData.length]; + System.arraycopy(mac, 0, lmv2Response, 0, mac.length); + System.arraycopy(clientData, 0, lmv2Response, mac.length, clientData.length); + return lmv2Response; + } + + static enum Mode + { + CLIENT, SERVER; + } + + static class Handle + { + final private byte[] exportedSessionKey; + private byte[] signingKey; + private byte[] sealingKey; + private final 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 + { + final 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 ( final 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. + * + * @param targetInformation + * The target information block from the Type 2 message. + * @param clientChallenge + * The random 8-byte client challenge. + * + * @return The blob, used in the calculation of the NTLMv2 Response. + */ + private static byte[] createBlob(final byte[] clientChallenge, final byte[] targetInformation, final byte[] timestamp) { + final byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00 }; + final byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; + final byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; + final byte[] unknown2 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; + final byte[] blob = new byte[blobSignature.length + reserved.length + timestamp.length + 8 + + unknown1.length + targetInformation.length + unknown2.length]; + int offset = 0; + System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length); + offset += blobSignature.length; + System.arraycopy(reserved, 0, blob, offset, reserved.length); + offset += reserved.length; + System.arraycopy(timestamp, 0, blob, offset, timestamp.length); + offset += timestamp.length; + System.arraycopy(clientChallenge, 0, blob, offset, 8); + offset += 8; + System.arraycopy(unknown1, 0, blob, offset, unknown1.length); + offset += unknown1.length; + System.arraycopy(targetInformation, 0, blob, offset, targetInformation.length); + offset += targetInformation.length; + System.arraycopy(unknown2, 0, blob, offset, unknown2.length); + offset += unknown2.length; + return blob; + } + + /** + * Creates a DES encryption key from the given key material. + * + * @param bytes + * A byte array containing the DES key material. + * @param offset + * The offset in the given byte array at which the 7-byte key + * material starts. + * + * @return A DES encryption key created from the key material starting at + * the specified offset in the given byte array. + */ + private static Key createDESKey(final byte[] bytes, final int offset) { + final byte[] keyBytes = new byte[7]; + System.arraycopy(bytes, offset, keyBytes, 0, 7); + final byte[] material = new byte[8]; + material[0] = keyBytes[0]; + material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >>> 1); + material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >>> 2); + material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >>> 3); + material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >>> 4); + material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >>> 5); + material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6); + material[7] = (byte) (keyBytes[6] << 1); + oddParity(material); + return new SecretKeySpec(material, "DES"); + } + + /** + * Applies odd parity to the given byte array. + * + * @param bytes + * The data whose parity bits are to be adjusted for odd parity. + */ + private static void oddParity(final byte[] bytes) { + for (int i = 0; i < bytes.length; i++) { + final byte b = bytes[i]; + final boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ (b >>> 4) ^ (b >>> 3) + ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0; + if (needsParity) { + bytes[i] |= (byte) 0x01; + } else { + bytes[i] &= (byte) 0xfe; + } + } + } + + /** + * 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 */ + protected byte[] messageContents = null; + + /** The current output position */ + protected int currentOutputPosition = 0; + + /** Constructor to use when message contents are not yet known */ + NTLMMessage() { + } + + /** Constructor taking a string */ + NTLMMessage(final String messageBody, final int expectedType) throws NTLMEngineException { + 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"); + } + int i = 0; + while (i < SIGNATURE.length) { + if (messageContents[i] != SIGNATURE[i]) { + throw new NTLMEngineException( + "NTLM message expected - instead got unrecognized bytes"); + } + i++; + } + + // Check to be sure there's a type 2 message indicator next + final int type = readULong(SIGNATURE.length); + if (type != expectedType) { + throw new NTLMEngineException("NTLM type " + Integer.toString(expectedType) + + " message expected - instead got type " + Integer.toString(type)); + } + + currentOutputPosition = messageContents.length; + } + + /** + * Get the length of the signature and flags, so calculations can adjust + * offsets accordingly. + */ + protected int getPreambleLength() { + return SIGNATURE.length + 4; + } + + /** Get the message length */ + protected int getMessageLength() { + return currentOutputPosition; + } + + /** Read a byte from a position within the message buffer */ + protected byte readByte(final int position) throws NTLMEngineException { + if (messageContents.length < position + 1) { + throw new NTLMEngineException("NTLM: Message too short"); + } + return messageContents[position]; + } + + /** Read a bunch of bytes from a position in the message buffer */ + protected void readBytes(final byte[] buffer, final int position) throws NTLMEngineException { + if (messageContents.length < position + buffer.length) { + throw new NTLMEngineException("NTLM: Message too short"); + } + System.arraycopy(messageContents, position, buffer, 0, buffer.length); + } + + /** Read a ushort from a position within the message buffer */ + protected int readUShort(final int position) throws NTLMEngineException { + return NTLMEngineImpl.readUShort(messageContents, position); + } + + /** Read a ulong from a position within the message buffer */ + protected int readULong(final int position) throws NTLMEngineException { + return NTLMEngineImpl.readULong(messageContents, position); + } + + /** Read a security buffer from a position within the message buffer */ + protected byte[] readSecurityBuffer(final int position) throws NTLMEngineException { + return NTLMEngineImpl.readSecurityBuffer(messageContents, position); + } + + /** + * Prepares the object to create a response of the given length. + * + * @param maxlength + * the maximum length of the response to prepare, + * including the type and the signature (which this method + * adds). + */ + protected void prepareResponse(final int maxlength, final int messageType) { + messageContents = new byte[maxlength]; + currentOutputPosition = 0; + addBytes(SIGNATURE); + addULong(messageType); + } + + /** + * Adds the given byte to the response. + * + * @param b + * the byte to add. + */ + protected void addByte(final byte b) { + messageContents[currentOutputPosition] = b; + currentOutputPosition++; + } + + /** + * Adds the given bytes to the response. + * + * @param bytes + * the bytes to add. + */ + protected void addBytes(final byte[] bytes) { + if (bytes == null) { + return; + } + for (final byte b : bytes) { + messageContents[currentOutputPosition] = b; + currentOutputPosition++; + } + } + + /** Adds a USHORT to the response */ + protected void addUShort(final int value) { + addByte((byte) (value & 0xff)); + addByte((byte) (value >> 8 & 0xff)); + } + + /** Adds a ULong to the response */ + protected void addULong(final int value) { + addByte((byte) (value & 0xff)); + addByte((byte) (value >> 8 & 0xff)); + addByte((byte) (value >> 16 & 0xff)); + addByte((byte) (value >> 24 & 0xff)); + } + + /** + * Returns the response that has been generated after shrinking the + * array if required and base64 encodes the response. + * + * @return The response as above. + */ + 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 */ + static class Type1Message extends NTLMMessage { + + 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(); + this.flags = ((flags == null)?getDefaultFlags():flags); + + // Strip off domain name from the host! + final String unqualifiedHost = convertHost(host); + // Use only the base domain name! + final String unqualifiedDomain = convertDomain(domain); + + hostBytes = unqualifiedHost != null ? + unqualifiedHost.getBytes(UNICODE_LITTLE_UNMARKED) : null; + domainBytes = unqualifiedDomain != null ? + unqualifiedDomain.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED) : null; + } + + Type1Message() { + 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 + 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 + 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(flags); + + // Domain length (two times). + addUShort(domainBytesLength); + addUShort(domainBytesLength); + + // Domain offset. + addULong(hostBytesLength + 32 + 8); + + // Host length (two times). + addUShort(hostBytesLength); + addUShort(hostBytesLength); + + // Host offset (always 32 + 8). + addULong(32 + 8); + + // Version + addUShort(0x0105); + // Build + addULong(2600); + // NTLM revision + addUShort(0x0f00); + + // Host (workstation) String. + if (hostBytes != null) { + addBytes(hostBytes); + } + // Domain String. + if (domainBytes != null) { + addBytes(domainBytes); + } + } + + } + + /** Type 2 message class */ + static class Type2Message extends NTLMMessage { + protected final byte[] challenge; + protected String target; + protected byte[] targetInfo; + protected final int flags; + + 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: + // First 8 bytes: NTLMSSP[0] + // Next 4 bytes: Ulong, value 2 + // Next 8 bytes, starting at offset 12: target field (2 ushort lengths, 1 ulong offset) + // Next 4 bytes, starting at offset 20: Flags, e.g. 0x22890235 + // Next 8 bytes, starting at offset 24: Challenge + // Next 8 bytes, starting at offset 32: ??? (8 bytes of zeros) + // Next 8 bytes, starting at offset 40: targetinfo field (2 ushort lengths, 1 ulong offset) + // Next 2 bytes, major/minor version number (e.g. 0x05 0x02) + // Next 8 bytes, build number + // Next 2 bytes, protocol version number (e.g. 0x00 0x0f) + // Next, various text fields, and a ushort of value 0 at the end + + // Parse out the rest of the info we need from the message + // The nonce is the 8 bytes starting from the byte in position 24. + challenge = new byte[8]; + readBytes(challenge, 24); + + flags = readULong(20); + + // Do the target! + target = null; + // The TARGET_DESIRED flag is said to not have understood semantics + // in Type2 messages, so use the length of the packet to decide + // how to proceed instead + if (getMessageLength() >= 12 + 8) { + final byte[] bytes = readSecurityBuffer(12); + if (bytes.length != 0) { + target = new String(bytes, getCharset(flags)); + } + } + + // Do the target info! + targetInfo = null; + // TARGET_DESIRED flag cannot be relied on, so use packet length + if (getMessageLength() >= 40 + 8) { + final byte[] bytes = readSecurityBuffer(40); + if (bytes.length != 0) { + targetInfo = bytes; + } + } + } + + /** Retrieve the challenge */ + byte[] getChallenge() { + return challenge; + } + + /** Retrieve the target */ + String getTarget() { + return target; + } + + /** Retrieve the target info */ + byte[] getTargetInfo() { + return targetInfo; + } + + /** Retrieve the response flags */ + int getFlags() { + return flags; + } + + } + + /** 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 final int type2Flags; + + protected final byte[] domainBytes; + protected final byte[] hostBytes; + protected final byte[] userBytes; + + protected byte[] lmResp; + protected byte[] ntResp; + 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, + 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); + + 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. + byte[] userSessionKey; + try { + // This conditional may not work on Windows Server 2008 R2 and above, where it has not yet + // been tested + if (((type2Flags & FLAG_TARGETINFO_PRESENT) != 0) && + targetInformation != null && target != null) { + // NTLMv2 + ntResp = gen.getNTLMv2Response(); + lmResp = gen.getLMv2Response(); + if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { + userSessionKey = gen.getLanManagerSessionKey(); + } else { + userSessionKey = gen.getNTLMv2UserSessionKey(); + } + } else { + // NTLMv1 + if ((type2Flags & FLAG_REQUEST_NTLM2_SESSION) != 0) { + // NTLM2 session stuff is requested + ntResp = gen.getNTLM2SessionResponse(); + lmResp = gen.getLM2SessionResponse(); + if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { + userSessionKey = gen.getLanManagerSessionKey(); + } else { + userSessionKey = gen.getNTLM2SessionResponseUserSessionKey(); + } + } else { + ntResp = gen.getNTLMResponse(); + lmResp = gen.getLMResponse(); + if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { + userSessionKey = gen.getLanManagerSessionKey(); + } else { + userSessionKey = gen.getNTLMUserSessionKey(); + } + } + } + } catch (final NTLMEngineException e) { + // This likely means we couldn't find the MD4 hash algorithm - + // fail back to just using LM + ntResp = new byte[0]; + lmResp = gen.getLMResponse(); + if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) { + userSessionKey = gen.getLanManagerSessionKey(); + } else { + userSessionKey = gen.getLMUserSessionKey(); + } + } + + if ((type2Flags & FLAG_REQUEST_SIGN) != 0) { + if ((type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) != 0) { + 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; + } + 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 + protected void buildMessage() { + final int ntRespLen = ntResp.length; + final int lmRespLen = lmResp.length; + + final int domainLen = domainBytes != null ? domainBytes.length : 0; + final int hostLen = hostBytes != null ? hostBytes.length: 0; + final int userLen = userBytes.length; + final int sessionKeyLen; + if (sessionKey != null) { + sessionKeyLen = sessionKey.length; + } else { + sessionKeyLen = 0; + } + + // Calculate the layout within the packet + 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; + final int hostOffset = userOffset + userLen; + final int sessionKeyOffset = hostOffset + hostLen; + final int finalLength = sessionKeyOffset + sessionKeyLen; + + // Start the response. Length includes signature and type + prepareResponse(finalLength, 3); + + // LM Resp Length (twice) + addUShort(lmRespLen); + addUShort(lmRespLen); + + // LM Resp Offset + addULong(lmRespOffset); + + // NT Resp Length (twice) + addUShort(ntRespLen); + addUShort(ntRespLen); + + // NT Resp Offset + addULong(ntRespOffset); + + // Domain length (twice) + addUShort(domainLen); + addUShort(domainLen); + + // Domain offset. + addULong(domainOffset); + + // User Length (twice) + addUShort(userLen); + addUShort(userLen); + + // User offset + addULong(userOffset); + + // Host length (twice) + addUShort(hostLen); + addUShort(hostLen); + + // Host offset + addULong(hostOffset); + + // Session key length (twice) + addUShort(sessionKeyLen); + addUShort(sessionKeyLen); + + // Session key offset + addULong(sessionKeyOffset); + + // Flags. + addULong( + /* + //FLAG_WORKSTATION_PRESENT | + //FLAG_DOMAIN_PRESENT | + + // Required flags + (type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) | + (type2Flags & FLAG_REQUEST_NTLMv1) | + (type2Flags & FLAG_REQUEST_NTLM2_SESSION) | + + // Protocol version request + FLAG_REQUEST_VERSION | + + // Recommended privacy settings + (type2Flags & FLAG_REQUEST_ALWAYS_SIGN) | + (type2Flags & FLAG_REQUEST_SEAL) | + (type2Flags & FLAG_REQUEST_SIGN) | + + // These must be set according to documentation, based on use of SEAL above + (type2Flags & FLAG_REQUEST_128BIT_KEY_EXCH) | + (type2Flags & FLAG_REQUEST_56BIT_ENCRYPTION) | + (type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) | + + (type2Flags & FLAG_TARGETINFO_PRESENT) | + (type2Flags & FLAG_REQUEST_UNICODE_ENCODING) | + (type2Flags & FLAG_REQUEST_TARGET) + */ + type2Flags + ); + + // Version + addUShort(0x0105); + // Build + addULong(2600); + // NTLM revision + addUShort(0x0f00); + + int micPosition = -1; + if ( computeMic ) { + micPosition = currentOutputPosition; + currentOutputPosition += 16; + } + + // Add the actual data + addBytes(lmResp); + addBytes(ntResp); + addBytes(domainBytes); + addBytes(userBytes); + addBytes(hostBytes); + if (sessionKey != null) { + addBytes(sessionKey); + } + + // 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 ); + + final 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 ( final CertificateEncodingException e ) + { + throw new NTLMEngineException( e.getMessage(), e ); + } + catch ( final 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) { + buffer[offset] = (byte) (value & 0xff); + buffer[offset + 1] = (byte) (value >> 8 & 0xff); + buffer[offset + 2] = (byte) (value >> 16 & 0xff); + buffer[offset + 3] = (byte) (value >> 24 & 0xff); + } + + static int F(final int x, final int y, final int z) { + return ((x & y) | (~x & z)); + } + + static int G(final int x, final int y, final int z) { + return ((x & y) | (x & z) | (y & z)); + } + + static int H(final int x, final int y, final int z) { + return (x ^ y ^ z); + } + + static int rotintlft(final int val, final int numbits) { + return ((val << numbits) | (val >>> (32 - numbits))); + } + + static MessageDigest getMD5() { + try { + return MessageDigest.getInstance("MD5"); + } catch (final 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. + * Code correctness was verified by looking at MD4.java from the jcifs + * library (http://jcifs.samba.org). It was massaged extensively to the + * final form found here by Karl Wright (kwright@metacarta.com). + */ + static class MD4 { + protected int A = 0x67452301; + protected int B = 0xefcdab89; + protected int C = 0x98badcfe; + protected int D = 0x10325476; + protected long count = 0L; + protected final byte[] dataBuffer = new byte[64]; + + MD4() { + } + + void update(final byte[] input) { + // We always deal with 512 bits at a time. Correspondingly, there is + // a buffer 64 bytes long that we write data into until it gets + // full. + int curBufferPos = (int) (count & 63L); + int inputIndex = 0; + while (input.length - inputIndex + curBufferPos >= dataBuffer.length) { + // We have enough data to do the next step. Do a partial copy + // and a transform, updating inputIndex and curBufferPos + // accordingly + final int transferAmt = dataBuffer.length - curBufferPos; + System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); + count += transferAmt; + curBufferPos = 0; + inputIndex += transferAmt; + processBuffer(); + } + + // If there's anything left, copy it into the buffer and leave it. + // We know there's not enough left to process. + if (inputIndex < input.length) { + final int transferAmt = input.length - inputIndex; + System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt); + count += transferAmt; + curBufferPos += transferAmt; + } + } + + byte[] getOutput() { + // Feed pad/length data into engine. This must round out the input + // to a multiple of 512 bits. + final int bufferIndex = (int) (count & 63L); + final int padLen = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex); + final byte[] postBytes = new byte[padLen + 8]; + // Leading 0x80, specified amount of zero padding, then length in + // bits. + postBytes[0] = (byte) 0x80; + // Fill out the last 8 bytes with the length + for (int i = 0; i < 8; i++) { + postBytes[padLen + i] = (byte) ((count * 8) >>> (8 * i)); + } + + // Update the engine + update(postBytes); + + // Calculate final result + final byte[] result = new byte[16]; + writeULong(result, A, 0); + writeULong(result, B, 4); + writeULong(result, C, 8); + writeULong(result, D, 12); + return result; + } + + protected void processBuffer() { + // Convert current buffer to 16 ulongs + final int[] d = new int[16]; + + for (int i = 0; i < 16; i++) { + d[i] = (dataBuffer[i * 4] & 0xff) + ((dataBuffer[i * 4 + 1] & 0xff) << 8) + + ((dataBuffer[i * 4 + 2] & 0xff) << 16) + + ((dataBuffer[i * 4 + 3] & 0xff) << 24); + } + + // Do a round of processing + final int AA = A; + final int BB = B; + final int CC = C; + final int DD = D; + round1(d); + round2(d); + round3(d); + A += AA; + B += BB; + C += CC; + D += DD; + + } + + protected void round1(final int[] d) { + A = rotintlft((A + F(B, C, D) + d[0]), 3); + D = rotintlft((D + F(A, B, C) + d[1]), 7); + C = rotintlft((C + F(D, A, B) + d[2]), 11); + B = rotintlft((B + F(C, D, A) + d[3]), 19); + + A = rotintlft((A + F(B, C, D) + d[4]), 3); + D = rotintlft((D + F(A, B, C) + d[5]), 7); + C = rotintlft((C + F(D, A, B) + d[6]), 11); + B = rotintlft((B + F(C, D, A) + d[7]), 19); + + A = rotintlft((A + F(B, C, D) + d[8]), 3); + D = rotintlft((D + F(A, B, C) + d[9]), 7); + C = rotintlft((C + F(D, A, B) + d[10]), 11); + B = rotintlft((B + F(C, D, A) + d[11]), 19); + + A = rotintlft((A + F(B, C, D) + d[12]), 3); + D = rotintlft((D + F(A, B, C) + d[13]), 7); + C = rotintlft((C + F(D, A, B) + d[14]), 11); + B = rotintlft((B + F(C, D, A) + d[15]), 19); + } + + protected void round2(final int[] d) { + A = rotintlft((A + G(B, C, D) + d[0] + 0x5a827999), 3); + D = rotintlft((D + G(A, B, C) + d[4] + 0x5a827999), 5); + C = rotintlft((C + G(D, A, B) + d[8] + 0x5a827999), 9); + B = rotintlft((B + G(C, D, A) + d[12] + 0x5a827999), 13); + + A = rotintlft((A + G(B, C, D) + d[1] + 0x5a827999), 3); + D = rotintlft((D + G(A, B, C) + d[5] + 0x5a827999), 5); + C = rotintlft((C + G(D, A, B) + d[9] + 0x5a827999), 9); + B = rotintlft((B + G(C, D, A) + d[13] + 0x5a827999), 13); + + A = rotintlft((A + G(B, C, D) + d[2] + 0x5a827999), 3); + D = rotintlft((D + G(A, B, C) + d[6] + 0x5a827999), 5); + C = rotintlft((C + G(D, A, B) + d[10] + 0x5a827999), 9); + B = rotintlft((B + G(C, D, A) + d[14] + 0x5a827999), 13); + + A = rotintlft((A + G(B, C, D) + d[3] + 0x5a827999), 3); + D = rotintlft((D + G(A, B, C) + d[7] + 0x5a827999), 5); + C = rotintlft((C + G(D, A, B) + d[11] + 0x5a827999), 9); + B = rotintlft((B + G(C, D, A) + d[15] + 0x5a827999), 13); + + } + + protected void round3(final int[] d) { + A = rotintlft((A + H(B, C, D) + d[0] + 0x6ed9eba1), 3); + D = rotintlft((D + H(A, B, C) + d[8] + 0x6ed9eba1), 9); + C = rotintlft((C + H(D, A, B) + d[4] + 0x6ed9eba1), 11); + B = rotintlft((B + H(C, D, A) + d[12] + 0x6ed9eba1), 15); + + A = rotintlft((A + H(B, C, D) + d[2] + 0x6ed9eba1), 3); + D = rotintlft((D + H(A, B, C) + d[10] + 0x6ed9eba1), 9); + C = rotintlft((C + H(D, A, B) + d[6] + 0x6ed9eba1), 11); + B = rotintlft((B + H(C, D, A) + d[14] + 0x6ed9eba1), 15); + + A = rotintlft((A + H(B, C, D) + d[1] + 0x6ed9eba1), 3); + D = rotintlft((D + H(A, B, C) + d[9] + 0x6ed9eba1), 9); + C = rotintlft((C + H(D, A, B) + d[5] + 0x6ed9eba1), 11); + B = rotintlft((B + H(C, D, A) + d[13] + 0x6ed9eba1), 15); + + A = rotintlft((A + H(B, C, D) + d[3] + 0x6ed9eba1), 3); + D = rotintlft((D + H(A, B, C) + d[11] + 0x6ed9eba1), 9); + C = rotintlft((C + H(D, A, B) + d[7] + 0x6ed9eba1), 11); + B = rotintlft((B + H(C, D, A) + d[15] + 0x6ed9eba1), 15); + + } + + } + + /** + * Cryptography support - HMACMD5 - algorithmically based on various web + * resources by Karl Wright + */ + static class HMACMD5 { + protected final byte[] ipad; + protected final byte[] opad; + protected final MessageDigest md5; + + HMACMD5(final byte[] input) { + byte[] key = input; + md5 = getMD5(); + + // Initialize the pad buffers with the key + ipad = new byte[64]; + opad = new byte[64]; + + int keyLength = key.length; + if (keyLength > 64) { + // Use MD5 of the key instead, as described in RFC 2104 + md5.update(key); + key = md5.digest(); + keyLength = key.length; + } + int i = 0; + while (i < keyLength) { + ipad[i] = (byte) (key[i] ^ (byte) 0x36); + opad[i] = (byte) (key[i] ^ (byte) 0x5c); + i++; + } + while (i < 64) { + ipad[i] = (byte) 0x36; + opad[i] = (byte) 0x5c; + i++; + } + + // Very important: processChallenge the digest with the ipad buffer + md5.reset(); + md5.update(ipad); + + } + + /** Grab the current digest. This is the "answer". */ + byte[] getOutput() { + final byte[] digest = md5.digest(); + md5.update(opad); + return md5.digest(digest); + } + + /** Update by adding a complete array */ + void update(final byte[] input) { + md5.update(input); + } + + /** Update the algorithm */ + void update(final byte[] input, final int offset, final int length) { + md5.update(input, offset, length); + } + + } + + @Override + public String generateType1Msg( + final String domain, + final String workstation) throws NTLMEngineException { + return getType1Message(workstation, domain); + } + + @Override + public String generateType3Msg( + final String username, + final String password, + final String domain, + final String workstation, + final String challenge) throws NTLMEngineException { + final Type2Message t2m = new Type2Message(challenge); + return getType3Message( + username, + password, + workstation, + domain, + t2m.getChallenge(), + t2m.getFlags(), + t2m.getTarget(), + t2m.getTargetInfo()); + } + +} diff -Nru httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/impl/client/AuthenticationStrategyImpl.java httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/impl/client/AuthenticationStrategyImpl.java --- httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/impl/client/AuthenticationStrategyImpl.java 2017-10-20 09:50:31.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/impl/client/AuthenticationStrategyImpl.java 2018-01-12 14:49:12.000000000 +0000 @@ -190,8 +190,7 @@ authScheme.processChallenge(challenge); final AuthScope authScope = new AuthScope( - authhost.getHostName(), - authhost.getPort(), + authhost, authScheme.getRealm(), authScheme.getSchemeName()); diff -Nru httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/impl/client/SystemDefaultCredentialsProvider.java httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/impl/client/SystemDefaultCredentialsProvider.java --- httpcomponents-client-4.5.4/httpclient/src/main/java/org/apache/http/impl/client/SystemDefaultCredentialsProvider.java 2017-11-26 15:48:01.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient/src/main/java/org/apache/http/impl/client/SystemDefaultCredentialsProvider.java 2018-01-12 14:49:12.000000000 +0000 @@ -111,7 +111,7 @@ final String host = authscope.getHost(); if (host != null) { final HttpHost origin = authscope.getOrigin(); - final String protocol = origin != null ? origin.getSchemeName() : (origin.getPort() == 443 ? "https" : "http"); + final String protocol = origin != null ? origin.getSchemeName() : (authscope.getPort() == 443 ? "https" : "http"); PasswordAuthentication systemcreds = getSystemCreds(protocol, authscope, Authenticator.RequestorType.SERVER); if (systemcreds == null) { systemcreds = getSystemCreds(protocol, authscope, Authenticator.RequestorType.PROXY); diff -Nru httpcomponents-client-4.5.4/httpclient/src/main/resources/mozilla/public-suffix-list.txt httpcomponents-client-4.5.5/httpclient/src/main/resources/mozilla/public-suffix-list.txt --- httpcomponents-client-4.5.4/httpclient/src/main/resources/mozilla/public-suffix-list.txt 2018-01-11 19:20:29.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient/src/main/resources/mozilla/public-suffix-list.txt 2018-01-25 09:50:23.000000000 +0000 @@ -3537,8 +3537,17 @@ yamanakako.yamanashi.jp yamanashi.yamanashi.jp -// ke : http://www.kenic.or.ke/index.php?option=com_content&task=view&id=117&Itemid=145 -*.ke +// ke : http://www.kenic.or.ke/index.php/en/ke-domains/ke-domains +ke +ac.ke +co.ke +go.ke +info.ke +me.ke +mobi.ke +ne.ke +or.ke +sc.ke // kg : http://www.domain.kg/dmn_n.html kg @@ -11557,10 +11566,6 @@ freebox-os.fr freeboxos.fr -// Fusion Intranet : https://www.fusion-intranet.com -// Submitted by Matthias Burtscher -myfusion.cloud - // Futureweb OG : http://www.futureweb.at // Submitted by Andreas Schnederle-Wagner *.futurecms.at diff -Nru httpcomponents-client-4.5.4/httpclient/src/test/java/org/apache/http/client/entity/TestGZip.java httpcomponents-client-4.5.5/httpclient/src/test/java/org/apache/http/client/entity/TestGZip.java --- httpcomponents-client-4.5.4/httpclient/src/test/java/org/apache/http/client/entity/TestGZip.java 2017-09-30 10:33:06.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient/src/test/java/org/apache/http/client/entity/TestGZip.java 2018-01-12 14:49:12.000000000 +0000 @@ -27,6 +27,7 @@ package org.apache.http.client.entity; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -35,6 +36,7 @@ import org.apache.http.HttpEntity; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; +import org.apache.http.entity.InputStreamEntity; import org.apache.http.entity.StringEntity; import org.apache.http.util.EntityUtils; import org.junit.Assert; @@ -79,4 +81,23 @@ } } + @Test + public void testDecompressionWithMultipleGZipStream() throws IOException { + final int[] data = new int[] { + 0x1f, 0x8b, 0x08, 0x08, 0x03, 0xf1, 0x55, 0x5a, 0x00, 0x03, 0x74, 0x65, 0x73, 0x74, 0x31, 0x00, + 0x2b, 0x2e, 0x29, 0x4a, 0x4d, 0xcc, 0xd5, 0x35, 0xe4, 0x02, 0x00, 0x03, 0x61, 0xf0, 0x5f, 0x09, + 0x00, 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x08, 0x08, 0xf1, 0x55, 0x5a, 0x00, 0x03, 0x74, 0x65, 0x73, + 0x74, 0x32, 0x00, 0x2b, 0x2e, 0x29, 0x4a, 0x4d, 0xcc, 0xd5, 0x35, 0xe2, 0x02, 0x00, 0xc0, 0x32, + 0xdd, 0x74, 0x09, 0x00, 0x00, 0x00 + }; + final byte[] bytes = new byte[data.length]; + for (int i = 0; i < data.length; i++) { + bytes[i] = (byte) (data[i] & 0xff); + } + + final InputStreamEntity out = new InputStreamEntity(new ByteArrayInputStream(bytes)); + final GzipDecompressingEntity gunZipEntity = new GzipDecompressingEntity(out); + Assert.assertEquals("stream-1\nstream-2\n", EntityUtils.toString(gunZipEntity, Consts.ASCII)); + } + } diff -Nru httpcomponents-client-4.5.4/httpclient/src/test/java/org/apache/http/client/utils/TestURLEncodedUtils.java httpcomponents-client-4.5.5/httpclient/src/test/java/org/apache/http/client/utils/TestURLEncodedUtils.java --- httpcomponents-client-4.5.4/httpclient/src/test/java/org/apache/http/client/utils/TestURLEncodedUtils.java 2017-09-30 10:33:06.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient/src/test/java/org/apache/http/client/utils/TestURLEncodedUtils.java 2018-01-18 10:44:19.000000000 +0000 @@ -170,6 +170,14 @@ } @Test + public void testEmptyQuery() throws Exception { + final List result = URLEncodedUtils.parse("", Consts.UTF_8); + Assert.assertEquals(0, result.size()); + // [HTTPCLIENT-1889]: + result.add(new BasicNameValuePair("key", "value")); + } + + @Test public void testParseEntity() throws Exception { final StringEntity entity = new StringEntity("Name1=Value1"); diff -Nru httpcomponents-client-4.5.4/httpclient-cache/pom.xml httpcomponents-client-4.5.5/httpclient-cache/pom.xml --- httpcomponents-client-4.5.4/httpclient-cache/pom.xml 2017-11-27 09:36:12.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient-cache/pom.xml 2018-01-18 11:51:14.000000000 +0000 @@ -28,7 +28,7 @@ org.apache.httpcomponents httpcomponents-client - 4.5.4 + 4.5.5 httpclient-cache Apache HttpClient Cache diff -Nru httpcomponents-client-4.5.4/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryUpdater.java httpcomponents-client-4.5.5/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryUpdater.java --- httpcomponents-client-4.5.4/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryUpdater.java 2017-09-30 10:33:06.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheEntryUpdater.java 2018-01-15 09:00:37.000000000 +0000 @@ -27,13 +27,10 @@ package org.apache.http.impl.client.cache; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; -import java.util.List; -import java.util.ListIterator; import org.apache.http.Header; +import org.apache.http.HeaderIterator; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.annotation.Contract; @@ -43,6 +40,7 @@ import org.apache.http.client.cache.Resource; import org.apache.http.client.cache.ResourceFactory; import org.apache.http.client.utils.DateUtils; +import org.apache.http.message.HeaderGroup; import org.apache.http.protocol.HTTP; import org.apache.http.util.Args; @@ -102,51 +100,46 @@ } protected Header[] mergeHeaders(final HttpCacheEntry entry, final HttpResponse response) { - if (entryAndResponseHaveDateHeader(entry, response) && entryDateHeaderNewerThenResponse(entry, response)) { // Don't merge headers, keep the entry's headers as they are newer. return entry.getAllHeaders(); } - final List
cacheEntryHeaderList = new ArrayList
(Arrays.asList(entry - .getAllHeaders())); - removeCacheHeadersThatMatchResponse(cacheEntryHeaderList, response); - removeCacheEntry1xxWarnings(cacheEntryHeaderList, entry); - cacheEntryHeaderList.addAll(Arrays.asList(response.getAllHeaders())); - - return cacheEntryHeaderList.toArray(new Header[cacheEntryHeaderList.size()]); - } - - private void removeCacheHeadersThatMatchResponse(final List
cacheEntryHeaderList, - final HttpResponse response) { - for (final Header responseHeader : response.getAllHeaders()) { - final ListIterator
cacheEntryHeaderListIter = cacheEntryHeaderList.listIterator(); - - while (cacheEntryHeaderListIter.hasNext()) { - final String cacheEntryHeaderName = cacheEntryHeaderListIter.next().getName(); + final HeaderGroup headerGroup = new HeaderGroup(); + headerGroup.setHeaders(entry.getAllHeaders()); + // Remove cache headers that match response + for (final HeaderIterator it = response.headerIterator(); it.hasNext(); ) { + final Header responseHeader = it.nextHeader(); + // Since we do not expect a content in a 304 response, should retain the original Content-Encoding header + if (HTTP.CONTENT_ENCODING.equals(responseHeader.getName())) { + continue; + } + final Header[] matchingHeaders = headerGroup.getHeaders(responseHeader.getName()); + for (final Header matchingHeader : matchingHeaders) { + headerGroup.removeHeader(matchingHeader); + } - if (cacheEntryHeaderName.equals(responseHeader.getName())) { - cacheEntryHeaderListIter.remove(); + } + // remove cache entry 1xx warnings + for (final HeaderIterator it = headerGroup.iterator(); it.hasNext(); ) { + final Header cacheHeader = it.nextHeader(); + if (HeaderConstants.WARNING.equalsIgnoreCase(cacheHeader.getName())) { + final String warningValue = cacheHeader.getValue(); + if (warningValue != null && warningValue.startsWith("1")) { + it.remove(); } } } - } - - private void removeCacheEntry1xxWarnings(final List
cacheEntryHeaderList, final HttpCacheEntry entry) { - final ListIterator
cacheEntryHeaderListIter = cacheEntryHeaderList.listIterator(); - - while (cacheEntryHeaderListIter.hasNext()) { - final String cacheEntryHeaderName = cacheEntryHeaderListIter.next().getName(); - - if (HeaderConstants.WARNING.equals(cacheEntryHeaderName)) { - for (final Header cacheEntryWarning : entry.getHeaders(HeaderConstants.WARNING)) { - if (cacheEntryWarning.getValue().startsWith("1")) { - cacheEntryHeaderListIter.remove(); - } - } + for (final HeaderIterator it = response.headerIterator(); it.hasNext(); ) { + final Header responseHeader = it.nextHeader(); + // Since we do not expect a content in a 304 response, should avoid updating Content-Encoding header + if (HTTP.CONTENT_ENCODING.equals(responseHeader.getName())) { + continue; } + headerGroup.addHeader(responseHeader); } + return headerGroup.getAllHeaders(); } private boolean entryDateHeaderNewerThenResponse(final HttpCacheEntry entry, final HttpResponse response) { @@ -154,22 +147,13 @@ .getValue()); final Date responseDate = DateUtils.parseDate(response.getFirstHeader(HTTP.DATE_HEADER) .getValue()); - if (entryDate == null || responseDate == null) { - return false; - } - if (!entryDate.after(responseDate)) { - return false; - } - return true; + return entryDate != null && responseDate != null && entryDate.after(responseDate); } private boolean entryAndResponseHaveDateHeader(final HttpCacheEntry entry, final HttpResponse response) { - if (entry.getFirstHeader(HTTP.DATE_HEADER) != null - && response.getFirstHeader(HTTP.DATE_HEADER) != null) { - return true; - } + return entry.getFirstHeader(HTTP.DATE_HEADER) != null + && response.getFirstHeader(HTTP.DATE_HEADER) != null; - return false; } } diff -Nru httpcomponents-client-4.5.4/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntryUpdater.java httpcomponents-client-4.5.5/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntryUpdater.java --- httpcomponents-client-4.5.4/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntryUpdater.java 2017-05-17 12:31:04.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestCacheEntryUpdater.java 2018-01-15 10:44:10.000000000 +0000 @@ -239,6 +239,27 @@ } } + @Test + public void testContentEncodingHeaderIsNotUpdatedByMerge() throws IOException { + final Header[] headers = { + new BasicHeader("Date", DateUtils.formatDate(requestDate)), + new BasicHeader("ETag", "\"etag\""), + new BasicHeader("Content-Encoding", "identity")}; + + entry = HttpTestUtils.makeCacheEntry(headers); + response.setHeaders(new Header[]{ + new BasicHeader("Last-Modified", DateUtils.formatDate(responseDate)), + new BasicHeader("Cache-Control", "public"), + new BasicHeader("Content-Encoding", "gzip")}); + + final HttpCacheEntry updatedEntry = impl.updateCacheEntry(null, entry, + new Date(), new Date(), response); + + final Header[] updatedHeaders = updatedEntry.getAllHeaders(); + headersContain(updatedHeaders, "Content-Encoding", "identity"); + headersNotContain(updatedHeaders, "Content-Encoding", "gzip"); + } + private void headersContain(final Header[] headers, final String name, final String value) { for (final Header header : headers) { if (header.getName().equals(name)) { @@ -250,4 +271,13 @@ fail("Header [" + name + ": " + value + "] not found in headers."); } + private void headersNotContain(final Header[] headers, final String name, final String value) { + for (final Header header : headers) { + if (header.getName().equals(name)) { + if (header.getValue().equals(value)) { + fail("Header [" + name + ": " + value + "] found in headers where it should not be"); + } + } + } + } } diff -Nru httpcomponents-client-4.5.4/httpclient-osgi/pom.xml httpcomponents-client-4.5.5/httpclient-osgi/pom.xml --- httpcomponents-client-4.5.4/httpclient-osgi/pom.xml 2017-11-27 09:36:12.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient-osgi/pom.xml 2018-01-18 11:51:14.000000000 +0000 @@ -28,7 +28,7 @@ org.apache.httpcomponents httpcomponents-client - 4.5.4 + 4.5.5 httpclient-osgi Apache HttpClient OSGi bundle diff -Nru httpcomponents-client-4.5.4/httpclient-win/pom.xml httpcomponents-client-4.5.5/httpclient-win/pom.xml --- httpcomponents-client-4.5.4/httpclient-win/pom.xml 2017-11-27 09:36:12.000000000 +0000 +++ httpcomponents-client-4.5.5/httpclient-win/pom.xml 2018-01-18 11:51:14.000000000 +0000 @@ -28,7 +28,7 @@ org.apache.httpcomponents httpcomponents-client - 4.5.4 + 4.5.5 httpclient-win Apache HttpClient Windows features diff -Nru httpcomponents-client-4.5.4/httpmime/pom.xml httpcomponents-client-4.5.5/httpmime/pom.xml --- httpcomponents-client-4.5.4/httpmime/pom.xml 2017-11-27 09:36:12.000000000 +0000 +++ httpcomponents-client-4.5.5/httpmime/pom.xml 2018-01-18 11:51:14.000000000 +0000 @@ -28,7 +28,7 @@ org.apache.httpcomponents httpcomponents-client - 4.5.4 + 4.5.5 httpmime Apache HttpClient Mime diff -Nru httpcomponents-client-4.5.4/NOTICE.txt httpcomponents-client-4.5.5/NOTICE.txt --- httpcomponents-client-4.5.4/NOTICE.txt 2017-09-30 10:33:06.000000000 +0000 +++ httpcomponents-client-4.5.5/NOTICE.txt 2018-01-18 10:44:19.000000000 +0000 @@ -1,6 +1,6 @@ -Apache HttpComponents Client -Copyright 1999-2017 The Apache Software Foundation - -This product includes software developed at -The Apache Software Foundation (http://www.apache.org/). - +Apache HttpComponents Client +Copyright 1999-2018 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + diff -Nru httpcomponents-client-4.5.4/pom.xml httpcomponents-client-4.5.5/pom.xml --- httpcomponents-client-4.5.4/pom.xml 2017-11-27 09:36:12.000000000 +0000 +++ httpcomponents-client-4.5.5/pom.xml 2018-01-18 11:51:14.000000000 +0000 @@ -33,7 +33,7 @@ 4.0.0 httpcomponents-client Apache HttpComponents Client - 4.5.4 + 4.5.5 Apache HttpComponents Client is a library of components for building client side HTTP services http://hc.apache.org/httpcomponents-client-ga/ 1999 @@ -61,13 +61,13 @@ scm:git:https://git-wip-us.apache.org/repos/asf/httpcomponents-client.git scm:git:https://git-wip-us.apache.org/repos/asf/httpcomponents-client.git https://github.com/apache/httpcomponents-client/tree/${project.scm.tag} - 4.5.4 + 4.5.5 1.6 1.6 - 4.4.7 + 4.4.9 1.2 1.10 2.6.11 @@ -78,7 +78,7 @@ 1.10.19 4.4.0 1 - 4.4 + 4.5 diff -Nru httpcomponents-client-4.5.4/RELEASE_NOTES.txt httpcomponents-client-4.5.5/RELEASE_NOTES.txt --- httpcomponents-client-4.5.4/RELEASE_NOTES.txt 2017-11-27 09:29:27.000000000 +0000 +++ httpcomponents-client-4.5.5/RELEASE_NOTES.txt 2018-01-18 11:48:31.000000000 +0000 @@ -1,3 +1,28 @@ +Release 4.5.5 +------------------- + +HttpClient 4.5.5 (GA) is a maintenance release that fixes a regression introduced +by the previous release causing a NPE in SystemDefaultCredentialsProvider. + +Please note that as of 4.4 HttpClient requires Java 1.6 or newer. + +Changelog: +------------------- + +* [HTTPCLIENT-1690] Avoid merging Content-Encoding headers coming with 304 status to cache entry. + Contributed by Sudheera Palihakkara + +* [HTTPCLIENT-1888] Regression in SystemDefaultCredentialsProvider#getCredentials causing NPE. + Contributed by Oleg Kalnichevski + +* [HTTPCLIENT-1886] Update HttpClient 4.5.x from HttpCore 4.4.7 to 4.4.9 + Contributed by Gary Gregory + +* [HTTPCLIENT-1889] org.apache.http.client.utils.URLEncodedUtils.parse() + should return a new ArrayList when there are no query parameters. + Contributed by Gary Gregory + + Release 4.5.4 -------------------