本篇文章主要有以下内容:
一 java安全模型的基本组件
二 安全管理器
三 代码签名与证书
四 基于安全管理器,策略,保护域的代码访问控制
一 java安全模型的基本组件
1 类装载器结构: 使得类加载器对加载的类进行命名空间的隔离
2 class文件检验器: class文件检验器在虚拟机装载类时,确保类的字节序列的正确性
3 java虚拟机及语言的安全特性,使得java虚拟机运行时保护内存的完整性:
(1)结构化的内存访问机制:
与c或c++不同,在java中没有通过使用强制转换指针类型或者通过进行指针运算直接访问内存的方法,引用只能转换为"正确"的类型,并且不能随意给引用加上偏移量让其指向对应对象以外的内存空间.
(2)垃圾收集器自动对内存进行管理,而c++程序中需要显式对内存进行管理,如果不再使用的对象没有被释放,会导致内存泄漏,多次释放一个对象会导致内存冲突
(3)数组边界的检查: 以c++中数组操作实际上就是指针运算,会带来潜在的内存冲突,而在java中会对数组进行越界检查,越界时会招出异常.
(4)对象引用的空检查,每次使用引用的时候java会确保引用不为空,如果为空只是抛出异常,但在c++中,使用一个空指针通常会导致程序崩溃
4 安全管理器及Java API,见后面介绍.
注: 前三种机制是对java虚拟机运行实例不被恶意或有的代码侵犯的保护,而安全管理器是用于保护虚拟机的外部资源不被虚拟机内运行的恶意或有的代码侵犯
二 安全管理器
重要的参考资料:
java之jvm学习笔记:
IBM文章之java安全模型介绍:http://www.ibm.com/developerworks/cn/java/j-lo-javasecurity/
1 安全管理器基本概念:
(1)它主要用于保护虚拟机的外部资源不被虚拟机内运行的恶意或有的代码侵犯.这个安全管理器是一个单独的对象,在运行的java虚拟机中,它在对于外部资源的访问控制中起中枢作用
(2)Java API在进行一个可能不安全的操作前,总是先检查安全管理器.
(3)当java应用程序启动时,它还没有安全管理器,就是说java应用程序在默认情况下不会有任何安全限制.可以通过一个指向java.lang.SecurityManager或是其子类的实例(自定义的子类实现)赋给System.setSecurityManager(),以此来安装安全管理器,也可以在jvm启动参数中加上-Djava.security.manager给让程序自动安装默认的安全管理器. 这样,java API在请求一个可能不安全的操作时,会被安全管理器审核.
(4)具体的实现上,当一个java api即将进行一个潜在不安全的动作时,将遵循以下两个步骤:
st1: 首先,JAVA API代码检查有没有安装安全管理器,如果没有安装,则跳过第二步直接继续这个潜在不安全的动作
st2: 否则,在第二步中,将调用安全管理器中的合适的check方法.如果这个操作被禁止,那么check方法将抛出一个安全异常.如果这个操作被允许,将么check方法将简单地返回
2 例如,如果书写了一个SecurityManager的子类MySecurityManager并重写了checkRead(String file)方法,当file为.exe后缀时抛出SecurityException异常,当程序启动时将MySecurityManager实例设置到System.setSecurityManager(),当调用FileInputStream(File file)构造方法时,如果file name包含.exe后缀,就会抛出异常,阻止对文件的操作.
注: ■当设置了SecurityManager后,使用自定义的类会抛出java.lang.ClassNotFoundException,需要把jdk/lib下的dt.jar加到classpath
三 代码签名与证书
1 A的签名: 使用A的公钥/私钥对保证content是由A提供并且没有被更改
(1)A用公开的单向散列算法对content进行单向散列运算,生成摘要
(2)A用自己的私钥将摘要加密附加到content后面
(3)content的使用者B使用A的公钥将加密的摘要解密
(4)B根据content使用单向散列算法重新生成一遍摘要与解密后的摘要做比较,如果一致,那么可以确认content是A提供的并且没有被第三方篡改
(5)只对摘要加密而不连content一起加密,是因为用私钥进行加密是一个相当费时的过程.并且由于公钥能解开被私钥加密的内容,那么内容一样没有隐私安全
2 证书
(1)假如B使用了C伪造了A的公钥,那么C可以使用假的content欺骗B,为了加强公钥的可信度,可以通过证书机构来为A的公钥做担保.例如,A可以在证书机构D登记自己的公钥APub,然后D用自己的公钥/私钥对的私钥Dcry对APub进行签名,进而发布认证机构签名后的Apub,即A的证书
(2)当B得到了证书后,用证书机构D的公钥对A的证书进行解密得到A的公钥,这样就使A的公钥被伪造的可能性降低了,因为要达到欺骗的目地,伪造者C还要获得证书机构的私钥.
(3)注意,证书的方式仍然无法避免欺骗的发生
3 在1和2的描述中,没有涉及到content的加密,下面的过程描述了如何对content进行加密与解密
(1)A需要得到B的公钥,然后用Bpub对content进行加密,这样,只有拥有Bcry的B才能对content进行解密
(2)由于公钥加密内容也是一个费时的过程,一般情况下,是在A使用一个对称密钥将content加密,然后用Bpub将对称密钥加密,附加在加密后的content后面付给B,这样只有B才能获取到对称密钥并对content进行解密
4 关于非对称密钥对的生成和使用
(1)keytool: 生成密钥对的工具
(2)keystore文件: 用来保存若干对密钥对,各密钥对以唯一的别名进行区分,一对密钥对有一个密码,一个keystore文件也有一个密码保存
5 关于jar包的签名:
st1: 用jdk的keytool工具生成keystore文件和密钥对,需要设定密码
st2: 用jdk的jarsigner工具对jar包进行签名,签名时指定keystore文件和密钥对,需要指定密码,被签名的jar包里面会生成签名相关的内容,如签名者的签名,签名者用自己私钥对自己公钥进行签名的证书等(即自签名证书),通过这些内容,可以用jarsigner -verify对已签名的jar包进行签名验证,但注意这个自签名证书是不可靠的,除非使用第三方公证机构的证书.
6 关于HTTPS的原理,参见: http://www.ruanyifeng.com/blog/2011/08/what_is_a_digital_signature.html
7 U盾的密码学原理: 参见:
四 基于安全管理器,策略,保护域的代码访问控制
1 策略的概念
java对应用程序的访问控制策略是由抽象类java.security.Policy的一个子类的单例所表示,任何时候,每个应用程序只有一个Policy对象,而Policy对象对应着策略文件,类装载器利用这个Policy对象来帮助他们决定,在把一段代码导入虚拟机时应该给予什么权限。
2 策略的使用示例:
(1)首先在D:/workspace/TestPolicy/bin/中放置一个class文件,即测试类com.yfq.test.TestPolicy,执行new FileWriter("d:/testPolicy.txt").write("hello1");
(2)然后在D:/workspace/TestPolicy/src/myPolicy.txt定义一个名字为myPolicy.txt的策略文件,内容如下:
grant codeBase "file:D:/workspace/TestPolicy/bin/*" {
permission java.io.FilePermission "d:/testPolicy.txt", "read";
};
解析:
grant codeBase "file:D:/workspace/TestPolicy/bin/*"意思是给D:/workspace/TestPolicy/bin/*给这个路径下的所有class文件定义权限,星号是统配符,所有的意思,codeBase后面的引号里面的内容其实是一个URL,代表了class文件,即代码的来源(CodeSource);
permission java.io.FilePermission "d:/testPolicy.txt", "read";意思是d:/testPolicy.txt这个文件只分配读的权限
(3)然后用命令行执行:
java -classpath D:/workspace/TestPolicy/bin -Djava.security.manager -Djava.security.policy=D:/workspace/TestPolicy/src/myPolicy.txt com.yfq.test.TestPolicy
于是,com.yfq.test.TestPolicy类将会被类装载器从classpath D:/workspace/TestPolicy/bin里装载,程序运行时也会安装默认的SecurityManager,并通过指定策略文件myPolicy.txt,控制com.yfq.test.TestPolicy运行时对d:/testPolicy.txt的访问,因为策略文件中只配置了读的权限,当调用write("hello1")方法时,将抛出AccessControlException.当将
permission java.io.FilePermission "d:/testPolicy.txt", "read" 改为 permission java.io.FilePermission "d:/testPolicy.txt", "read,write"后,运行会成功.
注: 策略文件不但可以给一个代码来源授权,还可以给一系列被签名的代码库授权(只要在策略文件中指明keystore的路径及所用到的密钥对).同时,策略文件不仅可以存储在文件中(后缀名是什么不重要),还可以存放在数据库里
3 代码访问控制总结:
st1 :关于角色名
(1)类: Class
(2)类加载器: ClassLoader
(3)安全管理器: SecurityManager
(4)策略: Policy
(5)权限: Permission
(6)代码来源: CodeSource
(7)保护域: ProtectionDomain
(8)访问控制器: AccessController
st2: 安全模型原理
(1)每个被类加载器加载的类将会被指派一个保护域,一个保护域对应着一组权限Permissions,表示对应的类访问外部资源所拥有的权限,Java核心API拥有所有权限,即AccessController不会拦截Java核心API对外部资源的访问.
(2)通过配置策略文件,指定哪些类(通常指的是代码来源CodeSource,包括URL来源,签名的jar等等)拥有哪些权限(通过grant Permission实现),每个程序启动后将生成一个唯一的Policy实例,对应着策略文件,Policy实例维护着ProtectionDomain与PermissionCollection的对应关系
(3)当某类调用一个方法直接或间接访问到外部资源时,首先是通过程序中设置的SecurityManager.check***()方法,间接调用AccessController.checkPermission()方法对当前线程所有方法栈对应的每个类的权限进行校验,从栈顶开始执行,java核心API对应的类自然是校验通过的,通过AccessController.doPrivileged()特权方式调用方法的类,并且这个类对应的保护域拥有对应的Permission,那么也是校验通过的,在特权方式并且拥有对应权限的情形下,AccessController就不用再对先入栈的方法对应的类的权限再进行校验了.但是如果在校验时在遇到doPrivileged()特权方式校验通过之前遇到有方法栈帧对应的类没有所需的权限,那么就会抛出AccessControlException,校验不通过.假如没有遇到doPrivileged()特权方式校验通过的情形,但是在线程的方法栈的所有方法调用所对应的类的权限校验都通过,那么访问校验的最终结果也是校验通过的
(4)也就是说,AccessController校验类对应的ProtectionDomain的Permission的过程,它采取的是栈的校验机制(先进后出,即先进后校验,出来才校验),如果一个没有权限的类要越权,那么就要借助有相应权限的类使用doPrivileged()方法的方式越权,并且在越权之前的校验中不会有校验不通过而抛异常的情形.
(5)而权限校验的具体方法是通过
Permission)方法 ,从而判断策略配置的Permission包不包含需要校验的权限
4 目前java安全模型不能解决恶意移动代码的两个可能的活动:
(1)不断分配内存直到内存耗尽
(2)不断生成线程导致每件事都很慢