Archive for the ‘adLDAP’ category

adLDAP 3.2

June 26th, 2009

Over the past few weeks I’ve been hacking to bits adLDAP to suit my own needs.  Of course I’ve contributed those findings back to the community in the adLDAP library at http://adldap.sourceforge.net

Version 3.2 of adLDAP has a lot more under the skin than would first imply by the version number.  Firstly in version 3.0 I made adLDAP truly PHP 5.x only with protected configuration variables.  However in adLDAP 3.2 I’ve allowed overloading with getters and setters that I’ve previously blogged on.

I’ve also added new computer information functions, I’ve also created a connect and disconnect function.  The intention is to allow changing connection details mid script.  The constructor and destructor normally handle the connections and disconnections but there might be a need to change certain connection properties mid-script and that is the point of these new functions.

I’ve also added a new folder_list() function.  This function will deliver a list of objects, whether they be OU’s, users, groups, etc in a specific OU.  With this function I believe you could effectively deliver a web based Active Directory User’s and Computers application.

See http://adldap.sourceforge.net/wiki/doku.php?id=api for more information.

Getters and Setters in PHP?

May 30th, 2009

This is the question I’ve been pondering this week.

In older versions of PHP other than to keep code neat there was no real need for this functionality, but with PHP 5 you can declare access properties for variables and functions in classes.  In other OO languages, such as C# we’ve been used to it for a long time.  In traditional OOP, if we wanted to get or set an object’s private member, we would need to write a public method to do the job.
The reason behind making a member data private and then accessing it through a public method is to avoid the implementing-user to access it directly.  There can be many reasons for prohibiting direct access to member data to the implementing-code.

PHP traditionally has a weak type system, a further check of the data type may be necessary. PHP 5 and C# .NET have a getter and setter method feature, making it look like we’re accessing the data member directly.

For instance in PHP

class Circle
 {
        private $radius; # integer
        # Constructor
        public function __construct($radius = 15)
         {
                self::__set('radius', $radius);
         }

        # Setter
        public function __set($name, $value)
         {
                switch ($name)
                 {
                        case 'radius':
                          if (!is_numeric($value)) # is_int() for strict type checking
                           throw new Exception('Radius must be of a numeric type');
                          if ($value < 10 || $value > 500)
                           throw new Exception('Radius must be within the range of 10 - 500');
                          $this->radius = $value;
                        break;
                        default:
                          throw new Exception("Attempt to set a non-existing property: $name");
                        break;
                 }
         }

        # Getter
        public function __get($name)
         {
                if (in_array($name, array('radius')))
                 return $this->$name;

                switch ($name)
                 {
                        default:
                          throw new Exception("Attempt to get a non-existing property: $name");
                        break;
                 }
         }

 }

$c = new Circle();
$c->radius = 25;
echo $c->radius;

The problems with this method are two fold. Firstly, because PHP is a weakly typed language you can’t really guarantee that the set and get are in the right format so you have to handle that in the __set and __get functions, and secondly you would be able to set or get any private variable in that class.

Based on the second point, is it therefore worth actually defining a manual setter and getter for each private variable you want to allow access to? For example:

class test {
	private $count;
	public function setCount( $value )
	{
		$this->count = $value;
	}
	public function getCount()
	{
		return $this->count;
	}
}

Because if we use this method we could actually check in the setter if the variable is_int($value) and throw an exception if it isn’t.

My question is, based on the fact that traditionally PHP is a weakly typed language, is it worth

  • using the traditional OO format of getters and setters __get and __set
  • is it worth declaring your own get and set public functions for only specific variables you want to allow
  • or is it worth just making the variable public and setting it directly from outside the class e.g.
class testClass {
    public $count;
}
$myclass = new testClass();
$myclass->count = 1;

The reason I ask is that is that I’m considering how to handle getters and setters in adLDAP. adLDAP is a PHP library that I’m one of two developers maintaining. This is an open source library that provides Microsoft Active Directory integration over LDAP for PHP scripts, such as creating user accounts, Exchange mailboxes, managing groups and authentication, etc.

The library currently has private member variables for things such as the domain controller, a domain admin username and password, ssl settings, etc. The __construct function also allows these to be set through an array passed to it. However I have the need to actually disconnect from my domain controller, change a property and re-connect again. So my reason for this post is, what’s the best way to handle these getters and setters based on my three choices above?

Installing a SSL certificate on your Domain Controller

May 12th, 2009

We’ve recently had the need to install an SSL certificate on our Domain Controllers.  Without SSL you cannot change Active Directory passwords or create user accounts (with a password) over LDAP.

There are two Microsoft Documents that help this process along

  1. A KB article entitled “How to enable LDAP over SSL with a third party certification authority
  2. A TechNet article “Advanced Certificate Enrollment and Management

Certificate Authority

In the first instance, we are installing a standalone CA (certificate authority) and a seperate Windows Server 2008 system.

Generate the Request

On the Domain controller we create a file called request.inf

;----------------- request.inf -----------------
[Version]
Signature="$Windows NT$
[NewRequest]
Subject = "CN=<DC fqdn>" ; replace with the FQDN of the DC
KeySpec = 1
KeyLength = 1024
; Can be 1024, 2048, 4096, 8192, or 16384.
; Larger key sizes are more secure, but have
; a greater impact on performance.
Exportable = TRUE
MachineKeySet = TRUE
SMIME = False
PrivateKeyArchive = FALSE
UserProtected = FALSE
UseExistingKeySet = FALSE
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
ProviderType = 12
RequestType = PKCS10
KeyUsage = 0xa0
[EnhancedKeyUsageExtension]
OID=1.3.6.1.5.5.7.3.1 ; this is for Server Authentication
;-----------------------------------------------

From the command line we then run

certreq -new request.inf request.req

A file called request.req is created

Change the Request Settings

We then copy the request.req file over to the CA.  This is where things get more complicated.

Again from the command line we need to run

certreq -attrib "CertificateTemplate:DomainController" request.req

This will give us a request ID, this is important, make a note of it.

The next stage mentions we need to copy the .asn file from the domain controller to the CA.  Hang on a moment, what .asn file and where is it?

Microsoft gloss over this and it took us around 2 hours to find the answer to this, it’s mentioned in Appendix 2, basically create and run this vbs script and it will create your .inf and your .asn files for you.

Set oArgs = WScript.Arguments
Set oShell = WScript.CreateObject("WScript.Shell")
'
' Parse command line
'
if oArgs.Count < 1 then
    sTemplateName = "DomainController"
    sType = "E"
else
    if ((oArgs(0) = "-?") or (oARgs.Count < 2)) then
        Wscript.Echo "Usage: reqdccert.vbs [Templatename] [Type]"
        Wscript.Echo "[Templatename] is the name of a V2 template"
        Wscript.Echo "[Type]         can be E for Email and A for Authentication certificate"
        Wscript.Echo "If no option is specified, the DomainController certificate template is used."
        Wscript.Quit 1
    else
        sTemplateName = oArgs(0)
                sType = oArgs(1)
    end if
end if
Set oFilesystem = CreateObject("Scripting.FileSystemObject")
Set objSysInfo = CreateObject("ADSystemInfo")
Set objDC = GetObject("LDAP://" & objSysInfo.ComputerName)
sGUID = objDC.GUID
sDNShostname = objDC.DNShostname
sHostname = objDC.cn
'##############################################################################
'
' Create the ASN.1 file
'
'##############################################################################
Dim aASNsubstring(2, 5)
Const HEX_DATA_LENGTH = 1
Const ASCIIDATA = 2
Const HEXDATA = 3
Const HEX_BLOB_LENGTH = 4
Const HEX_TYPE = 5
aASNsubstring(0, ASCIIDATA) = sDNShostname
aASNsubstring(0, HEX_TYPE) = "82"
'
' Convert DNS name into Hexadecimal
'
For i = 1 to Len(aASNsubstring(0, ASCIIDATA))
    aASNsubstring(0, HEXDATA) = aASNsubstring(0, HEXDATA) & _
                                    Hex(Asc(Mid(aASNsubstring(0, ASCIIDATA), i, 1)))
Next
aASNsubstring(0, HEX_DATA_LENGTH) = ComputeASN1 (Len(aASNsubstring(0, HEXDATA)) / 2)
'
' Build the ASN.1 blob for DNS name
'
sASN = aASNsubstring(0, HEX_TYPE) & _
       aASNsubstring(0, HEX_DATA_LENGTH) & _
       aASNsubstring(0, HEXDATA)
'
' Append the GUID as other name
'
if (sType = "E") then
    aASNsubstring(1, HEXDATA) = sGUID
    aASNsubstring(1, HEX_TYPE) = "A0"
    aASNsubstring(1, HEX_DATA_LENGTH) = ComputeASN1 (Len(aASNsubstring(1, HEXDATA)) / 2)
    sASN = sASN & _
           "A01F06092B0601040182371901" & _
           aASNsubstring(1, HEX_TYPE) & _
           "120410" & _
           aASNsubstring(1, HEXDATA)
end if
'
' Write the ASN.1 blob into a file
'
Set oFile = oFilesystem.CreateTextFile(sHostname & ".asn")
'
' Put sequence, total length and ASN1 blob into the file
'
oFile.WriteLine "30" & ComputeASN1 (Len(sASN) / 2) & sASN
oFile.Close
'
' Use certutil to convert the hexadecimal string into bin
'
oShell.Run "certutil -f -decodehex " & sHostname & ".asn " & _
                                       sHostname & ".bin", 0, True
'
' Use certutil to convert the bin into base64
'
oShell.Run "certutil -f -encode " & sHostname & ".bin " & _
                                    sHostname & ".b64", 0, True
'##############################################################################
'
' Create the INF file
'
'##############################################################################
Set iFile = oFilesystem.OpenTextFile(sHostname & ".b64")
Set oFile = oFilesystem.CreateTextFile(sHostname & ".inf")
oFile.WriteLine "[Version]"
oFile.WriteLine "Signature= " & Chr(34) & "$Windows NT$" & Chr(34)
oFile.WriteLine ""
oFile.WriteLine "[NewRequest]"
oFile.WriteLine "KeySpec = 1"
oFile.WriteLine "KeyLength = 1024"
oFile.WriteLine "Exportable = TRUE"
oFile.WriteLine "MachineKeySet = TRUE"
oFile.WriteLine "SMIME = FALSE"
oFile.WriteLine "PrivateKeyArchive = FALSE"
oFile.WriteLine "UserProtected = FALSE"
oFile.WriteLine "UseExistingKeySet = FALSE"
oFile.WriteLine "ProviderName = " & Chr(34) & _
                "Microsoft RSA SChannel Cryptographic Provider" & Chr(34)
oFile.WriteLine "ProviderType = 12"
oFile.WriteLine "RequestType = PKCS10"
oFile.WriteLine "KeyUsage = 0xa0"
oFile.WriteLine ""
oFile.WriteLine "[EnhancedKeyUsageExtension]"
oFile.WriteLine "OID=1.3.6.1.5.5.7.3.1"
oFile.WriteLine "OID=1.3.6.1.5.5.7.3.2"
oFile.WriteLine ";"
oFile.WriteLine "; The subject alternative name (SAN) can be included in the INF-file"
oFile.WriteLine "; for a Windows 2003 CA."
oFile.WriteLine "; You don't have to specify the SAN when submitting the request."
oFile.WriteLine ";"
oFile.WriteLine "[Extensions]"
iLine = 0
Do While iFile.AtEndOfStream <> True
    sLine = iFile.Readline
    If sLine = "-----END CERTIFICATE-----" then
        Exit Do
    end if
    if sLine <> "-----BEGIN CERTIFICATE-----" then
        if iLine = 0 then
            oFile.WriteLine "2.5.29.17=" & sLine
        else
            oFile.WriteLine "_continue_=" & sLine
        end if
        iLine = iLine + 1
    end if
Loop
oFile.WriteLine "Critical=2.5.29.17"
oFile.WriteLine ";"
oFile.WriteLine "; The template name can be included in the INF-file for any CA."
oFile.WriteLine "; You don't have to specify the template when submitting the request."
oFile.WriteLine ";"
oFile.WriteLine ";[RequestAttributes]"
oFile.WriteLine ";CertificateTemplate=" & sTemplateName
oFile.Close
iFile.Close
'##############################################################################
'
' Create the certreq.exe command-line to submit the certificate request
'
'##############################################################################
Set oFile = oFilesystem.CreateTextFile(sHostname & "-req.bat")
oFile.WriteLine "CERTREQ -attrib " _
                 & Chr(34) & "CertificateTemplate:" & sTemplateName _
                 & Chr(34) & " " & sHostname & ".req"
'
' The GUID structure needs to be reconstructed. The GUID is read
' as a string like f4aaa8576e6828418712b6ca89fbf5bc however the
' format that is required for the certreq command looks like
' 57a8aaf4-686e-4128-8712-b6ca89fbf5bc. The bytes are reordered
' in the following way:
'
'                            11111111112222222222333
'             Position 12345678901234567890123456789012
'                      |------|--|--|--|--------------|
' Original GUID:       f4aaa8576e6828418712b6ca89fbf5bc
'
'                            11 1 1111 1112 222222222333
'             Position 78563412 1290 5634 7890 123456789012
'                      |------- |--- |--- |--- |----------|
' Reformatted GUID:    57a8aaf4-686e-4128-8712-b6ca89fbf5bc
'
oFile.WriteLine "REM "
oFile.WriteLine "REM !!! Only valid for Windows 2003 or later versions !!!"
oFile.WriteLine "REM If you do not specify certificate extensions in the *.INF file"
oFile.WriteLine "REM they can be specified here like the following example"
oFile.WriteLine "REM "
oFile.WriteLine "REM CERTREQ -submit -attrib " _
                 & Chr(34) & "CertificateTemplate:" & sTemplateName _
                 & "\n" _
                 & "SAN:guid=" _
                 & Mid(sGUID, 7, 2) _
                 & Mid(sGUID, 5, 2) _
                 & Mid(sGUID, 3, 2) _
                 & Mid(sGUID, 1, 2) & "-" _
                 & Mid(sGUID, 11, 2) _
                 & Mid(sGUID, 9, 2) & "-" _
                 & Mid(sGUID, 15, 2) _
                 & Mid(sGUID, 13, 2) & "-" _
                 & Mid(sGUID, 17, 4) & "-" _
                 & Mid(sGUID, 21, 12) _
                 & "&DNS=" & sDNShostname & Chr(34) & " " & sHostname & ".req"
oFile.Close
'##############################################################################
'
' Create the certificate verification script
'
'##############################################################################
Set oFile = oFilesystem.CreateTextFile(sHostname & "-vfy.bat")
oFile.WriteLine "certutil -viewstore " & Chr(34) & objDC.distinguishedname & _
                "?usercertificate" & chr(34)
oFile.Close
'##############################################################################
'
' Compute the ASN1 string
'
'##############################################################################
Function ComputeASN1 (iStrLen)
    If Len(Hex(iStrLen)) Mod 2 = 0 then
        sLength = Hex(iStrLen)
    else
        sLength = "0" & Hex(iStrLen)
    end if
    if iStrLen > 127 then
        ComputeASN1 = Hex (128 + (Len(sLength) / 2)) & sLength
    else
        ComputeASN1 = sLength
    End If
End Function

So now we have generated a .req and a .asn file we can copy the .asn to the CA.  We actually used the .inf file created from this VBS script, but the contents look similar enough.

certutil -setextension <RequestID> 2.5.29.17 1 @<dcname>.asn

What Microsoft also doesn’t tell you here, is that if you are using Windows Server 2008, this command will not work unless you have loaded the command prompt with elevated priviledges (UAC). The subject alternative name, which is identified by the object identifier 2.5.29.17, is set with the attributes that are defined in the <dcname>.asn file. The fourth parameter that is set to “1” marks the extension as critical.

The TechNet ‘Processing Domain Controller Certificates‘ article mentions how to validate the certificate request is good, however we are assuming these tests come back as positive so next we need to issue and retrieve the certificate.

Retrieve the Certificate

From the CA now run

certutil –resubmit <RequestID>

and then finally

CERTREQ -retrieve <RequestID> <dcname>.cer <dcname>.p7b

You will now have a .cer and a .p7b file which you can copy back to the domain controller.

Install the Certificate

To install the certificate, from the command line on the domain controller

CERTREQ -ACCEPT <dcname>.p7b

Microsoft tells you to restart the Domain Controller.  In our experience this was not necessary and SSL over LDAP was available immediately

Test LDAP over SSL

After a certificate is installed, follow these steps to verify that LDAPS is enabled:

  1. Start the Active Directory Administration Tool (Ldp.exe).
  2. On the Connection menu, click Connect.
  3. Type the name of the domain controller to which you want to connect.
  4. Type 636 as the port number.
  5. Click OK

Update:  I’ve just updated the adLDAP Wiki documentation to reflect this.

8 bit characters in Active Directory

May 6th, 2009

Once again, after discovering very little information from Microsoft about Active Directory over LDAP I was posed with a question on the adLDAP forums https://sourceforge.net/forum/?group_id=104193

And that was how to handle accented characters over LDAP. Running a standard ldap_modify() will cause a ‘Constraint violation’ error from the domain controller.

I finally discovered, after reading the RFC for LDAP that I need to encode the ‘offending’ characters as UTF-8.

I could have simply executed

$adldap->user_modify('AD.UserName', array('firstname'=>utf8_encode('Göran'));

but that would be too easy wouldn’t it. Seeing as my believe with libraries should be, you shouldn’t have to think about this, let us allow the library to do this work for us.

Whenever you modify or create attributes in adLDAP it passes the attributes array through a schema function so let’s process the array detecting 8 bit characters

array_walk($attributes, array($this, 'encode8bit'));

Then let us create the function encode8bit

protected function encode8bit(&$item, $key) {
        $encode = false;
        if (is_string($item) === true) {
            for ($i = 0; $i < strlen($item); $i++)
            {
                // Detect the ordinal value of the character
                if (ord($item[$i]) >> 7) {
                    $encode = true;
                }

            }
        }
        if ($encode === true) {
            $item = utf8_encode($item);
        }
    }   

Update Wordpress was messing with the rendering of this function, I’ve now corrected it.

I’ve committed the code to the repository trunk and it’s now available to download directly from SVN

adLDAP version 3.0

May 1st, 2009

adldapWell since joining the adLDAP project on SourceForge, I’ve just published version 3.0.

Version 3.0 is a big change from 2.0, firstly I’ve removed support for PHP 4, by using proper PHP 5 constructors and making variables / functions public and protected.

I’ve added a number of new functions for Exchange mailbox creation, contact management and user deletion (as well as PHP Doc style commenting the code).

I’ve also gone and re-organised much of the documentation on the Wiki.

Go check it out for yourself http://adldap.sourceforge.net

Active Directory Management over PHP

April 22nd, 2009

Update: I’m now part of the original adLDAP project, so libAD has been withdrawn, look forward to seeing my contributions and Exchange supposed on adLDAP in the near future.

A while ago I started using a library called adLDAP, unfortunately there have been a number of bugs and lack of some features with it.  Couple this with the lack of updates for the past two years, I decided to re-work this library and release it myself.  This library is called libAD.

libAD is a PHP library providing Active Directory authentication and management over LDAP.

It provides intelligent Active Directory integration with PHP. This extends on the original project that has not been updated for some time. It’s aim is to help other developers with getting over the same hurdles that we’ve experienced in getting the whole LDAP SSL Active Directory puzzle working natively on Linux.

This library is not designed to be a complete Active Directory management systems, but give you a set of functions through an API that will allow you to interface successfully with your Active Directory.

Given the varied nature of organisations and sites, adLDAP may not be your complete solution, but it should be a very sound starting point. LDAP isn’t overly friendly on first glance, and it’s a steep learning curve made alot worse when coupled with Microsoft’s seemingly unending army of catches.

The information you can retrieve from Active Directory is as useful as you make it. If you don’t fill out all their account information there’s not really going to be much to query.

libAD is open source software and is released under the GNU General Public License v2. This is a change from the license used under adLDAP which was LGPL.

Update: I’m now part of the original adLDAP project, so libAD has been withdrawn, look forward to seeing my contributions and Exchange supposed on adLDAP in the near future.