| |
技术文档>>JAVA>>新手入门>>基础入门>查看文档 |
|
| |
javase 6基于jsr105的xml签名之实践篇 |
|
| |
文章作者:未知 文章来源:水木森林 |
|
| |
查看:98次 录入:管理员--2007-11-17 |
|
| |
在上篇(【理论篇】)中,我们分析了有关基于jsr-105进行xml签名的基本概念。在本篇中,我们开始分析一个实际的xml签名示例应用程序。
一、 密码学密钥和证书
现在,我们已经准备好我们的xml签名示例应用程序。
让我们首先分析下列xml文档-./etc/invoice.xml:
在第一种方法中,我们调用我们的keystoreinfo类的getprivatekey()方法来检索我们使用keytool创建的dsa类型私有密钥,并且把它储存在密钥存储文件-bizkeystore(前面的5.0情形)中。为了获得该私有密钥,我们还可以从bizkeystore中检索该keypair-通过调用keystoreinfo的getkeypair()方法,然后调用keypair实例(5.1.2情形)的getprivate()。另一方面,jca提供了一个名字为keypairgenerator的类用于根据需要随时动态地创建一个keypair,这正是sign.java中的情形5.1.1提到的情况。
读者还应该注意,jsr-105允许通过一个keyselector对象获得私有密钥。我们在下节讨论keyselector时还要详细分析。
【步骤6】创建一个keyinfo对象。这一步是可选的,就象keyinfo作为签名元素中的一个元素是可选的一样。在我们的示例的情形6.0下,我们使keyinfo成为null;这样以来,可以完全从结果xml签名中忽略它。
w3c建议和jsr-105定义rsa的keyvalues以及dsa类型for wrapping,respectively,rsa和dsa公共密钥,并允许它们成为keyinfo的内容。我们的示例中的情形6.1从我们以前使用jdk keytool生成的公共密钥中创建一个keyvalue对象,并且把它放到一个keyinfo对象。后面,当讨论我们的核心校验程序时,我们将看到它如何使用这样的一个keyinfo对象来检索公共密钥以用于签名校验。
在jsr-105中,我们通过调用一个keyinfofactory实例中的操作创建了keyvalue和keyinfo对象。其中,keyinfofactory负责创建所有主要的与keyinfo相关的对象-例如keyname,keyvalue,x509data等。我们可以以与我们在〖步骤1〗得到xmlsignaturefactory实例相同的方式得到一个keyinfofactory实例。我们的示例调用xmlsignaturefactory对象的getkeyinfofactory()方法取得keyinfofactory实例。
我们的示例的情形6.2将创建一个x509data对象-使用我们以前借助于工具keytool从bizkeystore中导出的证书biz.cer,然后把这个对象作为内容放入一个keyinfo对象中。再次,后面我们将讨论的核心校验程序将证明我们如何从这样的一个keyinfo对象中取得用于签名校验的公共密钥。
【步骤7】创建一个xmlsignature对象。在jsr-105中,xmlsignature接口为w3c中建议的签名元素实现了建模。我们已经在前面看到该签名元素的结构。为了创建一个xmlsiganture实例,我们可以在xmlsignaturefactory中调用下列两个方法之一:
public abstract xmlsignature newxmlsignature(signedinfo si, keyinfo ki); public abstract xmlsignature newxmlsignature(signedinfo si, keyinfo ki, list objects, string id, string signaturevalueid). |
第二个方法中的id和signaturevalueid参数将成为结果xml签名文档中的xml元素id。在我们的示例中,该xml签名将拥有一个object元素;因此,我们需要使用第二个工厂方法。
【步骤8】实例化一个domsigncontext对象,并且使用它注册私有密钥。xmlsigncontext接口(domsigncontext实现它)包含用于生成xml的上下文信息签名。
domsigncontext提供了几种形式的构造器-签名应用程序用来注册要使用的私有密钥,并且这也是我们的示例中所采用的方法。
在继续讨论签名过程的最后步骤之前,我们需要指出xmlsigncontext和domsigncontext实例都可能包含特定于它们所使用的xml签名结构的信息和状态。该jsr-105规范中声明:如果一个xmlsigncontext(或domsigncontext)与不同的签名结构一起使用,那么,结果将是无法预料的。例如,我们不应该使用相同的xmlsigncontext(或domsigncontext)实例来签名两个不同的xmlsignature对象。
【步骤9】签名。xmlsignature接口中的sign()操作实现签名xmlsignature。其实,该方法还实现若干操作,包括基于相应的digest方法计算所有引用的digest值,并且基于该签名方法和私有密钥计算签名值。该签名值被xmlsignature实例中的嵌入式signaturevalue类所捕获,而对xmlsignature实例的getsignaturevalue()方法的调用将返回使用结果值填充的signaturevalue对象。
在我们的签名程序的最后,我们把xmlsignature编排成一个xml文档-signature.xml。
三、 xml签名核心校验
在前面一节中,我们把invoice.xml文档签名成一个在signature.xml文件中捕获的enveloping xml签名。
为了校验该签名,我们可以使用下列程序-validate.java:
public class validate { public static void main(string[] args) throws exception { //第一步 string providername = system.getproperty("jsr105provider","org.jcp.xml.dsig.internal.dom.xmldsigri"); xmlsignaturefactory fac = xmlsignaturefactory.getinstance("dom",(provider) class.forname(providername).newinstance()); //第二步 documentbuilderfactory dbf = documentbuilderfactory.newinstance(); dbf.setnamespaceaware(true); document doc = dbf.newdocumentbuilder().parse(new fileinputstream(args[0])); //第三步 nodelist nl = doc.getelementsbytagnamens(xmlsignature.xmlns,"signature"); if (nl.getlength() == 0) { throw new exception("cannot find signature element!"); } //第四步,分为情形4.0,4.1,4.2或4.3 //第4.0种情形 domvalidatecontext valcontext = new domvalidatecontext(new keystorekeyselector(), nl.item(0)); //第4.1种情形,需要sign.java中的第6.1种情形 // domvalidatecontext valcontext = new domvalidatecontext( // new keyvaluekeyselector(), nl.item(0)); //第4.2种情形,需要sign.java中的第6.2种情形 // keystore ks = keystore.getinstance("jks"); // fileinputstream fis = new fileinputstream("./etc/bizkeystore"); // ks.load(fis,"sp1234".tochararray()); // fis.close(); // x509keyselector x509ks = new x509keyselector(ks); // domvalidatecontext valcontext = new domvalidatecontext(x509ks, nl.item(0)); //第4.3中情形 // publickey pkey = keystoreinfo.getpublickey("./etc/bizkeystore", // "sp1234", "biz"); //第五步 xmlsignature signature = fac.unmarshalxmlsignature(valcontext); //xmlsignature signature = fac.unmarshalxmlsignature(new domstructure(nl.item(0))); //第六步 boolean corevalidity = signature.validate(valcontext); //检查核心校验状态 if (corevalidity == false) { system.err.println("signature failed core validation!"); boolean sv = signature.getsignaturevalue().validate(valcontext); system.out.println("signature validation status: " + sv); //每一个reference的检查校验状态 iterator i = signature.getsignedinfo().getreferences().iterator(); for (int j = 0; i.hasnext(); j++) { boolean refvalid = ((reference) i.next()).validate(valcontext); system.out.println("reference (" + j + ") validation status: "+ refvalid); } } else { system.out.println("signature passed core validation!"); } } } |
要试验这个程序,读者可以运行ant目标校验。该程序把核心校验状态打印到system.out。如果签名是有效的,将输出"signature passed core validation!";否则,输出结果中将展示引用和签名的校验状态;而这样以来,我们就可以准确地搞清楚是它们其中的哪一些导致了此次失败。
校验signature.xml的过程可以分解成六个步骤。
步骤1-加载一个xmlsignaturefactory实例,这一步与在签名程序中是一样的。
步骤2-加载要校验的xml签名。在这一步中,我们需要把包含xml签名的xml加载到内存中并且把该xml文档转换成一棵dom树。
步骤3-识别dom树中的签名结点。签名是在命名空间http://www.w3.org/2000/09/xmldsig#中定义的,它被描述为在jsr-105中的xmlsignature接口的静态变量xmlns。
步骤4-创建一个domvalidatecontext实例。
一个校验上下文中的一项最关键的信息显然是密钥。我们可以使用domvalidatecontext并通过两种不同的方法来注册公共密钥。在第一种方法中,如果校验应用程序已经拥有公共密钥,它可以把该密钥直接通过下列domvalidatecontext的构造器放入上下文中:
| public domvalidatecontext(key validatingkey,node node) |
这正是在我们的示例中的情形4.3。
第二个方法将使用domvalidatecontext注册一个keyselector,并且让该keyselector选择公共密钥-基于在要校验的xmlsignature对象中可用的信息。在jsr-105中,keyselector是一个定义了两个操作的抽象类:
public abstract keyselectorresult select(keyinfo keyinfo, purpose purpose, algorithmmethod method, xmlcryptocontext context) throws keyselectorexception public static keyselector singletonkeyselector(key key) |
第二个操作创建一个总是返回相同密钥的keyselector。第一个操作试图选择一个密钥-它能够满足作为输出传递的要求。
keyselectorresult是jsr-105中的一个接口-该规范中要求这个接口包含一个使用keyselector选择的key值。在我们的示例中,我们使用simplekeyselectorresult类实现这个接口-简单地包装选择的公共密钥。
在我们的示例中,我们实现并利用三个不同的keyselectors来说明一个校验应用程序工作的一些情形。
在情形4.0中,keystorekeyselector基于输入参数从一个key存储中检索公共密钥。
在情形4.1中,keyvaluekeyselector基于在输入keyinfo对象(它应该包含一个keyvalue对象作为它的内容的一部分;请参考sign.java中的情形6.1)中的keyvalue信息选择一个键值。
在情形4.2中,x509keyselector基于包含在keyinfo对象(它应该包含一个x509data对象作为它的内容的一部分;请参考sign.java中的情形6.2)中的x509data及其它信息选择一个键。我们使用的是jsr-105中的x509keyselector-其原作者是sean mullan。在此,我们稍微修改了一下其中的私有certselect()方法以便它可以适合于我们使用keytool生成的证书。
既然签名中的keyinfo可能包含各种信息,显然,一个应用程序必须选择一个keyselector实现-由它来使用包含在它将处理的keyinfos中的信息。
步骤5-把签名结点反编排成一个xmlsiganture对象。在上一步骤中,我们把signature.xml文件加载进一棵dom树-由相应于树中的signature元素的结点所标识,并且使用一个domvalidatecontext和keyselector(或私有密钥)注册该结点。为了校验该xml签名,我们需要把signature结点反编排为一个xmlsignature对象。这是通过调用下列xmlsignaturefactory操作实现的:
public abstract xmlsignature unmarshalxmlsignature(xmlvalidatecontext context) throws marshalexception |
步骤6-校验xml签名。这是通过调用xmlsignature实例的validate()方法实现的-以domvalidatecontext作为唯一的输入参数。
该validate()方法根据在w3c建议中定义的核心校验过程校验xml签名。如前面所提及,这个过程包括两个部分。其一是校验所有的参考。在jsr-105中,这可以通过调用reference接口的validate()操作来实现-以相关的校验上下文作为输入参数。
该核心校验的第二部分是签名校验-校验规范的signedinfo元素的签名值。借助于jsr-105,我们可以显式地完成这一部分-通过调用与xmlsignature实例相关联的signaturevalue对象的validate()方法实现,并以相关的校验上下文作为输入参数。
在我们的示例中,我们使用这样的知识来输出每一个reference和siganturevalue的校验状态-当xml签名(核心)校验失败时;这样以来,我们就可以得到导致失败的更为详细的信息。
四、 修改xml签名
为了表明该校验程序确实能够捕获对生成的xml签名的修改,我们可以在我们的示例中创建一个tamper.java程序,允许我们修改清单中的信用卡号(或signature.xml文件中的signaturevalue元素)。
这个程序使用xml签名文档和一个布尔值作为参数。当该布尔参数为true时,程序改变信用卡号;否则,它修改签名值。
public class tamper { public static void main(string[] args) throws exception { string sigfile = "etc/signature.xml";
//决定要修改的标志-reference或signaturevalue boolean tamperref = true ;
if (args.length >= 2) { sigfile = args[0]; tamperref = boolean.parseboolean(args[1]); } file file = new file(sigfile); documentbuilderfactory dbf = documentbuilderfactory.newinstance(); dbf.setnamespaceaware(true); document signature = dbf.newdocumentbuilder().parse(file);
if (tamperref){ //修改信用卡号 nodelist targets =signature.getdocumentelement().getelementsbytagname("number"); node number = targets.item(0); if (!number.gettextcontent().equals("987654321")){ number.settextcontent("987654321"); }else{ number.settextcontent("000000000"); } }else{ //修改signaturevalue(第一字节) base64encoder en = new base64encoder(); base64decoder de = new base64decoder(); nodelist sigvalues =signature.getdocumentelement().getelementsbytagname("signaturevalue"); node sigvalue = sigvalues.item(0); byte[] oldvalue = de.decodebuffer(sigvalue.gettextcontent()); if (oldvalue[0]!= 111){ oldvalue[0] = (byte)111; }else{ oldvalue[0] = (byte)112; } sigvalue.settextcontent(en.encode(oldvalue)); } transformerfactory tf = transformerfactory.newinstance(); transformer trans = tf.newtransformer(); trans.transform(new domsource(signature),new streamresult(new fileoutputstream(file))); } } |
为了运行它,读者可以执行经修改的ant目标。在运行这个修改的程序后,如果我们再次运行该校验程序,核心校验将失败,并且system.out将分别输出引用和签名校验的状态。随着第二个boolean输入参数值的不同,引用与/或签名校验可能报告失败。
至此,我们已经讨论完本文中的示例应用程序。
五、 结论
xml签名和jsr-105中都包含了大量的内容,在一篇小小的文章中我们是无法全面涉及它们(例如transforms,canonicalization方法,还有manifest和signatureproperties元素)的。而且,我们也根本没有深入分析digest和签名算法。感兴趣的读者应该进一步参考w3c建议,jsr-105 api文档以及相关密码学信息。
w3c建议中仅要求实现基于sha-1哈希函数支持digest和签名方法。注意,一个由王小云教授率领的中国数学研究小组已经攻克了一些包括sha-1在内广泛应用的哈希函数中的安全问题。尽管他们的结果还不是立即意味着-现在sha-1在xml签名方面的使用还不是不安全的;但是,密码学专家们确实推荐在新的应用程序和系统中应该考虑使用更为安全的算法。
读者还应该明白,尽管jcp计划随同java se 6一起发行jsr-105,但是jsr-105要求全面兼容实现对jdk1.4及其高版本的支持。因此,在使用jdk 1.4开发的应用程序中使用这一技术应该是没有问题的。
|
|
|
|
相关文档
|