From 4e23f53f4d7294254973303014d6865d84287afd Mon Sep 17 00:00:00 2001 From: "honwhy.wang" Date: Mon, 10 Sep 2018 21:46:29 +0800 Subject: [PATCH] add support for ecdsa-ripemd160 --- .../xmlsec/BcEcdsaSignatureAlgorithm.java | 41 ++++ src/main/java/com/honey/xmlsec/MyUtil.java | 8 +- .../xmlsec/XPointerResourceResolver.java | 207 ++++++++++++++++++ .../honey/xmlsec/XmlSecApplicationTests.java | 47 +++- src/test/resources/ecdsa.jks | Bin 0 -> 850 bytes 5 files changed, 295 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/honey/xmlsec/BcEcdsaSignatureAlgorithm.java create mode 100644 src/test/java/com/honey/xmlsec/XPointerResourceResolver.java create mode 100644 src/test/resources/ecdsa.jks diff --git a/src/main/java/com/honey/xmlsec/BcEcdsaSignatureAlgorithm.java b/src/main/java/com/honey/xmlsec/BcEcdsaSignatureAlgorithm.java new file mode 100644 index 0000000..b3f520d --- /dev/null +++ b/src/main/java/com/honey/xmlsec/BcEcdsaSignatureAlgorithm.java @@ -0,0 +1,41 @@ +package com.honey.xmlsec; + +import org.apache.xml.security.algorithms.JCEMapper; +import org.apache.xml.security.exceptions.XMLSecurityException; +import org.apache.xml.security.signature.XMLSignatureException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.security.NoSuchAlgorithmException; +import java.security.Signature; + +/** + * for SignatureMethod, baseURI=http://www.w3.org/2007/05/xmldsig-more#ecdsa-ripemd160 + */ +public class BcEcdsaSignatureAlgorithm extends BcSignatureAlgorithm{ + public BcEcdsaSignatureAlgorithm(Document doc, String algorithmURI) throws XMLSecurityException { + super(doc, algorithmURI); + } + + public BcEcdsaSignatureAlgorithm(Document doc, String algorithmURI, int hmacOutputLength) throws XMLSecurityException { + super(doc, algorithmURI, hmacOutputLength); + } + + public BcEcdsaSignatureAlgorithm(Element element, String baseURI) throws XMLSecurityException { + super(element, baseURI); + } + + public BcEcdsaSignatureAlgorithm(Element element, String baseURI, boolean secureValidation) throws XMLSecurityException { + super(element, baseURI, secureValidation); + } + + @Override + protected void initEngine() throws XMLSecurityException { + try { + JCEMapper.setProviderId(provider.getName()); + engine = Signature.getInstance("RIPEMD160withECDSA", provider); + } catch (NoSuchAlgorithmException e) { + throw new XMLSignatureException(e); + } + } +} diff --git a/src/main/java/com/honey/xmlsec/MyUtil.java b/src/main/java/com/honey/xmlsec/MyUtil.java index 25fdfbc..99f0d94 100644 --- a/src/main/java/com/honey/xmlsec/MyUtil.java +++ b/src/main/java/com/honey/xmlsec/MyUtil.java @@ -143,8 +143,8 @@ public String signWithCertEcdsa(String sourceXml, PrivateKey privateKey, X509Cer null, Constants._ATT_ALGORITHM, Canonicalizer.ALGO_ID_C14N_OMIT_COMMENTS ); - AbstractSignatureAlgorithm signatureAlgorithm = - new BcEcdsaSignatureAlgorithm(doc, XMLSignature.ALGO_ID_SIGNATURE_ECDSA_RIPEMD160); + SignatureAlgorithm signatureAlgorithm = + new SignatureAlgorithm(doc, XMLSignature.ALGO_ID_SIGNATURE_ECDSA_RIPEMD160); XMLSignature sig = new XMLSignature(doc, null, signatureAlgorithm.getElement(), canonElem); @@ -158,8 +158,8 @@ public String signWithCertEcdsa(String sourceXml, PrivateKey privateKey, X509Cer x509data.addCertificate(signingCert); sig.getKeyInfo().add(x509data); - //sig.sign(privateKey); - signatureAlgorithm.doSign(privateKey,sig.getSignedInfo()); + sig.sign(privateKey); + //signatureAlgorithm.doSign(privateKey,sig.getSignedInfo()); ByteArrayOutputStream bos = new ByteArrayOutputStream(); XMLUtils.outputDOMc14nWithComments(doc, bos); diff --git a/src/test/java/com/honey/xmlsec/XPointerResourceResolver.java b/src/test/java/com/honey/xmlsec/XPointerResourceResolver.java new file mode 100644 index 0000000..1ca81a9 --- /dev/null +++ b/src/test/java/com/honey/xmlsec/XPointerResourceResolver.java @@ -0,0 +1,207 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.honey.xmlsec; + +import org.apache.xml.security.signature.XMLSignatureInput; +import org.apache.xml.security.utils.resolver.ResourceResolverContext; +import org.apache.xml.security.utils.resolver.ResourceResolverException; +import org.apache.xml.security.utils.resolver.ResourceResolverSpi; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * An implementation of a resource resolver, which evaluates xpointer expressions. + * + */ +public class XPointerResourceResolver extends ResourceResolverSpi { + private static final org.slf4j.Logger LOG = + org.slf4j.LoggerFactory.getLogger(XPointerResourceResolver.class); + + private static final String XP_OPEN = "xpointer("; + private static final String XNS_OPEN = "xmlns("; + + private Node baseNode; + + public XPointerResourceResolver(Node baseNode) { + this.baseNode = baseNode; + } + + @Override + public boolean engineCanResolveURI(ResourceResolverContext context) { + String v = context.uriToResolve; + + if (v == null || v.length() <= 0) { + return false; + } + + if (v.charAt(0) != '#') { + return false; + } + + String xpURI; + try { + xpURI = URLDecoder.decode(v, "utf-8"); + } catch (UnsupportedEncodingException e) { + LOG.warn("utf-8 not a valid encoding",e); + return false; + } + + String parts[] = xpURI.substring(1).split("\\s"); + + // plain ID reference. + if (parts.length == 1 && !parts[0].startsWith(XNS_OPEN)) { + return true; + } + + int i = 0; + for (; i < parts.length - 1; ++i) { + if (!parts[i].endsWith(")") || !parts[i].startsWith(XNS_OPEN)) { + return false; + } + } + + if (!parts[i].endsWith(")") || !parts[i].startsWith(XP_OPEN)) { + return false; + } + + LOG.debug("xpURI = " + xpURI); + LOG.debug("BaseURI = " + context.baseUri); + + return true; + } + + @Override + public XMLSignatureInput engineResolveURI(ResourceResolverContext context) + throws ResourceResolverException { + String v = context.uriToResolve; + + if (v.charAt(0) != '#') { + return null; + } + + String xpURI; + try { + xpURI = URLDecoder.decode(v, "utf-8"); + } catch (UnsupportedEncodingException e) { + LOG.warn("utf-8 not a valid encoding ", e); + return null; + } + + String parts[] = xpURI.substring(1).split("\\s"); + + int i = 0; + Map namespaces = new HashMap<>(); + + if (parts.length > 1) { + + for (; i < parts.length - 1; ++i) { + if (!parts[i].endsWith(")") || !parts[i].startsWith(XNS_OPEN)) { + return null; + } + + String mapping = parts[i].substring(XNS_OPEN.length(), parts[i].length() - 1); + + int pos = mapping.indexOf('='); + + if (pos <= 0 || pos >= mapping.length() - 1) { + throw new ResourceResolverException( + "malformed namespace part of XPointer expression", context.uriToResolve, context.baseUri + ); + } + + namespaces.put( + mapping.substring(0, pos), + mapping.substring(pos + 1) + ); + } + } + + try { + Node node = null; + NodeList nodes = null; + + // plain ID reference. + if (i == 0 && !parts[i].startsWith(XP_OPEN)) { + node = this.baseNode.getOwnerDocument().getElementById(parts[i]); + } else { + if (!parts[i].endsWith(")") || !parts[i].startsWith(XP_OPEN)) { + return null; + } + + String xpathExpr = parts[i].substring(XP_OPEN.length(), parts[i].length() - 1); + + XPathFactory xpf = XPathFactory.newInstance(); + XPath xpath = xpf.newXPath(); + DSNamespaceContext namespaceContext = + new DSNamespaceContext(namespaces); + xpath.setNamespaceContext(namespaceContext); + + nodes = + (NodeList) xpath.evaluate( + xpathExpr, this.baseNode, XPathConstants.NODESET + ); + + if (nodes.getLength() == 0) { + return null; + } + if (nodes.getLength() == 1) { + node = nodes.item(0); + } + } + + XMLSignatureInput result = null; + + if (node != null) { + result = new XMLSignatureInput(node); + } else if (nodes != null) { + Set nodeSet = new HashSet<>(nodes.getLength()); + + for (int j = 0; j < nodes.getLength(); ++j) { + nodeSet.add(nodes.item(j)); + } + + result = new XMLSignatureInput(nodeSet); + } else { + return null; + } + + result.setMIMEType("text/xml"); + result.setExcludeComments(true); + result.setSourceURI((context.baseUri != null) ? context.baseUri.concat(v) : v); + + return result; + } catch (XPathExpressionException e) { + throw new ResourceResolverException( + e, context.uriToResolve, context.baseUri, "Problem evaluating XPath expression" + ); + } + } + +} diff --git a/src/test/java/com/honey/xmlsec/XmlSecApplicationTests.java b/src/test/java/com/honey/xmlsec/XmlSecApplicationTests.java index f0750c0..6af62ab 100644 --- a/src/test/java/com/honey/xmlsec/XmlSecApplicationTests.java +++ b/src/test/java/com/honey/xmlsec/XmlSecApplicationTests.java @@ -1,16 +1,22 @@ package com.honey.xmlsec; import org.apache.xml.security.Init; +import org.apache.xml.security.keys.KeyInfo; +import org.apache.xml.security.signature.XMLSignature; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.SpringApplication; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; +import org.w3c.dom.Document; +import org.w3c.dom.Element; -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathFactory; +import java.io.*; +import java.nio.charset.Charset; import java.security.*; import java.security.cert.X509Certificate; @@ -29,6 +35,8 @@ public void contextLoads() { private static KeyPair keyPair; static { try { + BouncyCastleProvider provider = new BouncyCastleProvider(); + Security.addProvider(provider); keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); Init.init(); } catch (NoSuchAlgorithmException e) { @@ -79,6 +87,8 @@ public static void main(String[] args) throws Exception{ X509Certificate x509 = (X509Certificate)keyStore.getCertificate("ECDSA"); String signed3 = myUtil.signWithCertEcdsa(body, privateKey2, x509); System.err.println(signed3); + boolean validate3 = myUtil.verify(signed3); + System.err.println(validate3); /** close streams */ fis.close(); //fis2.close(); @@ -99,4 +109,33 @@ public static String getFileContent( return sb.toString(); } } + + private static boolean doVerify(String signedXML) throws Exception { + Document doc = null; + try (InputStream is = new ByteArrayInputStream(signedXML.getBytes(Charset.forName("utf-8")))) { + doc = MyXMLUtils.read(is, false); + } + + XPathFactory xpf = XPathFactory.newInstance(); + XPath xpath = xpf.newXPath(); + xpath.setNamespaceContext(new DSNamespaceContext()); + + String expression = "//ds:Signature[1]"; + Element sigElement = + (Element) xpath.evaluate(expression, doc, XPathConstants.NODE); + XMLSignature signature = new XMLSignature(sigElement, ""); + + signature.addResourceResolver(new XPointerResourceResolver(sigElement)); + + KeyInfo ki = signature.getKeyInfo(); + if (ki == null) { + throw new RuntimeException("No keyinfo"); + } + X509Certificate cert = signature.getKeyInfo().getX509Certificate(); + + if (cert == null) { + throw new RuntimeException("No certificate"); + } + return signature.checkSignatureValue(cert); + } } diff --git a/src/test/resources/ecdsa.jks b/src/test/resources/ecdsa.jks new file mode 100644 index 0000000000000000000000000000000000000000..699e0b7bff01603e8d1f83ae8d431e507c3dc2a8 GIT binary patch literal 850 zcmezO_TO6u1_mY|W&~r_)Z~=nL?F*3nvXdTNb?vpF>)C2v2kg$F|sgfF$pp0cto7HgEk|RFb{jo2`F4B0_x25p zM>~G?Sy(m`zmlT~O*i zm(Q+qr!zHwczp4NO}oFpakb9$lZ*$$*MHW%#4WMm(VR>j)5HT$l?0B*$)8^JVabHk zkRXcCGc~XT1sX7D{s3{^0%j&gCMGsem@!Q=;ACUhYV$Z}%fe_7V5n#y&&C|e!ptKP z;p?N|>g*Eis1Tf)o|jlsT9m2~l3HA1C}tqS#>F{Ft#k4xofJk%F}B2l#N>=rLtz6! z6mjPKqI3f}ab811Ln9*tLjz+=W5XyQ*A$6M0~_lM1fU*bVq|00Ze(FlWzJ$?VX#v! zy)S?I>P_jWC2QnE6vO{9o!VgVCL`zO)b$QmJy+JeubvPdaIH12KP+bR=I@_Ye`siC zblGz7v@+jB#T`l&Z`hxwNpy8;bJnVC=$>?4s#kRCTb-`Sr&Dew-+TD)$zCtxW$Uu< z>Kt9HU?69}0}Ml1VMfOPEKCLrz{CjT@q>6Qz<^(d6yVIc3ee+X)VM~^aGA}FtS$ZE^^+0m(hhVww Up_<3*-