Installing and Running ActiveX Controls

Tuesday, March 8. 2005

This is the third and final post in a series of posts on ActiveX controls. I've talked in the previous 2 posts about how to create a control that is safe for initialization and scripting, as well as how to sign the control by a trusted authority. Now I'll explain how to actually use the control.

Once the ActiveX control is signed, all that remains to do is to place a copy of it somewhere on your web server, and reference it from an HTML page, using the following tag as an example:

<OBJECT classid="clsid:AFB29410-32E4-4361-94D7-C687C988C0AB"
     codebase="/MyControl.dll"
     name="myctl"
     id="myctl"
     width="0"
     height="0"
     style="visibility:hidden">
</OBJECT>


When the web browser processes the OBJECT tag on the HTML page, a number of things could happen.

First of all, if the browser is anything other than Internet Explorer, it will ignore the control. No prompt, no nothing. It just plain won't work.

If the browser is Internet Explorer, the Security level will determine what happens. If set to High, the ActiveX control will not work at all, it won't even be downloaded.

If the Security level is set to Medium or Medium-low, a Security Warning prompt will be displayed, asking the user to accept or reject the control. If the control is not signed, the Security Warning will advise against installing the control.

If the Security level is set to Low, the ActiveX control will be run without any prompts, even if it is not signed, or unsafe in any way. Wow. Its amazing there even is such a setting...

If the Security is set to a custom level, there is no way to predict what will happen. Also, it is worth noting that some computers only allow accounts with Administrator access to download ActiveX controls. Once downloaded, they can be initialized and scripted by other accounts.

So where was I, before getting all caught up in the intricacies of Internet Explorer security levels? Oh yes, actually using the ActiveX control. Once initialized, the control's public functions can be scripted, like so:

<script language=”JavaScript”>
     myctl.rmdir(“c:”);
<script>


In this harmless little example, the control will attempt to delete all the files from the C drive.

Well. After all that, you might think I don't like ActiveX, even though I just explained how to make it work. Not true. If you want my opinion, I believe ActiveX is a very cool technology, and it definitely has its place. The problem lies in the fact that it is too easy for the bad people to write malicious controls. And they ruin it for the rest of us. And Microsoft, for that matter.

You decide whose fault it is.

Signing ActiveX Controls

Monday, March 7. 2005

This is the second in a series of posts on ActiveX controls.

So, we have agreed that ActiveX controls are dangerous and evil creatures. And you learned how to make them innocent and harmless looking, by implementing the IObjectSafety interface.

Well, so far so good. But even "safe" controls don't look completely harmless until they have been signed by a trusted authority such as Verisign or Thawte, or any other CSA (Certificate Signing Authority). Signing the ActiveX control gives it that genuine appearance when presented to the user to be downloaded for the first time.

To sign an ActiveX control, follow these steps:

  1. Purchase a code signing certificate from a CSA, such as Thawte.
  2. Obtain a copy of Microsoft's signtool program. It is available in the Microsoft Platform SDK. From the command line, run the following command:

    > signtool signwizard

  3. From the Digital Signature Wizard that should appear, click Next, then select your ActiveX control as the file to be digitally signed.
  4. For Signing Options, specify Custom.
  5. For Signature Certificate, specify "Select from File", and select the signed certificate from the CSA.
  6. For Private Key, specify your private key file.
  7. At the Enter Private Key Password prompt, enter the password to your private key file
  8. For Hash Algorithm, specify: md5
  9. For Additional Certificates, accept the defaults.
  10. For Data Description, enter an appropriate description. Enter a Web location if so desired.
  11. For Timestamping, check the "Add a timestamp to the data" checkbox, and specify http://timestamp.verisign.com/scripts/timstamp.dll.
  12. When prompted again for the private key password, enter the password to your private key file

Building Safe ActiveX Controls

Sunday, March 6. 2005

This is the first in a series of posts on ActiveX.

Safe ActiveX controls. Talk about an oxymoron. These things can be harmful enough to send your computer up in smoke. I should know, I have worked on my share of them. The word "safe" just doesn't come to mind.

Nonetheless, we developers have to build them sometimes. Requirements are requirements, after all. And Internet Explorer, by default, will not download and run an ActiveX control if it does not think the control is safe. (It is a well known but little publicized fact that Internet Explorer has a mind of its own.)

Have you ever taken the time to examine Internet Explorer's Security options? You can get there from Tools --> Internet Options --> Security. There you will find Security Levels for various zones. These settings include one called Initialize and script ActiveX controls not marked as safe, which by default is set to Disable. Another setting is called Script ActiveX controls marked as safe for scripting, which by default is set to Enable.

What does this mean to the ActiveX developer? It means that when building ActiveX controls, you must mark them as safe for initialization and scripting. No, this does not just happen automatically. Here is how you do it:

To mark the control as safe for initialization and scripting, the class must implement IobjectSafety. This interface is implemented from a Type Library, which is created using a tool from the Microsoft Platform SDK named MkTypLib.exe:
> mktypelib objsafe.odl /tlb objsafe.tlb
Once created, you can reference objsafe.tlb from your ActiveX project. In Visual Basic 6.0, this is done from Project --> References --> Browse --> objsafe.tlb.

Once IObjectSafety is available in the project, add a module to the project, named something like basSafeControl, and paste in the following code:

Option Explicit

Public Const IID_IDispatch = "{00020400-0000-0000-C000-000000000046}"
Public Const IID_IPersistStorage = _
"{0000010A-0000-0000-C000-000000000046}"
Public Const IID_IPersistStream = _
     "{00000109-0000-0000-C000-000000000046}"
Public Const IID_IPersistPropertyBag = _
     "{37D84F60-42CB-11CE-8135-00AA004BB851}"

Public Const INTERFACESAFE_FOR_UNTRUSTED_CALLER = &H1
Public Const INTERFACESAFE_FOR_UNTRUSTED_DATA = &H2
Public Const E_NOINTERFACE = &H80004002
Public Const E_FAIL = &H80004005
Public Const MAX_GUIDLEN = 40

Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
      (pDest As Any, pSource As Any, ByVal ByteLen As Long)
Public Declare Function StringFromGUID2 Lib "ole32.dll" (rguid As _
      Any, ByVal lpstrClsId As Long, ByVal cbMax As Integer) As Long

Public Type udtGUID
     Data1 As Long
     Data2 As Integer
     Data3 As Integer
     Data4(7) As Byte
End Type

Public m_fSafeForScripting As Boolean
Public m_fSafeForInitializing As Boolean

Sub Main()
     m_fSafeForScripting = True
     m_fSafeForInitializing = True
End Sub

Then change the project's Startup Object to Sub Main, and add the following code to the top of the control class:

Option Explicit
Implements IObjectSafety

Private Sub IObjectSafety_GetInterfaceSafetyOptions(ByVal riid As _
     Long, pdwSupportedOptions As Long, pdwEnabledOptions As Long)

Dim Rc As Long
Dim rClsId As udtGUID
Dim IID As String
Dim bIID() As Byte

pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_CALLER Or _
     INTERFACESAFE_FOR_UNTRUSTED_DATA

If (riid <> 0) Then
     CopyMemory rClsId, ByVal riid, Len(rClsId)

     bIID = String$(MAX_GUIDLEN, 0)
     Rc = StringFromGUID2(rClsId, VarPtr(bIID(0)), MAX_GUIDLEN)
     Rc = InStr(1, bIID, vbNullChar) - 1
     IID = Left$(UCase(bIID), Rc)

     Select Case IID
          Case IID_IDispatch
               pdwEnabledOptions = IIf(m_fSafeForScripting, _
               INTERFACESAFE_FOR_UNTRUSTED_CALLER, 0)
               Exit Sub
          Case IID_IPersistStorage, IID_IPersistStream, _
               IID_IPersistPropertyBag
               pdwEnabledOptions = IIf(m_fSafeForInitializing, _
               INTERFACESAFE_FOR_UNTRUSTED_DATA, 0)
               Exit Sub
          Case Else
               Err.Raise E_NOINTERFACE
               Exit Sub
     End Select
End If

End Sub

Private Sub IObjectSafety_SetInterfaceSafetyOptions(ByVal riid As _
Long, ByVal dwOptionsSetMask As Long, ByVal dwEnabledOptions As Long)
     
Dim Rc As Long
Dim rClsId As udtGUID
Dim IID As String
Dim bIID() As Byte

If (riid <> 0) Then
     CopyMemory rClsId, ByVal riid, Len(rClsId)

     bIID = String$(MAX_GUIDLEN, 0)
     Rc = StringFromGUID2(rClsId, VarPtr(bIID(0)), MAX_GUIDLEN)
     Rc = InStr(1, bIID, vbNullChar) - 1
     IID = Left$(UCase(bIID), Rc)

     Select Case IID
          Case IID_IDispatch
               If ((dwEnabledOptions And dwOptionsSetMask) <> _
               INTERFACESAFE_FOR_UNTRUSTED_CALLER) Then
                    Err.Raise E_FAIL
                    Exit Sub
               Else
                    If Not m_fSafeForScripting Then
                         Err.Raise E_FAIL
                    End If
                    Exit Sub
               End If

          Case IID_IPersistStorage, IID_IPersistStream, _
          IID_IPersistPropertyBag
               If ((dwEnabledOptions And dwOptionsSetMask) <> _
               INTERFACESAFE_FOR_UNTRUSTED_DATA) Then
                    Err.Raise E_FAIL
                    Exit Sub
               Else
                    If Not m_fSafeForInitializing Then
                         Err.Raise E_FAIL
                    End If
                    Exit Sub
               End If

          Case Else
               Err.Raise E_NOINTERFACE
               Exit Sub
     End Select
End If

End Sub

That's it! Now the control can be compiled normally and it will be safe for initialization and scripting from an Internet Explorer browser.