package org.apache.http.impl.auth;
import java.nio.charset.Charset;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.Arrays;
import java.util.Locale;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.Consts;
final class NTLMEngineImpl implements NTLMEngine {
private static final Charset UNICODE_LITTLE_UNMARKED = Charset.forName("UnicodeLittleUnmarked");
private static final Charset DEFAULT_CHARSET = Consts.ASCII;
static final int FLAG_REQUEST_UNICODE_ENCODING = 0x00000001;
static final int FLAG_REQUEST_OEM_ENCODING = 0x00000002;
static final int FLAG_REQUEST_TARGET = 0x00000004;
static final int FLAG_REQUEST_SIGN = 0x00000010;
static final int FLAG_REQUEST_SEAL = 0x00000020;
static final int FLAG_REQUEST_LAN_MANAGER_KEY = 0x00000080;
static final int FLAG_REQUEST_NTLMv1 = 0x00000200;
static final int FLAG_DOMAIN_PRESENT = 0x00001000;
static final int FLAG_WORKSTATION_PRESENT = 0x00002000;
static final int FLAG_REQUEST_ALWAYS_SIGN = 0x00008000;
static final int FLAG_REQUEST_NTLM2_SESSION = 0x00080000;
static final int FLAG_REQUEST_VERSION = 0x02000000;
static final int FLAG_TARGETINFO_PRESENT = 0x00800000;
static final int FLAG_REQUEST_128BIT_KEY_EXCH = 0x20000000;
static final int FLAG_REQUEST_EXPLICIT_KEY_EXCH = 0x40000000;
static final int FLAG_REQUEST_56BIT_ENCRYPTION = 0x80000000;
static final int MSV_AV_EOL = 0x0000;
static final int MSV_AV_NB_COMPUTER_NAME = 0x0001;
static final int MSV_AV_NB_DOMAIN_NAME = 0x0002;
static final int MSV_AV_DNS_COMPUTER_NAME = 0x0003;
static final int MSV_AV_DNS_DOMAIN_NAME = 0x0004;
static final int MSV_AV_DNS_TREE_NAME = 0x0005;
static final int MSV_AV_FLAGS = 0x0006;
static final int MSV_AV_TIMESTAMP = 0x0007;
static final int MSV_AV_SINGLE_HOST = 0x0008;
static final int MSV_AV_TARGET_NAME = 0x0009;
static final int MSV_AV_CHANNEL_BINDINGS = 0x000A;
static final int MSV_AV_FLAGS_ACCOUNT_AUTH_CONSTAINED = 0x00000001;
static final int MSV_AV_FLAGS_MIC = 0x00000002;
static final int MSV_AV_FLAGS_UNTRUSTED_TARGET_SPN = 0x00000004;
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;
}
private static final byte[] SIGNATURE = getNullTerminatedAsciiString("NTLMSSP");
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");
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() {
}
static String getType1Message(final String host, final String domain) {
return TYPE_1_MESSAGE;
}
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();
}
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) {
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) {
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) {
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;
}
private static byte[] makeRandomChallenge(final Random random) {
final byte[] rval = new byte[8];
synchronized (random) {
random.nextBytes(rval);
}
return rval;
}
private static byte[] makeSecondaryKey(final Random random) {
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;
protected byte[] clientChallenge;
protected byte[] clientChallenge2;
protected byte[] secondaryKey;
protected byte[] timestamp;
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);
}
public byte[] getClientChallenge()
throws NTLMEngineException {
if (clientChallenge == null) {
clientChallenge = makeRandomChallenge(random);
}
return clientChallenge;
}
public byte[] getClientChallenge2()
throws NTLMEngineException {
if (clientChallenge2 == null) {
clientChallenge2 = makeRandomChallenge(random);
}
return clientChallenge2;
}
public byte[] getSecondaryKey()
throws NTLMEngineException {
if (secondaryKey == null) {
secondaryKey = makeSecondaryKey(random);
}
return secondaryKey;
}
public byte[] getLMHash()
throws NTLMEngineException {
if (lmHash == null) {
lmHash = lmHash(password);
}
return lmHash;
}
public byte[] getLMResponse()
throws NTLMEngineException {
if (lmResponse == null) {
lmResponse = lmResponse(getLMHash(),challenge);
}
return lmResponse;
}
public byte[] getNTLMHash()
throws NTLMEngineException {
if (ntlmHash == null) {
ntlmHash = ntlmHash(password);
}
return ntlmHash;
}
public byte[] getNTLMResponse()
throws NTLMEngineException {
if (ntlmResponse == null) {
ntlmResponse = lmResponse(getNTLMHash(),challenge);
}
return ntlmResponse;
}
public byte[] getLMv2Hash()
throws NTLMEngineException {
if (lmv2Hash == null) {
lmv2Hash = lmv2Hash(domain, user, getNTLMHash());
}
return lmv2Hash;
}
public byte[] getNTLMv2Hash()
throws NTLMEngineException {
if (ntlmv2Hash == null) {
ntlmv2Hash = ntlmv2Hash(domain, user, getNTLMHash());
}
return ntlmv2Hash;
}
public byte[] getTimestamp() {
if (timestamp == null) {
long time = this.currentTime;
time += 11644473600000l;
time *= 10000;
timestamp = new byte[8];
for (int i = 0; i < 8; i++) {
timestamp[i] = (byte) time;
time >>>= 8;
}
}
return timestamp;
}
public byte[] getNTLMv2Blob()
throws NTLMEngineException {
if (ntlmv2Blob == null) {
ntlmv2Blob = createBlob(getClientChallenge2(), targetInformation, getTimestamp());
}
return ntlmv2Blob;
}
public byte[] getNTLMv2Response()
throws NTLMEngineException {
if (ntlmv2Response == null) {
ntlmv2Response = lmv2Response(getNTLMv2Hash(),challenge,getNTLMv2Blob());
}
return ntlmv2Response;
}
public byte[] getLMv2Response()
throws NTLMEngineException {
if (lmv2Response == null) {
lmv2Response = lmv2Response(getLMv2Hash(),challenge,getClientChallenge());
}
return lmv2Response;
}
public byte[] getNTLM2SessionResponse()
throws NTLMEngineException {
if (ntlm2SessionResponse == null) {
ntlm2SessionResponse = ntlm2SessionResponse(getNTLMHash(),challenge,getClientChallenge());
}
return ntlm2SessionResponse;
}
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;
}
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;
}
public byte[] getNTLMUserSessionKey()
throws NTLMEngineException {
if (ntlmUserSessionKey == null) {
final MD4 md4 = new MD4();
md4.update(getNTLMHash());
ntlmUserSessionKey = md4.getOutput();
}
return ntlmUserSessionKey;
}
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;
}
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;
}
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;
}
}
static byte[] hmacMD5(final byte[] value, final byte[] key)
throws NTLMEngineException {
final HMACMD5 hmacMD5 = new HMACMD5(key);
hmacMD5.update(value);
return hmacMD5.getOutput();
}
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);
}
}
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);
}
}
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);
}
}
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();
}
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);
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();
}
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);
hmacMD5.update(user.toUpperCase(Locale.ROOT).getBytes(UNICODE_LITTLE_UNMARKED));
if (domain != null) {
hmacMD5.update(domain.getBytes(UNICODE_LITTLE_UNMARKED));
}
return hmacMD5.getOutput();
}
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);
}
}
private static byte[] lmv2Response(final byte[] hash, final byte[] challenge, final byte[] clientData) {
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 )
{
return rc4.update( data );
}
private byte[] decrypt( final byte[] data )
{
return rc4.update( data );
}
private byte[] computeSignature( final byte[] message )
{
final byte[] sig = new byte[16];
sig[0] = 0x01;
sig[1] = 0x00;
sig[2] = 0x00;
sig[3] = 0x00;
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 );
encodeLong( sig, 12, sequenceNumber );
return sig;
}
private boolean validateSignature( final byte[] signature, final byte message[] )
{
final byte[] computedSignature = computeSignature( message );
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 );
}
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;
}
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");
}
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;
}
}
}
private static Charset getCharset(final int flags) throws NTLMEngineException
{
if ((flags & FLAG_REQUEST_UNICODE_ENCODING) == 0) {
return DEFAULT_CHARSET;
}
if (UNICODE_LITTLE_UNMARKED == null) {
throw new NTLMEngineException( "Unicode not supported" );
}
return UNICODE_LITTLE_UNMARKED;
}
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;
}
private static String convertHost(final String host) {
return stripDotSuffix(host);
}
private static String convertDomain(final String domain) {
return stripDotSuffix(domain);
}
static class NTLMMessage {
protected byte[] messageContents = null;
protected int currentOutputPosition = 0;
NTLMMessage() {
}
NTLMMessage(final String messageBody, final int expectedType) throws NTLMEngineException {
this(Base64.decodeBase64(messageBody.getBytes(DEFAULT_CHARSET)), expectedType);
}
NTLMMessage(final byte[] message, final int expectedType) throws NTLMEngineException {
messageContents = 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++;
}
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;
}
protected int getPreambleLength() {
return SIGNATURE.length + 4;
}
protected int getMessageLength() {
return currentOutputPosition;
}
protected byte readByte(final int position) throws NTLMEngineException {
if (messageContents.length < position + 1) {
throw new NTLMEngineException("NTLM: Message too short");
}
return messageContents[position];
}
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);
}
protected int readUShort(final int position) throws NTLMEngineException {
return NTLMEngineImpl.readUShort(messageContents, position);
}
protected int readULong(final int position) throws NTLMEngineException {
return NTLMEngineImpl.readULong(messageContents, position);
}
protected byte[] readSecurityBuffer(final int position) throws NTLMEngineException {
return NTLMEngineImpl.readSecurityBuffer(messageContents, position);
}
protected void prepareResponse(final int maxlength, final int messageType) {
messageContents = new byte[maxlength];
currentOutputPosition = 0;
addBytes(SIGNATURE);
addULong(messageType);
}
protected void addByte(final byte b) {
messageContents[currentOutputPosition] = b;
currentOutputPosition++;
}
protected void addBytes(final byte[] bytes) {
if (bytes == null) {
return;
}
for (final byte b : bytes) {
messageContents[currentOutputPosition] = b;
currentOutputPosition++;
}
}
protected void addUShort(final int value) {
addByte((byte) (value & 0xff));
addByte((byte) (value >> 8 & 0xff));
}
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));
}
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());
}
}
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);
final String unqualifiedHost = convertHost(host);
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_REQUEST_NTLMv1 |
FLAG_REQUEST_NTLM2_SESSION |
FLAG_REQUEST_VERSION |
FLAG_REQUEST_ALWAYS_SIGN |
FLAG_REQUEST_128BIT_KEY_EXCH |
FLAG_REQUEST_56BIT_ENCRYPTION |
FLAG_REQUEST_UNICODE_ENCODING;
}
@Override
protected void buildMessage() {
int domainBytesLength = 0;
if ( domainBytes != null ) {
domainBytesLength = domainBytes.length;
}
int hostBytesLength = 0;
if ( hostBytes != null ) {
hostBytesLength = hostBytes.length;
}
final int finalLength = 32 + 8 + hostBytesLength + domainBytesLength;
prepareResponse(finalLength, 1);
addULong(flags);
addUShort(domainBytesLength);
addUShort(domainBytesLength);
addULong(hostBytesLength + 32 + 8);
addUShort(hostBytesLength);
addUShort(hostBytesLength);
addULong(32 + 8);
addUShort(0x0105);
addULong(2600);
addUShort(0x0f00);
if (hostBytes != null) {
addBytes(hostBytes);
}
if (domainBytes != null) {
addBytes(domainBytes);
}
}
}
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);
challenge = new byte[8];
readBytes(challenge, 24);
flags = readULong(20);
target = null;
if (getMessageLength() >= 12 + 8) {
final byte[] bytes = readSecurityBuffer(12);
if (bytes.length != 0) {
target = new String(bytes, getCharset(flags));
}
}
targetInfo = null;
if (getMessageLength() >= 40 + 8) {
final byte[] bytes = readSecurityBuffer(40);
if (bytes.length != 0) {
targetInfo = bytes;
}
}
}
byte[] getChallenge() {
return challenge;
}
String getTarget() {
return target;
}
byte[] getTargetInfo() {
return targetInfo;
}
int getFlags() {
return flags;
}
}
static class Type3Message extends NTLMMessage {
protected final byte[] type1Message;
protected final byte[] type2Message;
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;
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);
}
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);
}
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);
}
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");
}
this.type2Flags = type2Flags;
this.type1Message = type1Message;
this.type2Message = type2Message;
final String unqualifiedHost = convertHost(host);
final String unqualifiedDomain = convertDomain(domain);
byte[] responseTargetInformation = targetInformation;
if (peerServerCertificate != null) {
responseTargetInformation = addGssMicAvsToTargetInfo(targetInformation, peerServerCertificate);
computeMic = true;
} else {
computeMic = false;
}
final CipherGen gen = new CipherGen(random, currentTime,
unqualifiedDomain,
user,
password,
nonce,
target,
responseTargetInformation);
byte[] userSessionKey;
try {
if (((type2Flags & FLAG_TARGETINFO_PRESENT) != 0) &&
targetInformation != null && target != null) {
ntResp = gen.getNTLMv2Response();
lmResp = gen.getLMv2Response();
if ((type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) != 0) {
userSessionKey = gen.getLanManagerSessionKey();
} else {
userSessionKey = gen.getNTLMv2UserSessionKey();
}
} else {
if ((type2Flags & FLAG_REQUEST_NTLM2_SESSION) != 0) {
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) {
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;
}
@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;
}
final int lmRespOffset = 72 +
( computeMic ? 16 : 0 );
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;
prepareResponse(finalLength, 3);
addUShort(lmRespLen);
addUShort(lmRespLen);
addULong(lmRespOffset);
addUShort(ntRespLen);
addUShort(ntRespLen);
addULong(ntRespOffset);
addUShort(domainLen);
addUShort(domainLen);
addULong(domainOffset);
addUShort(userLen);
addUShort(userLen);
addULong(userOffset);
addUShort(hostLen);
addUShort(hostLen);
addULong(hostOffset);
addUShort(sessionKeyLen);
addUShort(sessionKeyLen);
addULong(sessionKeyOffset);
addULong(
type2Flags
);
addUShort(0x0105);
addULong(2600);
addUShort(0x0f00);
int micPosition = -1;
if ( computeMic ) {
micPosition = currentOutputPosition;
currentOutputPosition += 16;
}
addBytes(lmResp);
addBytes(ntResp);
addBytes(domainBytes);
addBytes(userBytes);
addBytes(hostBytes);
if (sessionKey != null) {
addBytes(sessionKey);
}
if (computeMic) {
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 );
}
}
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;
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);
}
}
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) {
int curBufferPos = (int) (count & 63L);
int inputIndex = 0;
while (input.length - inputIndex + curBufferPos >= dataBuffer.length) {
final int transferAmt = dataBuffer.length - curBufferPos;
System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt);
count += transferAmt;
curBufferPos = 0;
inputIndex += transferAmt;
processBuffer();
}
if (inputIndex < input.length) {
final int transferAmt = input.length - inputIndex;
System.arraycopy(input, inputIndex, dataBuffer, curBufferPos, transferAmt);
count += transferAmt;
curBufferPos += transferAmt;
}
}
byte[] getOutput() {
final int bufferIndex = (int) (count & 63L);
final int padLen = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex);
final byte[] postBytes = new byte[padLen + 8];
postBytes[0] = (byte) 0x80;
for (int i = 0; i < 8; i++) {
postBytes[padLen + i] = (byte) ((count * 8) >>> (8 * i));
}
update(postBytes);
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() {
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);
}
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);
}
}
static class HMACMD5 {
protected final byte[] ipad;
protected final byte[] opad;
protected final MessageDigest md5;
HMACMD5(final byte[] input) {
byte[] key = input;
md5 = getMD5();
ipad = new byte[64];
opad = new byte[64];
int keyLength = key.length;
if (keyLength > 64) {
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++;
}
md5.reset();
md5.update(ipad);
}
byte[] getOutput() {
final byte[] digest = md5.digest();
md5.update(opad);
return md5.digest(digest);
}
void update(final byte[] input) {
md5.update(input);
}
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());
}
}