/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2011-2012 profiq s.r.o. * Portions copyright 2011 ForgeRock AS. */ package org.opends.server.plugins; import static org.opends.messages.PluginMessages.*; import static org.opends.server.loggers.ErrorLogger.logError; import static org.opends.server.util.StaticUtils.bytesToHexNoSpace; import static org.opends.server.util.StaticUtils.toLowerCase; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.*; import javax.crypto.*; import javax.crypto.spec.SecretKeySpec; import org.opends.messages.Message; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.meta.PluginCfgDefn; import org.opends.server.admin.std.meta.SambaPasswordPluginCfgDefn.*; import org.opends.server.admin.std.server.SambaPasswordPluginCfg; import org.opends.server.api.plugin.DirectoryServerPlugin; import org.opends.server.api.plugin.PluginResult; import org.opends.server.api.plugin.PluginType; import org.opends.server.config.ConfigException; import org.opends.server.controls.LDAPAssertionRequestControl; import org.opends.server.core.DirectoryServer; import org.opends.server.core.ModifyOperation; import org.opends.server.extensions.PasswordModifyExtendedOperation; import org.opends.server.loggers.debug.DebugLogger; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.protocols.ldap.LDAPFilter; import org.opends.server.types.*; import org.opends.server.types.operation.PostOperationExtendedOperation; import org.opends.server.types.operation.PreOperationModifyOperation; /** * The Samba password synchronization plugin implementation class. *
* This plugin synchronizes the userPassword attribute with the Samba password * attribute(s) for all entries containing the specified Samba object class. *
* It handles clear-text userPassword modify operations and password modify
* extended operations. It does not cover the case of using pre-encoded
* password.
*/
public final class SambaPasswordPlugin extends
DirectoryServerPlugin
* Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle
* (http://www.bouncycastle.org)
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
static final class MD4MessageDigest extends MessageDigest
{
// Class is package private for testing.
private final byte[] xBuf = new byte[4];
private int xBufOff;
private long byteCount;
private static final int DIGEST_LENGTH = 16;
private int H1, H2, H3, H4; // IV's
private final int[] X = new int[16];
private int xOff;
//
// round 1 left rotates
//
private static final int S11 = 3;
private static final int S12 = 7;
private static final int S13 = 11;
private static final int S14 = 19;
//
// round 2 left rotates
//
private static final int S21 = 3;
private static final int S22 = 5;
private static final int S23 = 9;
private static final int S24 = 13;
//
// round 3 left rotates
//
private static final int S31 = 3;
private static final int S32 = 9;
private static final int S33 = 11;
private static final int S34 = 15;
/**
* Creates a new MD4 message digest algorithm.
*/
MD4MessageDigest()
{
super("MD4");
engineReset();
}
/**
* {@inheritDoc}
*/
@Override
public byte[] engineDigest()
{
final byte[] digestBytes = new byte[DIGEST_LENGTH];
finish();
unpackWord(H1, digestBytes, 0);
unpackWord(H2, digestBytes, 4);
unpackWord(H3, digestBytes, 8);
unpackWord(H4, digestBytes, 12);
engineReset();
return digestBytes;
}
/**
* {@inheritDoc}
*/
@Override
public void engineReset()
{
byteCount = 0;
xBufOff = 0;
for (int i = 0; i < xBuf.length; i++)
{
xBuf[i] = 0;
}
H1 = 0x67452301;
H2 = 0xefcdab89;
H3 = 0x98badcfe;
H4 = 0x10325476;
xOff = 0;
for (int i = 0; i != X.length; i++)
{
X[i] = 0;
}
}
/**
* {@inheritDoc}
*/
@Override
public void engineUpdate(final byte input)
{
xBuf[xBufOff++] = input;
if (xBufOff == xBuf.length)
{
processWord(xBuf, 0);
xBufOff = 0;
}
byteCount++;
}
/**
* {@inheritDoc}
*/
@Override
public void engineUpdate(final byte[] in, int inOff, int len)
{
//
// fill the current word
//
while ((xBufOff != 0) && (len > 0))
{
update(in[inOff]);
inOff++;
len--;
}
//
// process whole words.
//
while (len > xBuf.length)
{
processWord(in, inOff);
inOff += xBuf.length;
len -= xBuf.length;
byteCount += xBuf.length;
}
//
// load in the remainder.
//
while (len > 0)
{
update(in[inOff]);
inOff++;
len--;
}
}
/*
* F, G, H and I are the basic MD4 functions.
*/
private int F(final int u, final int v, final int w)
{
return (u & v) | (~u & w);
}
private void finish()
{
final long bitLength = (byteCount << 3);
//
// add the pad bytes.
//
engineUpdate((byte) 128);
while (xBufOff != 0)
{
engineUpdate((byte) 0);
}
processLength(bitLength);
processBlock();
}
private int G(final int u, final int v, final int w)
{
return (u & v) | (u & w) | (v & w);
}
private int H(final int u, final int v, final int w)
{
return u ^ v ^ w;
}
private void processBlock()
{
int a = H1;
int b = H2;
int c = H3;
int d = H4;
//
// Round 1 - F cycle, 16 times.
//
a = rotateLeft(a + F(b, c, d) + X[0], S11);
d = rotateLeft(d + F(a, b, c) + X[1], S12);
c = rotateLeft(c + F(d, a, b) + X[2], S13);
b = rotateLeft(b + F(c, d, a) + X[3], S14);
a = rotateLeft(a + F(b, c, d) + X[4], S11);
d = rotateLeft(d + F(a, b, c) + X[5], S12);
c = rotateLeft(c + F(d, a, b) + X[6], S13);
b = rotateLeft(b + F(c, d, a) + X[7], S14);
a = rotateLeft(a + F(b, c, d) + X[8], S11);
d = rotateLeft(d + F(a, b, c) + X[9], S12);
c = rotateLeft(c + F(d, a, b) + X[10], S13);
b = rotateLeft(b + F(c, d, a) + X[11], S14);
a = rotateLeft(a + F(b, c, d) + X[12], S11);
d = rotateLeft(d + F(a, b, c) + X[13], S12);
c = rotateLeft(c + F(d, a, b) + X[14], S13);
b = rotateLeft(b + F(c, d, a) + X[15], S14);
//
// Round 2 - G cycle, 16 times.
//
a = rotateLeft(a + G(b, c, d) + X[0] + 0x5a827999, S21);
d = rotateLeft(d + G(a, b, c) + X[4] + 0x5a827999, S22);
c = rotateLeft(c + G(d, a, b) + X[8] + 0x5a827999, S23);
b = rotateLeft(b + G(c, d, a) + X[12] + 0x5a827999, S24);
a = rotateLeft(a + G(b, c, d) + X[1] + 0x5a827999, S21);
d = rotateLeft(d + G(a, b, c) + X[5] + 0x5a827999, S22);
c = rotateLeft(c + G(d, a, b) + X[9] + 0x5a827999, S23);
b = rotateLeft(b + G(c, d, a) + X[13] + 0x5a827999, S24);
a = rotateLeft(a + G(b, c, d) + X[2] + 0x5a827999, S21);
d = rotateLeft(d + G(a, b, c) + X[6] + 0x5a827999, S22);
c = rotateLeft(c + G(d, a, b) + X[10] + 0x5a827999, S23);
b = rotateLeft(b + G(c, d, a) + X[14] + 0x5a827999, S24);
a = rotateLeft(a + G(b, c, d) + X[3] + 0x5a827999, S21);
d = rotateLeft(d + G(a, b, c) + X[7] + 0x5a827999, S22);
c = rotateLeft(c + G(d, a, b) + X[11] + 0x5a827999, S23);
b = rotateLeft(b + G(c, d, a) + X[15] + 0x5a827999, S24);
//
// Round 3 - H cycle, 16 times.
//
a = rotateLeft(a + H(b, c, d) + X[0] + 0x6ed9eba1, S31);
d = rotateLeft(d + H(a, b, c) + X[8] + 0x6ed9eba1, S32);
c = rotateLeft(c + H(d, a, b) + X[4] + 0x6ed9eba1, S33);
b = rotateLeft(b + H(c, d, a) + X[12] + 0x6ed9eba1, S34);
a = rotateLeft(a + H(b, c, d) + X[2] + 0x6ed9eba1, S31);
d = rotateLeft(d + H(a, b, c) + X[10] + 0x6ed9eba1, S32);
c = rotateLeft(c + H(d, a, b) + X[6] + 0x6ed9eba1, S33);
b = rotateLeft(b + H(c, d, a) + X[14] + 0x6ed9eba1, S34);
a = rotateLeft(a + H(b, c, d) + X[1] + 0x6ed9eba1, S31);
d = rotateLeft(d + H(a, b, c) + X[9] + 0x6ed9eba1, S32);
c = rotateLeft(c + H(d, a, b) + X[5] + 0x6ed9eba1, S33);
b = rotateLeft(b + H(c, d, a) + X[13] + 0x6ed9eba1, S34);
a = rotateLeft(a + H(b, c, d) + X[3] + 0x6ed9eba1, S31);
d = rotateLeft(d + H(a, b, c) + X[11] + 0x6ed9eba1, S32);
c = rotateLeft(c + H(d, a, b) + X[7] + 0x6ed9eba1, S33);
b = rotateLeft(b + H(c, d, a) + X[15] + 0x6ed9eba1, S34);
H1 += a;
H2 += b;
H3 += c;
H4 += d;
//
// reset the offset and clean out the word buffer.
//
xOff = 0;
for (int i = 0; i != X.length; i++)
{
X[i] = 0;
}
}
private void processLength(final long bitLength)
{
if (xOff > 14)
{
processBlock();
}
X[14] = (int) (bitLength & 0xffffffff);
X[15] = (int) (bitLength >>> 32);
}
private void processWord(final byte[] in, final int inOff)
{
X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8)
| ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24);
if (xOff == 16)
{
processBlock();
}
}
/*
* rotate int x left n bits.
*/
private int rotateLeft(final int x, final int n)
{
return (x << n) | (x >>> (32 - n));
}
private void unpackWord(final int word, final byte[] out, final int outOff)
{
out[outOff] = (byte) word;
out[outOff + 1] = (byte) (word >>> 8);
out[outOff + 2] = (byte) (word >>> 16);
out[outOff + 3] = (byte) (word >>> 24);
}
}
/**
* Plugin configuration object.
*/
private SambaPasswordPluginCfg config;
// The name of the Samba LanMan password attribute.
private static final String SAMBA_LM_PASSWORD_ATTRIBUTE_NAME =
"sambaLMPassword";
// The name of the Samba NT password attribute.
private static final String SAMBA_NT_PASSWORD_ATTRIBUTE_NAME =
"sambaNTPassword";
// The name of the Samba account object class.
private static final String SAMBA_SAM_ACCOUNT_OC_NAME = "sambaSAMAccount";
// The name of the Samba last password change attribute.
private static final String SAMBA_PWD_LAST_SET_NAME = "sambaPwdLastSet";
/**
* Debug tracer object to log debugging information.
*/
private static final DebugTracer TRACER = DebugLogger.getTracer();
/**
* Password Modify Extended Operation OID.
*/
private static final String PWMOD_EXTOP_OID = "1.3.6.1.4.1.4203.1.11.1";
/**
* Magic string to be used as salt.
*/
private static final String MAGIC_STR = "KGS!@#$%";
// Default timestamp provider implementation.
private static final TimeStampProvider DEFAULT_TIMESTAMP_PROVIDER =
new TimeStampProvider()
{
public long getCurrentTime()
{
return System.currentTimeMillis() / 1000L;
}
};
// Use the default implementation of the timestamp provider... by default.
private TimeStampProvider timeStampProvider = DEFAULT_TIMESTAMP_PROVIDER;
/**
* Add the parity to the 56-bit key converting it to 64-bit key.
*
* @param key56
* 56-bit key.
* @return 64-bit key.
*/
private static byte[] addParity(final byte[] key56)
{
final byte[] key64 = new byte[8];
final int[] key7 = new int[7];
final int[] key8 = new int[8];
for (int i = 0; i < 7; i++)
{
key7[i] = key56[i] & 0xFF;
}
key8[0] = key7[0];
key8[1] = ((key7[0] << 7) & 0xFF | (key7[1] >> 1));
key8[2] = ((key7[1] << 6) & 0xFF | (key7[2] >> 2));
key8[3] = ((key7[2] << 5) & 0xFF | (key7[3] >> 3));
key8[4] = ((key7[3] << 4) & 0xFF | (key7[4] >> 4));
key8[5] = ((key7[4] << 3) & 0xFF | (key7[5] >> 5));
key8[6] = ((key7[5] << 2) & 0xFF | (key7[6] >> 6));
key8[7] = (key7[6] << 1);
for (int i = 0; i < 8; i++)
{
key64[i] = (byte) (setOddParity(key8[i]));
}
return key64;
}
/**
* Create a LanMan hash from a clear-text password.
*
* @param password
* Clear-text password.
* @return Hex string version of the hash based on the clear-text password.
* @throws UnsupportedEncodingException
* if the US-ASCII
coding is not available.
* @throws NoSuchAlgorithmException
* if the algorithm does not exist for the used provider.
* @throws InvalidKeyException
* if the key is inappropriate to initialize the cipher.
* @throws NoSuchPaddingException
* if the padding scheme is not available.
* @throws IllegalBlockSizeException
* if this encryption algorithm is unable to process the input data
* provided
* @throws BadPaddingException
* if this cipher is in decryption mode, and (un)padding has been
* requested, but the decrypted data is not bounded by the
* appropriate padding bytes
*/
private static String lmHash(final String password)
throws UnsupportedEncodingException, NoSuchAlgorithmException,
InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException,
BadPaddingException
{
// Password has to be OEM encoded and in upper case
final byte[] oemPass = password.toUpperCase().getBytes("US-ASCII");
// It shouldn't be longer then 14 bytes
int length = 14;
if (oemPass.length < length)
{
length = oemPass.length;
}
// The password should be divided into two 7-byte keys
final byte[] key1 = new byte[7];
final byte[] key2 = new byte[7];
if (length <= 7)
{
System.arraycopy(oemPass, 0, key1, 0, length);
}
else
{
System.arraycopy(oemPass, 0, key1, 0, 7);
System.arraycopy(oemPass, 7, key2, 0, length - 7);
}
// We create two DES keys using key1 and key2 to on the magic string
final SecretKey lowKey = new SecretKeySpec(addParity(key1), "DES");
final SecretKey highKey = new SecretKeySpec(addParity(key2), "DES");
final Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
des.init(Cipher.ENCRYPT_MODE, lowKey);
final byte[] lowHash = des.doFinal(MAGIC_STR.getBytes());
des.init(Cipher.ENCRYPT_MODE, highKey);
final byte[] highHash = des.doFinal(MAGIC_STR.getBytes());
// We finally merge hashes and return them to the client
final byte[] lmHash = new byte[16];
System.arraycopy(lowHash, 0, lmHash, 0, 8);
System.arraycopy(highHash, 0, lmHash, 8, 8);
return toLowerCase(bytesToHexNoSpace(lmHash));
}
/**
* Creates a NTLM hash from a clear-text password.
*
* @param password
* Clear text password.
* @return Returns a NTLM hash.
* @throws NoSuchProviderException
* if the BouncyCastle provider does not load
* @throws NoSuchAlgorithmException
* if the MD4 algorithm is not found
* @throws UnsupportedEncodingException
* if the encoding UnicodeLittleUnmarked
is not
* supported.
*/
private static String ntHash(final String password)
throws NoSuchProviderException, UnsupportedEncodingException,
NoSuchAlgorithmException
{
final byte[] unicodePassword = password.getBytes("UnicodeLittleUnmarked");
final MessageDigest md4 = new MD4MessageDigest();
return toLowerCase(bytesToHexNoSpace(md4.digest(unicodePassword)));
}
/**
* Set the parity bit for an integer.
*
* @param integer
* to add the parity bit for.
* @return integer with the parity bit set.
*/
private static int setOddParity(final int parity)
{
final boolean hasEvenBits = ((parity >>> 7) ^ (parity >>> 6)
^ (parity >>> 5) ^ (parity >>> 4)
^ (parity >>> 3) ^ (parity >>> 2)
^ (parity >>> 1) & 0x01) == 0;
if (hasEvenBits)
{
return parity | 0x01;
}
else
{
return parity & 0xFE;
}
}
/**
* Default constructor.
*/
public SambaPasswordPlugin()
{
super();
}
/**
* {@inheritDoc}
*/
@Override
public ConfigChangeResult applyConfigurationChange(
final SambaPasswordPluginCfg newConfig)
{
// No validation required and no restart required.
config = newConfig;
return new ConfigChangeResult(ResultCode.SUCCESS, false);
}
/**
* {@inheritDoc}
*/
@Override
public PluginResult.PostOperation doPostOperation(
final PostOperationExtendedOperation extendedOperation)
{
/*
* If the operation is not Password Modify Extended Operation then skip this
* operation.
*/
if (!extendedOperation.getRequestOID().equals(PWMOD_EXTOP_OID))
{
return PluginResult.PostOperation.continueOperationProcessing();
}
/*
* If the operation has not been successful then ignore the operation.
*/
if (extendedOperation.getResultCode() != ResultCode.SUCCESS)
{
return PluginResult.PostOperation.continueOperationProcessing();
}
/*
* Verify if the operation has been initiated by what was defined as Samba
* administrative user. If so, we will skip this operation to avoid double
* synchronization of Samba attributes.
*/
final DN authDN = extendedOperation.getAuthorizationDN();
final DN sambaAdminDN = config.getSambaAdministratorDN();
if (sambaAdminDN != null && !sambaAdminDN.isNullDN())
{
if (authDN.equals(sambaAdminDN))
{
if (DebugLogger.debugEnabled())
{
TRACER.debugInfo("This operation will be skipped because"
+ " it was performed by Samba admin user: " + sambaAdminDN);
}
return PluginResult.PostOperation.continueOperationProcessing();
}
}
// Get the name of the entry and clear passwords from the operation
// attachments.
final DN dn = (DN) extendedOperation
.getAttachment(PasswordModifyExtendedOperation.AUTHZ_DN_ATTACHMENT);
if (dn == null)
{
// The attachment is missing which should never happen.
if (DebugLogger.debugEnabled())
{
TRACER.debugInfo("SambaPasswordPlugin: missing DN attachment");
}
return PluginResult.PostOperation.continueOperationProcessing();
}
final String password = extendedOperation.getAttachment(
PasswordModifyExtendedOperation.CLEAR_PWD_ATTACHMENT).toString();
if (password == null)
{
if (DebugLogger.debugEnabled())
{
TRACER.debugInfo("SambaPasswordPlugin: skipping syncing "
+ "pre-encoded password");
}
return PluginResult.PostOperation.continueOperationProcessing();
}
@SuppressWarnings("unchecked")
final Listtrue
for the correct configuration and
* false
for the incorrect one.
*/
public boolean isConfigurationAcceptable(
final SambaPasswordPluginCfg configuration,
final List