diff --git a/core/src/main/java/org/springframework/ldap/support/LdapUtils.java b/core/src/main/java/org/springframework/ldap/support/LdapUtils.java index cbae2118..20ee0174 100644 --- a/core/src/main/java/org/springframework/ldap/support/LdapUtils.java +++ b/core/src/main/java/org/springframework/ldap/support/LdapUtils.java @@ -16,6 +16,7 @@ package org.springframework.ldap.support; +import java.math.BigInteger; import java.util.Collection; import javax.naming.CompositeName; @@ -24,6 +25,7 @@ import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import javax.naming.ldap.LdapContext; +import org.apache.commons.lang.ArrayUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.ldap.NamingException; @@ -310,4 +312,171 @@ public final class LdapUtils { return ""; } } + + /** + * Converts a binary SID to its String representation, according to the + * algorithm described here. Thanks to Eyal + * Lupu for algorithmic inspiration. + * + *
+ * If you have a SID like S-a-b-c-d-e-f-g-... + * + * Then the bytes are + * a (revision) + * N (number of dashes minus two) + * bbbbbb (six bytes of "b" treated as a 48-bit number in big-endian format) + * cccc (four bytes of "c" treated as a 32-bit number in little-endian format) + * dddd (four bytes of "d" treated as a 32-bit number in little-endian format) + * eeee (four bytes of "e" treated as a 32-bit number in little-endian format) + * ffff (four bytes of "f" treated as a 32-bit number in little-endian format) + * etc. + * + * So for example, if your SID is S-1-5-21-2127521184-1604012920-1887927527-72713, then your raw hex SID is + * + * 010500000000000515000000A065CF7E784B9B5FE77C8770091C0100 + * + * This breaks down as follows: + * 01 S-1 + * 05 (seven dashes, seven minus two = 5) + * 000000000005 (5 = 0x000000000005, big-endian) + * 15000000 (21 = 0x00000015, little-endian) + * A065CF7E (2127521184 = 0x7ECF65A0, little-endian) + * 784B9B5F (1604012920 = 0x5F9B4B78, little-endian) + * E77C8770 (1887927527 = 0X70877CE7, little-endian) + * 091C0100 (72713 = 0x00011c09, little-endian) + * + * S-1- version number (SID_REVISION) + * -5- SECURITY_NT_AUTHORITY + * -21- SECURITY_NT_NON_UNIQUE + * -...-...-...- these identify the machine that issued the SID + * 72713 unique user id on the machine + *+ * + * @param sid binary SID in byte array format + * @return String version of the given sid + * @since 1.3.1 + */ + public static String convertBinarySidToString(byte[] sid) { + // Add the 'S' prefix + StringBuffer sidAsString = new StringBuffer("S-"); + + // bytes[0] : in the array is the version (must be 1 but might + // change in the future) + sidAsString.append(sid[0]).append('-'); + + // bytes[2..7] : the Authority + StringBuffer sb = new StringBuffer(); + for (int t = 2; t <= 7; t++) { + String hexString = Integer.toHexString(sid[t] & 0xFF); + sb.append(hexString); + } + sidAsString.append(Long.parseLong(sb.toString(), 16)); + + // bytes[1] : the sub authorities count + int count = sid[1]; + + // bytes[8..end] : the sub authorities (these are Integers - notice + // the endian) + for (int i = 0; i < count; i++) { + int currSubAuthOffset = i * 4; + sb.setLength(0); + sb.append(toHexString((byte) (sid[11 + currSubAuthOffset] & 0xFF))); + sb.append(toHexString((byte) (sid[10 + currSubAuthOffset] & 0xFF))); + sb.append(toHexString((byte) (sid[9 + currSubAuthOffset] & 0xFF))); + sb.append(toHexString((byte) (sid[8 + currSubAuthOffset] & 0xFF))); + + sidAsString.append('-').append(Long.parseLong(sb.toString(), 16)); + } + + // That's it - we have the SID + return sidAsString.toString(); + } + + /** + * Converts a String SID to its binary representation, according to the + * algorithm described here. + * + * @param sid SID in readable format + * @return Binary version of the given sid + * @see LdapUtils#convertBinarySidToString(byte[]) + * @since 1.3.1 + */ + public static byte[] convertStringSidToBinary(String string) { + String[] parts = string.split("-"); + byte sidRevision = (byte) Integer.parseInt(parts[1]); + int subAuthCount = parts.length - 3; + + byte[] sid = new byte[] {sidRevision, (byte) subAuthCount}; + sid = ArrayUtils.addAll(sid, numberToBytes(parts[2], 6, true)); + for (int i = 0; i < subAuthCount; i++) { + sid = ArrayUtils.addAll(sid, numberToBytes(parts[3 + i], 4, false)); + } + return sid; + } + + /** + * Converts the given number to a binary representation of the specified + * length and "endian-ness". + * + * @param number String with number to convert + * @param length How long the resulting binary array should be + * @param bigEndian
true if big endian (5=0005), or
+ * false if little endian (5=5000)
+ * @return byte array containing the binary result in the given order
+ */
+ static byte[] numberToBytes(String number, int length, boolean bigEndian) {
+ BigInteger bi = new BigInteger(number);
+ byte[] bytes = bi.toByteArray();
+ int remaining = length - bytes.length;
+ if (remaining < 0) {
+ bytes = ArrayUtils.subarray(bytes, -remaining, bytes.length);
+ } else {
+ byte[] fill = new byte[remaining];
+ bytes = ArrayUtils.addAll(fill, bytes);
+ }
+ if (!bigEndian) {
+ ArrayUtils.reverse(bytes);
+ }
+ return bytes;
+ }
+
+ /**
+ * Converts a byte into its hexadecimal representation, padding with a
+ * leading zero to get an even number of characters.
+ *
+ * @param b value to convert
+ * @return hex string, possibly padded with a zero
+ */
+ static String toHexString(final byte b) {
+ String hexString = Integer.toHexString(b & 0xFF);
+ if (hexString.length() % 2 != 0) {
+ // Pad with 0
+ hexString = "0" + hexString;
+ }
+ return hexString;
+ }
+
+ /**
+ * Converts a byte array into its hexadecimal representation, padding each
+ * with a leading zero to get an even number of characters.
+ *
+ * @param b values to convert
+ * @return hex string, possibly with elements padded with a zero
+ */
+ static String toHexString(final byte[] b) {
+ StringBuffer sb = new StringBuffer("{");
+ for (int i = 0; i < b.length; i++) {
+ sb.append(toHexString(b[i]));
+ if (i < b.length - 1) {
+ sb.append(",");
+ }
+ }
+ sb.append("}");
+ return sb.toString();
+ }
}
diff --git a/core/src/test/java/org/springframework/ldap/support/LdapUtilsTest.java b/core/src/test/java/org/springframework/ldap/support/LdapUtilsTest.java
index 22794c2a..ad3d3215 100644
--- a/core/src/test/java/org/springframework/ldap/support/LdapUtilsTest.java
+++ b/core/src/test/java/org/springframework/ldap/support/LdapUtilsTest.java
@@ -7,6 +7,7 @@ import javax.naming.directory.BasicAttributes;
import junit.framework.TestCase;
+import org.apache.commons.lang.ArrayUtils;
import org.easymock.MockControl;
import org.springframework.ldap.NoSuchAttributeException;
@@ -89,4 +90,125 @@ public class LdapUtilsTest extends TestCase {
handlerControl.verify();
}
+
+ /**
+ * Example SID from "http://www.pcreview.co.uk/forums/thread-1458615.php".
+ */
+ public void testConvertBinarySidToString() throws Exception {
+ byte[] sid = { (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x05,
+ (byte) 0x15, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0xe9, (byte) 0x67, (byte) 0xbb, (byte) 0x98,
+ (byte) 0xd6, (byte) 0xb7, (byte) 0xd7, (byte) 0xbf,
+ (byte) 0x82, (byte) 0x05, (byte) 0x1e, (byte) 0x6c,
+ (byte) 0x28, (byte) 0x06, (byte) 0x00, (byte) 0x00 };
+ String result = LdapUtils.convertBinarySidToString(sid);
+ assertEquals("S-1-5-21-2562418665-3218585558-1813906818-1576", result);
+ }
+
+ /**
+ * Example SID from "http://blogs.msdn.com/oldnewthing/archive/2004/03/15/89753.aspx".
+ */
+ public void testConvertAnotherBinarySidToString() throws Exception {
+ byte[] sid = { (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x05,
+ (byte) 0x15, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0xa0, (byte) 0x65, (byte) 0xcf, (byte) 0x7e,
+ (byte) 0x78, (byte) 0x4b, (byte) 0x9b, (byte) 0x5f,
+ (byte) 0xe7, (byte) 0x7c, (byte) 0x87, (byte) 0x70,
+ (byte) 0x09, (byte) 0x1c, (byte) 0x01, (byte) 0x00 };
+ String result = LdapUtils.convertBinarySidToString(sid);
+ assertEquals("S-1-5-21-2127521184-1604012920-1887927527-72713", result);
+ }
+
+ /**
+ * Hand-crafted SID.
+ */
+ public void testConvertHandCraftedBinarySidToString() throws Exception {
+ byte[] sid = { (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x05,
+ (byte) 0x15, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
+ String result = LdapUtils.convertBinarySidToString(sid);
+ assertEquals("S-1-5-21-1-2-3-4", result);
+ }
+
+ public void testSmallNumberToBytesBigEndian() throws Exception {
+ byte[] result = LdapUtils.numberToBytes("5", 6, true);
+ assertEquals(6, result.length);
+ assertEquals(0, result[0]);
+ assertEquals(0, result[1]);
+ assertEquals(0, result[2]);
+ assertEquals(0, result[3]);
+ assertEquals(0, result[4]);
+ assertEquals(5, result[5]);
+ }
+
+ public void testLargeNumberToBytesBigEndian() throws Exception {
+ byte[] result = LdapUtils.numberToBytes("1183728", 6, true);
+ assertEquals(6, result.length);
+ assertEquals(0, result[0]);
+ assertEquals(0, result[1]);
+ assertEquals(0, result[2]);
+ assertEquals(18, result[3]);
+ assertEquals(15, result[4]);
+ assertEquals(-16, result[5]);
+ }
+
+ public void testSmallNumberToBytesLittleEndian() throws Exception {
+ byte[] result = LdapUtils.numberToBytes("21", 4, false);
+ assertEquals(4, result.length);
+ assertEquals(21, result[0]);
+ assertEquals(0, result[1]);
+ assertEquals(0, result[2]);
+ assertEquals(0, result[3]);
+ }
+
+ public void testLargeNumberToBytesLittleEndian() throws Exception {
+ byte[] result = LdapUtils.numberToBytes("2127521184", 4, false);
+ assertEquals(4, result.length);
+ assertEquals(-96, result[0]);
+ assertEquals(101, result[1]);
+ assertEquals(-49, result[2]);
+ assertEquals(126, result[3]);
+ }
+
+ /**
+ * Hand-crafted SID.
+ */
+ public void testConvertHandCraftedStringSidToBinary() throws Exception {
+ byte[] expectedSid = { (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x05,
+ (byte) 0x15, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
+ byte[] result = LdapUtils.convertStringSidToBinary("S-1-5-21-1-2-3-4");
+ assertTrue("incorrect length of array", ArrayUtils.isSameLength(expectedSid, result));
+ for (int i = 0; i < result.length; i++) {
+ assertEquals("i=" + i + ",", expectedSid[i], result[i]);
+ }
+ }
+
+ /**
+ * Example SID from "http://www.pcreview.co.uk/forums/thread-1458615.php".
+ */
+ public void testConvertStringSidToBinary() throws Exception {
+ byte[] expectedSid = { (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x05,
+ (byte) 0x15, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0xe9, (byte) 0x67, (byte) 0xbb, (byte) 0x98,
+ (byte) 0xd6, (byte) 0xb7, (byte) 0xd7, (byte) 0xbf,
+ (byte) 0x82, (byte) 0x05, (byte) 0x1e, (byte) 0x6c,
+ (byte) 0x28, (byte) 0x06, (byte) 0x00, (byte) 0x00 };
+ byte[] result = LdapUtils.convertStringSidToBinary("S-1-5-21-2562418665-3218585558-1813906818-1576");
+ assertTrue("incorrect length of array", ArrayUtils.isSameLength(expectedSid, result));
+ for (int i = 0; i < result.length; i++) {
+ assertEquals("i=" + i + ",", expectedSid[i], result[i]);
+ }
+ }
}