互连网的广泛应用使得基于网络的软件成为现今软件应用的主流,软件计算环境正在由主机计算、PC计算向网络计算过渡,真正的网络计算时代已经来临。为方便企业软件的发行,众多企业在建立网站的同时,将企业的功能软件集成其中,给用户以最大程度的快捷和便利来应用本企业的软件,这样的应用使得现今软件已成为一种基于网络的服务,也使得软件的人机界面更为互动。然而如何实现和推广这样的应用就成为亟待解决的问题。微软文档在这些方面有非常全面的细节描述,但由于比较分散,文字表述比较抽象,会花费软件开发人员比较多的时间进行理解,故本文系统的介绍这方面的相关知识和具体实现,以方便其他人的软件开发和研究工作。
一、介绍基于Internet发布的软件
1.1 基于Internet的软件发布模式:
根据客户端和服务器端应用模式的不同,基于网络的软件可以分为C/S和B/S结构。
对于C/S结构,发布者在服务器运行服务器端软件,并保持其状态,而使用者需安装软件发布商所提供的专用客户端,并按照发布商的说明完成客户机的设置工作。安装和运行过程有所不便。
对于B/S结构,软件的发布可以有两种方式:
(1)在服务器端发布,发布者制作组件并对组件注册,组件提供COM接口,供程序调用,如ASP就可以在网页中对组件进行调用,使客户端浏览者通过服务器完成计算或其他功能。
(2)在客户端发布,通过访问企业网站,浏览者的浏览器自动下载已将ActiveX控件和需要的功能链接库打包的数据文件,检查并登记软件发行企业的数字签名,然后解开该压缩包,根据包内的配置文件完成功能部件和ActiveX控件的注册,最后,检查控件内脚本的安全性,自动完成客户端软件的安装和设置,并在网页的相应位置显示ActiveX控件(企业软件界面)供用户操作。
以上B/S结构软件的两种发行方式可以混合使用,完成网络交互的复杂功能。
1.2 数字签名介绍:
用户同意下载并注册软件发布者提供的工具包,首先必须确保该包中控件的源代码是安全的,这就需要软件发布商以自己的身份标记该工具包。这个标记过程就是软件发布商对所发布软件的数字签名。
1.3 软件出版商证书介绍:
在签署文件之前,需要有一个软件出版商证明书。软件发布者必须向证书发放机构申请自己的证书。在申请过程中,必须生成一个密匙对,并向证书发放机构提供证明信息,如名字、地址以及公共密匙。证书发放机构在收到申请后生成一个符合工业标准X.509证书格式的软件出版商证书 。该证书确定并且包含公共密匙,以证书发放机构存档作为参考,并把一个拷贝以电子邮件的方式返回。收到该证书之后,软件发布者应在所有要发布的、用私人密匙签署的软件当中,包含一份该证书的拷贝。
为方便软件的测试,也可以通过相关工具创建一个用于测试目的的测试证书,但是该证书不能用于发售代码。
1.4 ActiveX控件的脚本安全介绍:
代码签字可以向用户保证代码的可信度。但是运行一个ActiveX控件将带来新的安全性问题--控件被申明是可靠的,但如果使用不可信脚本代码访问就不能保证其安全性。
为在网络上获得安全应用,表明所开发的控件有安全性的保障,ActiveX初始化和脚本操作都需要一个确定的安全性机制保证其安全性不被违背。
IE具有三个安全性层次,当用户调用一个包含非安全性初始化和脚本操作的控件的网页时,IE分别给出下列基于当前安全级别的警告信息:
低级:没有警告
中级:提示用户潜在的风险
高级:提示用户潜在的风险并且禁止控件运行
大多数控件开发者都是将IE的安全性级别设置为最低以便于开发调试。但是控件被其他浏览者应用时,安全级别却设定为默认的高级。因此开发者必须在控件代码编写时提供安全性保障代码。
有两种方法提供控件的脚本操作安全性保证。第一种是使用组件分组管理器在组件导入以后在注册表上面创建正确的入口,IE在脚本操作之前检查注册表确认安全性;第二种是实现一个 IObjectSafety 接口到控件,如果IE发现控件支持 IObjectSafety,就在导入控件之前调用 IObjectSafety::SetInterfaceSafetyOptions 方法来确保安全性脚本操作。
二、基于Internet发布软件的具体实现
2.1 制作测试软件出版商证书:
使用Visual C++ 5.0 光盘中CAB&SIGN目录下的 MAKECERT和 CERT2SPC 公用程序。举例来说,制作分两步:
(1)做一个私人密匙文件MYKEY.PVK 和一个公司证书CERT.CER,运行公用程序MAKECERT,命令为:
MAKECERT -u:MyKey -n:CN=Company -k:MYKEY.PVK CERT.CER
其中MyKey是密匙名,Company是公司名。注意:公用程序 MAKECERT 在命令行选项中区分大小写,因此必须使用小写的-u、-n以及-k,对于-n选项的值必须是大写的CN。
(2)完成名为CERT.SPC测试软件出版商证书的制作,运行CERT2SPC,命令如下:
CERT2SPC ROOT.CER CERT.CER CERT.SPC
其中CERT.SPC文件是利用第一步创建的CERT.CER文件以及CAB&SIGN目录下提供的ROOT.CER文件创建的。
2.2 打包待发布的ActiveX控件和相关链接库:
使用CAB&SIGN目录下的CABARC.EXE,举例来说,过程分两步:
(1) 创建一个INF文件:
INF文件是一个文本文件,指定运行控件所需要下载和注册的组件(比如DLL或其它OCX)。一个INF文件描述了CAB压缩包中所有必须的文件。在缺省情况下,若客户端系统中有压缩包中的文件,则版本号相同的文件不被下载。一个INF文件(texture.inf)的示例为:
[version] signature="$CHICAGO$" AdvancedINF=2.0 [Add.Code] Cards.ocx=Cards.ocx mfc42.dll=mfc42.dll opengl32.dll=opengl32.dll glu32.dll=glu32.dll [Cards.ocx] file-win32-x86=thiscab clsid= FileVersion=1,0,0,1 RegisterServer=yes [mfc42.dll] file-win32-x86=thiscab RegisterServer=yes [opengl32.dll] FileVersion=4,0,0,0 hook=openglinstaller [glu32.dll] FileVersion=4,0,0,0 hook=openglinstaller [openglinstaller] file-win32-x86=OpenGL95.cab run=%EXTRACT_DIR%OpenGL95.exe |
其中条目[version]固定,条目[Add.Code]将需要下载的动态链接库和ActiveX 组件填写其中作为下载清单,其他以[Add.Code]中的文件名为条目名的表项是对这些文件的来源、版本、下载路径和是否需客户端系统注册的具体说明--关键字"file-win32-x86"指定平台是x86,其值"thiscab"也是一个关键字,意指包含该INF的CAB文件,也可以是其他同时被下载的cab文件名或者是一个网址;"clsid"是指待注册控件的CLSID;"RegisterServer=yes"表示对应文件需要系统注册;"hook"表示该文件位置要去hook语句所指示的条目里寻找;
(2) 运行公用程序CABARC.EXE,对应于以上INF文件有如下命令行:
CABARC -s 6144 n texturemap.cab cards.ocx mfc42.dll texture.inf |
执行后创建了一个名为texturemap.cab的CAB文件。其中,CAB包中的文件需要在命令行列出,次序同它们在INF文件中的完全一致,-s选项为代码签署保留空间,n 命令指定创建的是CAB文件。
2.3 对打包得到的文件进行数字签名:
使用CAB&SIGN目录下的SIGNCODE.EXE,步骤如下:
(1) 运行该程序,在需填写属性的第一页,选择打包好的文件(也可以直接是ActiveX控件所对应的文件,如OCX/DLL),填写发布软件名和发布商的网址(方便用户根据这些资料直观的判断是否运行该控件);
(2) 第二页是证书发放机构的列表,选择软件发布商所获得证书的所属机构;
(3) 第三个用户填写页,选定软件开发商证书(SPC)文件名,另外,因为需在该证书的一个文件中查找密码、密匙,所以还需选定私人密匙(PVK)文件名;
(4) 确认所填写的信息后,SIGNCODE就对文件进行签署。
2.4 使用组件分组管理器确保控件初始化和脚本操作安全性:
如果一个控件使用组件分组管理器将自己注册为安全,则该控件的注册表入口就包含一个分组关键字,该关键字含一个或者两个子键,其中一个设置控件支持安全性初始化,另一个设置支持安全性脚本操作。安全性初始化子键对应CATID_SafeForInitializing,安全性脚本操作子键对应CATID_SafeForScripting(组件分组子键定义在 Comcat.h 文件,而安全性初始化和脚本操作子键定义在 Objsafe.h 文件)。
要创建一个组件分组的子键,控件必须实现以下步骤:
(1)创建一个组件分组管理器实例来接收 ICatRegister 接口的地址;
(2)设置CATEGORYINFO结构变量;
(3)调用ICatRegister::RegisterCategories方法,将以上设定的CATEGORYINFO结构变量作为参数传递。
在控件编写时就需添加如下的全局函数:
HRESULT CreateComponentCategory(CATID catid, WCHAR *catDescription) { ICatRegister* pcr = NULL; HRESULT hr = S_OK; //创建一个组件管理器实例(进程内) hr=CoCreateInstance(CLSID_StdComponentCategoriesMgr,NULL,CLSCTX_INPROC_SERVER,IID_ICatRegister,(void**)&pcr); if (FAILED(hr)) return hr; //确信HKCRComponent Categories键已经被注册 CATEGORYINFO catinfo; catinfo.catid = catid; catinfo.lcid = 0x0409;//英语 //确信提供的描述在127个字符以内 int len = wcslen(catDescription); if (len>127) len = 127; wcsncpy(catinfo.szDescription,catDescription,len); //确信描述使用''结束 catinfo.szDescription[len] = ''; hr = pcr->RegisterCategories(1,&catinfo); pcr->Release(); return hr; } |
当一个子键被创建到需要的分组,控件应该按以下步骤注册到该分组:
(1)创建一个组件分组管理器实例接收ICatRegister接口地址;
(2)调用ICatRegister::RegisterClassImplCategories方法,将控件的CLSID和需要的category ID 作为参数传递。
对应的全局函数为:
HRESULT RegisterCLSIDInCategory(REFCLSID clsid, CATID catid) { //注册组件分组信息 ICatRegister* pcr = NULL; HRESULT hr = S_OK; hr=CoCreateInstance(CLSID_StdComponentCategoriesMgr,NULL,CLSCTX_INPROC_SERVER,IID_ICatRegister,(void**)&pcr); if(SUCCEEDED(hr)) { //注册已实现的类到分组 CATID rgcatid[1]; rgcatid[0] = catid; hr = pcr->RegisterClassImplCategories(clsid,1,rgcatid); } if(pcr != NULL) pcr->Release(); return hr; }
ActiveX的注册是在函数DLLRegisterServer中进行的,在组件中 DLLRegisterServer 函数调用了CreateComponentCategory和RegisterCLSIDInCategory函数来保证控件的安全性初始化和脚本操作。所以DLLRegisterServer应添加如下代码:
//注册控件是安全性初始化的 hr=CreateComponentCategory(CATID_SafeForInitializing,L"Controls safely initializable from persistent data!"); if(FAILED(hr))return hr; CLSID m_clsid;//本控件的的clsid CLSIDFromString(L"",&m_clsid); hr=RegisterCLSIDInCategory(m_clsid,CATID_SafeForInitializing); if(FAILED(hr))return hr; //注册控件是安全性脚本操作的 hr=CreateComponentCategory(CATID_SafeForScripting,L"Controls safely scriptable!"); if(FAILED(hr))return hr; hr=RegisterCLSIDInCategory(m_clsid,CATID_SafeForScripting); if(FAILED(hr))return hr; |
注意,一个创建安全性分组入口到注册表的控件,也应该负责卸载所有分组信息。要卸载一个已经安全性初始化和脚本操作的控件,应该按以下步骤:
(1)创建一个组件分类管理器实例接收ICatRegister接口地址;
(2)调用ICatRegister::UnRegisterClassImplCategories方法,将控件的CLSID和必要的category ID作为参数传递。
对应全局函数如下:
HRESULT UnRegisterCLSIDInCategory(REFCLSID clsid,CATID catid) { ICatRegister* pcr=NULL; HRESULT hr=S_OK; hr=CoCreateInstance(CLSID_StdComponentCategoriesMgr,NULL,CLSCTX_INPROC_SERVER,IID_ICatRegister,(void**)&pcr); if(SUCCEEDED(hr)){// 从分组卸载组件 CATID rgcatid[1]; rgcatid[0]=catid; hr=pcr->UnRegisterClassImplCategories(clsid,1,rgcatid); } if(pcr!=NULL)pcr->Release(); return hr; } |
控件卸载时调用的是DLLUnRegisterServer函数,故需在该函数中添加如下代码:
CLSID m_clsid; //本控件的clsid CLSIDFromString(L"",&m_clsid); hr=UnRegisterCLSIDInCategory(m_clsid,CATID_SafeForInitializing); if(FAILED(hr))return hr; hr=UnRegisterCLSIDInCategory(m_clsid,CATID_SafeForScripting); if(FAILED(hr))return hr; |
2.5 在网页中嵌入ActiveX文件包实现基于Internet软件发布:
给出一个嵌入实例:
<object style="LEFT:0px;TOP:0px" codebase="texturemap.cab#version=1,0,0,1" width="640" height="480" align="baseline" border="0" classid="clsid:0F968806-D214-11D5-9022-5254AB123A61" name="texture" VIEWASTEXT> <param name="_Version" value="65536"> <param name="_ExtentX" value="2646"> <param name="_ExtentY" value="1323"> <param name="_StockProps" value="2"> </object> |
其中name属性是控件对应的对象名,实现在网页中对控件定义的方法进行调用、属性进行设置;codebase属性指示的是控件包的下载位置,除了cab文件外,也可以直接指向一个OCX 文件或者DLL文件,但要发布软件,这些文件都必须事先签署过;codebase中的version表示控件的版本号,当控件在客户端不存在或现有控件的版本比codebase属性中指定的版本旧时,浏览器将下载并注册指定的文件;classid属性指定的是控件的CLSID,客户端系统将通过此ID在注册表中寻找该控件是否已被注册,并确定控件的版本以决定是否重新下载注册。Width,height属性定义控件在浏览器中的客户视窗大小。
在将ActiveX嵌入后就需要实现调用控件中设定的方法和属性。可以通过VBScript脚本进行调用,如:
<script language="VBscript"> Sub window_onload() texture.Radius=5 end sub </script> <input type=button onclick='call texture.servertexturemapping("218.108.175.187","Apple.bmp")'> |
其中servertexturemapping是控件中定义的方法,Radius是控件中定义的属性。
三、总结
基于Internet发布软件降低了对系统软件的要求,避免了用户或者专业人员对客户端的安装和设置,实现软件安装访问自动化、自由化。而安全证书机制的引入,又确保了软件的安全性,使得基于Internet发布软件实现高效、快捷、实用。本文的目的就是系统的介绍基于Internet发布软件的各类相关概念和实现流程。