JavaSE6基于JSR105的XML签名是怎样实现的-创新互联

这篇文章将为大家详细讲解有关JavaSE 6基于JSR105的XML签名是怎样实现的,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

十余年建站经验, 做网站、成都网站设计客户的见证与正确选择。创新互联提供完善的营销型网页建站明细报价表。后期开发更加便捷高效,我们致力于追求更美、更快、更规范。

我们开始分析一个实际的XML签名示例应用程序。

  一、 密码学密钥和证书

  现在,我们已经准备好我们的XML签名示例应用程序。

  让我们首先分析下列XML文档-./etc/invoice.xml:

<?XML version="1.0" encoding="UTF-8" standalone="no"?>
<invoice XMLns="http://www.company.com/accounting">
<items>
 <item>
  <desc>Applied Cryptography</desc>
  <type>book</type>
  <unitprice>44.50</unitprice>
  <quantity>1</quantity>
 </item>
</items>
<creditcard>
 <number>123456789</number>
 <expiry>10/20/2009</expiry>
 <lastname>John</lastname>
 <firstname>Smith</firstname>
</creditcard>
</invoice>


  我们计划使用一个XML签名对它进行签名并且希望使用一个基于一个公共密钥的签名方法。

  让我们先生成密码学密钥。为此,我们可以使用JDK中提供的keytool工具-把该程序移动到./etc文件夹下,并且执行下列命令:

keytool -genkey -keysize 512 -sigalg DSA -dname "cn=Young Yang, ou=Architecture, o=Company, L=New York, ST=NY, c=US" -alias biz -keypass kp1234 -keystore bizkeystore -storepass sp1234 -validity 180


  这个命令能够创建密钥并预以存储-名字为bizkeystore,存储在工作目录./etc下,并且指定它的口令为sp1234。它还生成一个针对实体(它包含有一个卓著的名字-Young Yang)的公有/私有密钥对。【注意】,这里使用DSA密钥生成算法来创建公有/私有密钥-都为512位长。
上面的命令进一步创建了一个自签名的证书,这是使用SHA1的DSA算法(JSR-105注释中的DSA_SHA1,其中包括了公共密钥和前面那个卓著名字信息)实现的。这个证书将保持180天的有效期并且关联与一个密钥存储文件(此处引用的别名为"biz")中的私有密钥。该私有密钥被赋予口令kp1234。

  我们的示例中包括一个简单的Java类-KeyStoreInfo,用于把存储于前面的密钥存储文件中的密钥和证书信息输出到System.out;这个类也用于应用程序从中取得密钥对-这里的私有和公共密钥匹配作为输入参数指定的条件。为了试验它能够输出包含在前面存储文件bizkeystore中的信息,读者可以运行Ant目标ksInfo。

  下列代码片断显示KeyStoreInfo中的用来检索一个KeyPair的方法:

public static KeyPair getKeyPair(String store,String sPass,String kPass,String alias)
throws CertificateException,
IOException,
UnrecoverableKeyException,
KeyStoreException,
NoSuchAlgorithmException{
 KeyStore ks = loadKeyStore(store,sPass);
 KeyPair keyPair = null;
 Key key = null;
 PublicKey publicKey = null;
 PrivateKey privateKey = null;
 if (ks.containsAlias(alias)){
  key = ks.getKey(alias,kPass.toCharArray());
  if (key instanceof PrivateKey){
   Certificate cert = ks.getCertificate(alias);
   publicKey = cert.getPublicKey();
   privateKey = (PrivateKey)key;
   return new KeyPair(publicKey,privateKey);
  }else{
   return null;
  }
 } else {
  return null;
 }
}


  借助于一个KeyPair,我们可以容易地得到PrivateKey和PublicKey-通过调用相应的操作getPrivate()和getPublic()实现。

  为了从KeyStore中得到一个PublicKey,我们并不真正需要在上面的方法中所要求的密钥口令,而这正是下列方法所实现的:

public static PublicKey getPublicKey(String store,
String sPass, String alias)
throws KeyStoreException,
NoSuchAlgorithmException,
CertificateException,
IOException{
 KeyStore ks = loadKeyStore(store, sPass);
 Certificate cert = ks.getCertificate(alias);
 return cert.getPublicKey();
}


  在上面两部分代码片断中,方法KeyStore loadKeyStore(String store,String sPass)是一个工具函数,用于实例化一个KeyStore对象,并且从文件系统加载入口。我们以如下方式实现它:

private static KeyStore loadKeyStore(String store, String sPass)
throws KeyStoreException,
NoSuchAlgorithmException,
CertificateException,
IOException{
 KeyStore myKS = KeyStore.getInstance("JKS");
 FileInputStream fis = new FileInputStream(store);
 myKS.load(fis,sPass.toCharArray());
 fis.close();
 return myKS;
}


  伴随JDK提供的keytool还可以把存储在一个密钥储存文件内的证书输出到系统文件中。例如,为了创建一个包含X509证书(关联于别名为biz的密钥入口)的biz.cer文件,我们可以从文件夹./etcdirectory下运行下列命令:

keytool -export -alias biz -file biz.cer -keystore bizkeystore -storepass sp1234

  这个证书实现认证我们讨论上面的公共密钥。

  我们还在示例中包括了一个Java类-CertificateInfo,用于把一个证书中的一些有趣的信息输出到System.out。为了试验这一点,读者可以运行Ant目标certInfo。然而,要理解该代码及其输出,必须具有DSA和RSA算法的基本知识。当然,读者可以安全地绕过这个程序而继续阅读本文后面的内容。

二、 生成一个Enveloping签名  这一节讨论借助于JSR-105 API及其缺省实现来实现对invoice.xml文件的签名。

  我们的示例中创建了一个enveloping签名。注意,当你想使用在一种detached或enveloped签名情形下时,也仅需对本例作一些细微修改。

  下面,让我们分析程序Sign.java,它能够生成invoice.xml文件的XML签名。

public class Sign {
 public static void main(String[] args) throws Exception {
  String input = "./etc/invoice.xml ";
  String output = "./etc/signature.xml";
  if (args.length > 2) {
   input = args[0];
   output = args[1];
  }
  //准备
  DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  dbf.setNamespaceAware(true);
  //步骤1
  String providerName = System.getProperty("jsr105Provider","org.jcp.XML.dsig.internal.dom.XMLDSigRI");
  XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM",(Provider) Class.forName(providerName).newInstance());
  //步骤2
  Reference ref = fac.newReference("#invoice",fac.newDigestMethod(DigestMethod.SHA1, null));
  //步骤3
  Document XML = dbf.newDocumentBuilder().parse(new File(input));
  Node invoice = XML.getDocumentElement();
  XMLStructure content = new DOMStructure(invoice);
  XMLObject obj = fac.newXMLObject(Collections.singletonList(content),"invoice", null, null);
  //步骤4
  SignedInfo si = fac.newSignedInfo(fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,
(C14NMethodParameterSpec) null),
fac.newSignatureMethod(SignatureMethod.DSA_SHA1, null),
Collections.singletonList(ref));
  //步骤5,分为情形5.0或5.1
  PrivateKey privateKey = null;
  //情形5.0
  privateKey = KeyStoreInfo.getPrivateKey("./etc/bizkeystore","sp1234","kp1234", "biz");
  //情形5.1,分为情形5.1.1或5.1.2

  //情形5.1.1
  //KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
  //kpg.initialize(512);
  //KeyPair kp = kpg.generateKeyPair();

  //情形5.1.2
  // KeyPair kp = KeyStoreInfo.getKeyPair("./etc/bizkeystore", "sp1234",
  // "kp1234","biz");

  //如果针对情形5.1,请去掉下面一行中的注释
  // privateKey = kp.getPrivate();

  //步骤6,分为情形6.0,6.1或6.2

  //情形6.0,如果针对情形6.1或6.2也使用下面这一行
  KeyInfo ki = null;

  //如果针对情形6.1或6.2请去掉下面一行中的注释
  // KeyInfoFactory kif = fac.getKeyInfoFactory();

  //情形6.1
  // KeyValue kv = kif.newKeyValue(kp.getPublic());
  // ki = kif.newKeyInfo(Collections.singletonList(kv));

  //情形6.2
  // CertificateFactory cf = CertificateFactory.getInstance("X.509");
  // FileInputStream fis = new FileInputStream("./etc/biz.cer");
  // java.security.cert.Certificate cert = cf.generateCertificate(fis);
  // fis.close();
  // X509Data x509d = kif.newX509Data(Collections.singletonList(cert));
  // ki = kif.newKeyInfo(Collections.singletonList(x509d));

  //步骤7
  XMLSignature signature = fac.newXMLSignature(si, ki,Collections.singletonList(obj), null, null);

  //步骤8
  Document doc = dbf.newDocumentBuilder().newDocument();
  DOMSignContext dsc = new DOMSignContext(privateKey, doc);

  //步骤9
  signature.sign(dsc);
  //转换成一个xml文档
  TransformerFactory tf = TransformerFactory.newInstance();
  Transformer trans = tf.newTransformer();
  trans.transform(new DOMSource(doc),new StreamResult(new FileOutputStream(output)));
 }
}


  为了试验这个程序,读者可以运行Ant目标签名-它将创建一个XML文档./etc/signature.xml。这就是所谓的XML签名。为了保持我们的代码更为整洁和集中,我们省略了分析XML和转换DOM树中所有相关的格式设置。结果是,signature.xml文件成为一个有些凌乱的文本文件。

  现在,让我们详细分析一下这个程序来说明如何在JSR-105中对一个XML签名进行签名。

  签名invoice.xm的过程可以分解为如下九个步骤。

  【步骤1】加载一个XMLSignatureFactory实例。这个工厂类将负责构建几乎所有主要的对象-我们在JSR-105中API中处理XML签名时需要使用这些对象,除了那些与KeyInfo相关的对象之外。

  【步骤2】选择一个digest方法并创建相应的Reference对象。我们使用在〖步骤1〗中创建的XMLSignatureFactory实例来创建DigestMethod和Reference对象。

  在XMLSignatureFactory中的针对DigestMethod对象的工厂操作如下所示:

public abstract DigestMethod newDigestMethod(String algorithm,
DigestMethodParameterSpec params) throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException


  【注意】这个params参数用来指定digest算法可能需要的参数;在SHA-1,SHA-256或SHA-512的情况下,我们可以使用null。

  为了创建一个Reference对象,XMLSignatureFactory提供了四种操作:

public abstract Reference newReference(String uri, DigestMethod dm);
public abstract Reference newReference(String uri, DigestMethod dm,
List transforms, String type, String id);
public abstract Reference newReference(String uri, DigestMethod dm,
List transforms, String type, String id, byte[] digestValue);
......


  为了全面地理解在那些操作中的输入参数的意思,我们需要分析一下在W3C建议中的Reference元素的XML模式定义:

<element name="Reference" type="ds:ReferenceType"/>
<complexType name="ReferenceType">
 <sequence>
  <element ref="ds:Transforms" minOccurs="0"/>
  <element ref="ds:DigestMethod"/>
  <element ref="ds:DigestValue"/>
 </sequence>
<attribute name="Id" type="ID" use="optional"/>
<attribute name="URI" type="anyURI" use="optional"/>
<attribute name="Type" type="anyURI" use="optional"/>
</complexType>


  其中,URI属性参考Reference相应的数据对象。

  对于我们的示例来说,我们使用SHA-1作为digest方法,并且使用#invoice来在相同的XML签名文档(它包含这个Reference对象的XML描述)中引用一个元素。由#invoice所引用的元素正是我们要在下一步所要讨论的内容。

[@more@]【步骤3】加载invoice.xml并且用一个XMLObject对象把它包装起来。注意,并非所有的签名生成过程都要求这个步骤。XMLObject在JSR-105中对于我们以前简短地讨论过的可选的Object元素进行建模。该Object元素具有下列模式定义:

<element name="Object" type="ds:ObjectType"/>
<complexType name="ObjectType" mixed="true">
 <sequence minOccurs="0" maxOccurs="unbounded">
  <any namespace="##any" processContents="lax"/>
 </sequence>
 <attribute name="Id" type="ID" use="optional"/>
 <attribute name="MimeType" type="string" use="optional"/>
 <attribute name="Encoding" type="anyURI" use="optional"/>
</complexType>


  XMLSignatureFactory提供下列方法来创建一个XMLObject实例:

public abstract XMLObject newXMLObject(List content, String id,String mimeType,String encoding)

  我们使用一个DOMStructure对象来包装invoice.xml的根结点。在JSR-105中定义的DOMStructure可以帮助从原始待签名的XML文档中把结点导入到JSR-105运行时刻。

  我们指定#invoice作为结果对象元素的id。JSR-105实现知道在步骤2中创建的引用对象参考invoice.xml文档,因为这个id把它们链接在一起(在Reference一边,URI属性指向这个id)。

  【步骤4】创建SignedInfo对象。在W3C建议中,SignedInfo元素具有下列模式定义:

<element name="SignedInfo" type="ds:SignedInfoType"/>
<complexType name="SignedInfoType">
 <sequence>
  <element ref="ds:CanonicalizationMethod"/>
  <element ref="ds:SignatureMethod"/>
  <element ref="ds:Reference" maxOccurs="unbounded"/>
 </sequence>
 <attribute name="Id" type="ID" use="optional"/>
</complexType>


  为了创建一个SignedInfo对象,我们需要在〖步骤2〗中创建的Reference;我们还需要两个实例-一个是CanonicalizationMethod的实例,另一个是SignatureMethod的实例。我们建议感兴趣的读者参考一下规范说明书从而对这四种XML规范算法有一个更为精确的了解;在此,我们只是简单地指出,在我们决定选择一个特定的算法-alg后,后面对XMLSignatureFactory的一个实例(即fac)的调用将会创建CanonicalizationMethod的实例:

fac.newCanonicalizationMethod(alg,null)


  我们可以创建一个SignatureMethod实例-通过调用下列在XMLSigantureFactory中定义的操作:

public abstract SignatureMethod newSignatureMethod(String algorithm,
SignatureMethodParameterSpec params) throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException


  在我们的示例中,我们拥有一个DSA类型密钥对;因此,我们需要选择一个基于DSA的算法-例如DSA_SHA1。对于DSA-SHA1,我们可以把params参数设置为null。

  为了创建一个SignedInfo实例,XMLSignatureFactory定义了如下两个工厂方法:

public abstract SignedInfo newSignedInfo(CanonicalizationMethod cm,
SignatureMethod sm, List references);
public abstract SignedInfo newSignedInfo(CanonicalizationMethod cm,
SignatureMethod sm, List references, String id).


  在第二个工厂方法中的第二个参数-id响应于XML签名文档中的SignedInfo元素的Id属性。

【步骤5】-获得签名私有密钥。

  在我们的示例中,我们展示了三种不同的方法来得到该私有密钥。在第一种方法中,我们调用我们的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树中的签名结点。签名是在命名空间/tupian/20230522/Overview.html  步骤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输入参数值的不同,引用与/或签名校验可能报告失败。

关于JavaSE 6基于JSR105的XML签名是怎样实现的就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。


网页题目:JavaSE6基于JSR105的XML签名是怎样实现的-创新互联
网页网址:http://pwwzsj.com/article/djcpco.html