1. IceSSL简介
Ice版本:Ice-3.1.1
操作系统:Windows XP SP2
JDK版本:JDK 1.6
安全性对于许多分布式应用程序来说是一个重要的考虑因素,无论是在企业内部网还是在不可信的网络,如Internet。保护敏感的信息,确保其完整性,并验证通信双方的身份的能力,这些能力对于开发安全的应用程序来说是必不可少的。考虑到这些目标,Ice提供了这些功能的IceSSL插件,IceSSL插件使用SSL协议。
2. 配置IceSSL
将IceSSL合并到你的应用程序中,需要安装IceSSL插件,根据你的安全需求进行配置,并创建SSL端点。
安装IceSSL插件通过在配置文件中添加Ice.Plugin属性,不需要修改应用程序源代码。
2.1. C++应用程序
C++ IceSSL插件的可执行代码驻留在Unix的共享库上和Windows上的一个动态链接库(DLL)中。如下所示属性Ice.Plugin:
Ice.Plugin.IceSSL=IceSSL:createIceSSL
该属性值IceSSL:createIceSSL允许Unix和Windows的Ice运行时寻找到IceSSL库(在Unix 和Windows上),并且初始化插件。该库必须出现在共享库路径(大多数Unix 平台的LD_LIBRARY_PATH,在Windows平台上的path环境变量)指定的目录中。
2.2. Java应用程序
Ice.Plugin属性配置如下:
Ice.Plugin.IceSSL=IceSSL.PluginFactory
IceSSL.PluginFactory 是一个class类名,允许ice运行时初始化IceSSL插件,这些类包含在Ice.jar 包中。
2.3. 创建SSL端点
安装了IceSSL 插件之后,就可以在端点中使用一种新协议ssl了。例如,下面的端点列表创建了一个TCP 端点、一个SSL 端点,以及一个UDP 端点:
MyAdapter.Endpoints=tcp -p 8000:ssl -p 8001:udp -p 8000
如这个例子所演示的,UDP端点可以使用和TCP或SSL端点相同的端口号,因为UDP是一种不同的协议,有自己的端口集。但TCP端点和SSL端点不能使用同一个端口号,因为SSL 在本质上是位于TCP 之上的一个层面。在串化代理中使用SSL 同样直截了当:
MyProxy=MyObject:tcp -p 8000:ssl -p 8001:udp -p 8000
2.4. 安全考虑事项
像上面的例子那样,对象适配器的端点使用多种协议,对安全有一些明显的影响。如果意图是用SSL来保护会话通信,或者是限制对服务器的访问,那么应该只定义SSL端点。
但在有些情况下,使用不安全的端点协议也有好处。下图阐释了一种环境,在防火墙以内可以使用TCP,但外部客户必须使用SSL。
图中的防火墙被配置成阻塞外部对TCP端口8000的访问,并把与端口8001 的连接转到服务器的机器。
在防火墙以内使用TCP的一个原因是,它比SSL效率更高,使用时所需的管理工作更少。当然,这里的例子假定内部客户是可信的,在许多环境中事情未必如此。
2.5. 配置IceSSL
配置环境
IceGrid节点 | IP地址 |
注册器(registry) | 192.168.1.10 |
节点1(Node1) | 192.168.1.20 |
节点2(Node2) | 192.168.1.30 |
客户端(client) | 192.168.1.40 |
注册器配置:
# IceSSL properties
Ice.Plugin.IceSSL=IceSSL:createIceSSL
IceSSL.DefaultDir=C:\IceGrid
IceSSL.CertFile=registry_cert.pem
IceSSL.KeyFile=registry_key.pem
IceSSL.CertAuthFile=ca_cert.pem
IceSSL.Password=123456
# Registry properties
IceGrid.Registry.Client.Endpoints=ssl -p 4065:tcp -p 4066
IceGrid.Registry.Server.Endpoints=ssl
IceGrid.Registry.Internal.Endpoints=ssl
IceGrid.Registry.AdminPermissionsVerifier=IceGrid/NullPermissionsVerifier
IceGrid.Registry.PermissionsVerifier=IceGrid/NullPermissionsVerifier
IceGrid.Registry.Data=C:\IceGrid\registry
IceGrid.Registry.Admin.Endpoints=default
#Log properties
IceGrid.Registry.Trace.Adapter=1
IceGrid.Registry.Trace.Node=2
启动注册器我们使用的是icegridregistry.exe可执行程序,所以配置IceSSL插件使用C++配置IceSSL:createIceSSL。IceSSL.Password属性配置了明文密码,这不是一个安全的做法。更好的设置密码的方式,参考官方文档。
配置说明:
属性 | 说明 |
Ice.Plugin.IceSSL | 开启IceSSL插件服务的主类 |
IceSSL.DefaultDir | 数字证书存放的目录 |
IceSSL.CertFile | 注册器的数字证书文件,pem格式 |
IceSSL.KeyFile | 注册器的数字证书对应的私钥文件,pem格式 |
IceSSL.CertAuthFile | 给注册器的数字证书签名的根证书,pem格式 |
IceSSL.Password | 读取私钥时使用的密码 |
其他的属性不属于IceSSL插件特有的配置,在此不再次说明。
IceGrid.Registry.Client.Endpoints属性提供了两种协议,添加了ssl协议,保留了tcp协议,保留的tcp协议提供给客户端连接注册器使用,客户端连接注册器只是为了获取一个可用的代理,不需要使用加密通信,这样也可以降低对性能的影响。
管理客户端配置:
Ice.Plugin.IceSSL=IceSSL:createIceSSL
IceSSL.DefaultDir=C:\IceGrid
IceSSL.CertFile=admin_cert.pem
IceSSL.KeyFile=admin_key.pem
IceSSL.CertAuthFile=ca_cert.pem
IceSSL.Password=123456
Ice.Default.Locator=IceGrid/Locator:ssl -h 192.168.1.10 -p 4065
管理客户端发布我们的应用程序,启动管理客户端我们使用的是icegridadmin.exe可执行程序,所以配置IceSSL插件使用C++配置IceSSL:createIceSSL。
部署文件:
<icegrid>
<application name="PrinterApplication">
<replica-group id="PrinterAdapters">
<load-balancing type="round-robin" />
<object identity="SimplePrinter" type="iceGrid.sampleAdapterLocator.servant.PrinterI"/>
</replica-group>
<server-template id="PrinterServerTemplate">
<parameter name="index" />
<server id="PrinterServer${index}" exe="java" activation="on-demand">
<option>-jar</option>
<option>C:\PrinterApp.jar</option>
<adapter name="PrinterAdapter" replica-group="PrinterAdapters"
endpoints="ssl" />
<property name="Identity" value="SimplePrinter"/>
</server>
</server-template>
<node name="Node1">
<server-instance template="PrinterServerTemplate"
index="1" />
</node>
<node name="Node2">
<server-instance template="PrinterServerTemplate"
index="2" />
</node>
</application>
</icegrid>
和普通的发布文件相比,只需要将适配器adapter 的端点修改为ssl即可。
节点1配置:
Ice.Plugin.IceSSL=IceSSL:createIceSSL
IceSSL.DefaultDir=C:\IceGrid
IceSSL.CertFile=node_cert.pem
IceSSL.KeyFile=node_key.pem
IceSSL.CertAuthFile=ca_cert.pem
IceSSL.Password=123456
# Node properties
IceGrid.Node.Endpoints=ssl
IceGrid.Node.Name=Node1
IceGrid.Node.Data=C:\IceGrid\node
Ice.Default.Locator=IceGrid/Locator:ssl -h 192.168.1.10 -p 4065
#log properties
IceGrid.Node.Trace.Activator=3
IceGrid.Node.Trace.Adapter=3
IceGrid.Node.Trace.Server=3
启动节点
1
我们使用的是
icegridnode.exe
可执行程序,所以配置IceSSL
插件使用
C++
配置
IceSSL:createIceSSL
。节点
2
使用相同的配置,修改
IceGrid.Node.Name=Node2
。
java应用服务程序:
Ice.Plugin.IceSSL=IceSSL.PluginFactory
IceSSL.DefaultDir=C:\IceGrid
IceSSL.Keystore=cert.jks
IceSSL.Truststore=ca.jks
IceSSL.Password=123456
IceSSL.Alias=printservercert
Ice.ThreadPerConnection=1
#IceSSL.Ciphers=NONE (RSA.*AES) !(EXPORT)
#Log
IceSSL.Trace.Security=3
Ice.Trace.Protocol=1
Ice.Trace.Network=3
应用服务是我们自己使用java开发的服务器程序,使用该配置文件初始化通信器Ice.Communicator,java开发的应用和C++开发的应用程序使用IceSSl插件,在配置上有一些区别,插件类为IceSSL.PluginFactory,此类在Ice.jar包中,
IceSSL使用Java的原始格式存储密钥和证书:密钥库(keystore)。密钥库作为一个文件包含密钥对及相关证书,通常使用keytool工具管理密钥库文件。
密码被分配到每个keystore中的密钥对,以及密钥库本身,即程序在读取密钥库和密钥库中的密钥等信息时,都需要提供密码,该密码由IceSSL.Password提供。IceSSL要求密钥库中的密钥存取必须要使用密码,但密钥库的密码是可选的。如果一个密钥库的密码是指定的,是只用于验证密钥库的完整性。IceSSL要求密钥库中所有的密钥对使用相同的密码。
配置说明:
属性 | 说明 |
Ice.Plugin.IceSSL | 开启IceSSL插件服务的主类 |
IceSSL.DefaultDi | 密钥库文件存放的目录 |
IceSSL.Keystore | 服务器密钥库,存放根证书签发的服务器证书 |
IceSSL.Truststore | 受信任的根证书存放在此密钥库中 |
IceSSL.Password | 读取密钥时使用的密码 |
IceSSL.Alias | 如果服务器密钥库中有多个证书条目,此属性指定服务器使用哪个证书与客户端建立安全通信。 |
Ice.ThreadPerConnection | 如果属性值被设置为大于零,Ice运行时对于每一个连接都会创建一个线程。 |
服务器示例代码:
public class Server extends Ice.Application {
public int run(String[] args) {
//创建名为SimplePrinterAdapter的适配器,
Ice.ObjectAdapter adapter = communicator().createObjectAdapter("PrinterAdapter");
//实例化一个PrinterI对象,为Printer接口创建一个服务对象
Ice.Object object = new PrinterI();
//将服务单元增加到适配器中,并给服务对象指定名称为SimplePrinter,该名称用于唯一确定一个服务单元
adapter.add(object, Ice.Util.stringToIdentity("SimplePrinter"));
//激活适配器,这样做的好处是可以等到所有资源就位后再触发
adapter.activate();
//让服务在退出之前,一直持续对请求的监听
communicator().waitForShutdown();
return 0;
}
public static void main(String[] args) {
Server app = new Server();
System.out.println("服务器已经启动!");
String conf = “c:\\conf\\server.cnf”
System.exit(app.main("Server", args,conf ));
}
}
服务器使用
java
语言实现了一个简单的打印功能,将客户端发送到服务器端的信息打印到终端。服务端在调用
app.main("Server", args,conf )的时候,会使用
conf 变量指定的配置文件初始化通信器,同时会开启
IceSSL
插件。
客户端配置:
Ice.Plugin.IceSSL=IceSSL.PluginFactory
IceSSL.DefaultDir=C:\IceGrid
IceSSL.Keystore=cert.jks
IceSSL.Truststore=ca.jks
IceSSL.Password=123456
IceSSL.Alias=usercert
Ice.ThreadPerConnection=1
Ice.Default.Locator=IceGrid/Locator:tcp -h 192.168.1.10 -p 4066
#Ice.Trace.Network=3
客户端在建立Ice.Communicator通信器时,使用此属性集初始化通信器。在本文的例子中,客户端同样是使用
java
语言开发。Ice.Default.Locator属性指定注册器所在的服务器,使用的是tcp
协议,客户端与注册器通信内容,不会包含太过重要的信息,因此没有必要使用
ssl
协议建立通信。
客户端示例代码:
public class Client extends Ice.Application {
public int run(String[] args) {
//获取Printer的远程代理,这里使用的stringToProxy方式
Ice.ObjectPrx base = communicator().stringToProxy("SimplePrinter");
//通过checkedCast向下转换,获取Printer接口的远程,并同时检测根据传入的名称获取的服务单元是否Printer的代理接口,如果不是则返回null对象
Demo.PrinterPrx printer = Demo.PrinterPrxHelper.checkedCast(base);
if (printer == null) throw new Error("Invalid proxy");
//把Hello World传给服务端,让服务端打印出来,因为这个方法最终会在服务端上执行
for(int i = 0;i<10;i++){
String ret = printer.printString("Hello World!哈哈");
System.out.println(ret);
}
return 0;
}
public static void main(String[] args) {
Client app = new Client();
System.exit(app.main("Client", args,"C:\\client.conf"));
}
}
服务端在调用
app.main("Client", args,"C:\\client.conf")的时候,会使用
client.conf
配置文件初始化通信器,同时会开启
IceSSL
插件。
3. 安装证书
在开发过程中,有一个简单的方法创建新的证书(也可以使用其他方式制作证书,只要符合Ice要求的证书格式都可以)。OpenSSL包括所有必要的基础设施来设立自己的证书颁发机构(CA),但是我们必须对OpenSSL非常的熟悉。为了简化这个过程,Ice提供了一系列Python脚本(对应Ice-3.1.1版本的Python版本为2.7.5),位于Ice安装目录下的config/ca子目录,这些Python脚本隐藏了复杂的OpenSSL操作过程,并允许我们快速执行必要的任务:
l 初始化一张新的根证书
l 生成新的证书请求
l 给证书请求签名,创建一个有效的证书链
l 转换证书,以匹配特定的平台需求
首先需要在系统中安装Python-2.7.5版本,才能够顺利执行Python脚本。
设置环境变量ICE_CA_HOME,指定一个目录,制作证书的过程中,运行Python脚本生成的文件,将存放在该目录中。
3.1. 初始化root CA
通过以下的命令初始化一个根证书
python initca.py [--no-password] [--overwrite]
--overwrite操作会覆盖一个已经存在的根证书,如果对CA的私钥不做加密处理可以使用--no-password操作。
假设ICE_CA_HOME环境变量设置为c:\iceca,则会在该目录下生产两个文件,req.cnf和ca_cert.pem。ca_cert.pem文件包含根证书,我们的IceSSL配置文件必须指定该根证书作为我们的信任证书,C++程序通过IceSSL.CertAuthFile=C:\iceca\ca_cert.pem 指定,在java中,我们需要添加该证书到信任域中,使用如下的命令:
keytool -import -trustcacerts -file ca_cert.pem -keystore ca.jks
ca.jks是java支持的密钥库文件格式,Java中通过IceSSL.Truststore属性指定该根证书。
3.2. 生成证书请求
生成证书请求使用如下命令:
python req.py [--overwrite] [--no-password] [--node|--registry|--server|--user]
此脚本会查找req.cnf和ca_cert.pem文件,在ICE_CA_HOME 环境变量指定的目录下,然后生成两个文件:一个私钥和一个文件包含证书请求。请求文件必须被传送到证书颁发机构签名,产生一个有效的证书链。
--node,--registry代表IceGrid 的节点和注册器,--server表示Ice 的服务器,--user表示一个人或者一个客户端,这些操作只是影响着生成文件的名称,如服务器需要一个证书,可以使用命令python req.py --server,执行完脚本之后生成的文件名字为server_key.pem和server_req.pem。节点证书请求、应用程序证书请求、注册器证书请求、客户端证书请求,都需要分别执行这一条命令。server_key.pem文件为服务器生成的新的私钥,这个文件必须被安全保存,server_req.pem为服务器证书请求文件。
3.3. 签名证书请求
使用如下命令:
sign.py --in <req> --out <cert>
输入文件req为证书请求文件,输出文件cert是证书链,如给节点证书请求签名,使用如下命令:
python sign.py --in node_req.pem --out node_cert.pem
3.4. 转换证书格式
对于java用户来说,私钥和证书链必须被转换成合适的格式,使用如下命令:
python import.py [--overwrite] [--java alias cert key keystore]
如从节点的证书链文件中导出节点的私钥和节点证书到一个java密钥库中,使用如下命令:
python import.py --java mycert node_cert.pem node_key.pem cert.jks
在密钥库cert.jks文件中,Mycert为一个节点证书条目的别名,IceSSL配置文件通过属性IceSSL.Keystore来指定这个密钥库。
参考Ice官网Ice-3.1.1版本操作手册