Java xml-dsig和命名空间的问题

时间:2016-03-03 09:17:28

标签: java xml cryptography xml-signature xml-dsig

我一直在尝试使用JDK的javax.xml.crypto.dsig包来使用XML签名。

当我没有任何名称空间时,它会起作用,但只要我添加名称空间,然后签名和验证就会在内存中的DOM对象上运行。一旦对象被转换然后再次解析,验证就会失败。

我已经创建了一个可以轻松看到的示例。在main方法中,在创建Document之后,我必须转换然后解析文档,在这种情况下签名和验证工作。这似乎真的很愚蠢=))

所以我的问题:有没有人知道我如何修复代码所以我不需要先将Document转换为字节然后再解析它们,以便签名验证有效? / p>

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;

import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class Test {

    public static void main(String[] args) throws Exception {

        System.out.println("Creating doc...");
        Document doc = createDoc();

        // AFTER creating the document, i must immediately 
        // transform and parse the document, to have xml 
        // signing and validation work:
        doc = parse(transform(doc));
        System.out.println();
        System.out.println("Initial unsigned document:");
        System.out.println();
        dumpDocument(doc);
        System.out.println();

        System.out.println("Signing...");
        sign(doc);
        validate(doc);
        System.out.println();
        System.out.println("Initial signed and validated document:");
        System.out.println();
        dumpDocument(doc);
        System.out.println();

        // transform
        System.out.println("Transforming...");
        byte[] bytes = transform(doc);
        System.out.println("Transformed document:");
        System.out.println();
        System.out.println(new String(bytes, "UTF-8"));
        System.out.println();

        // now parse
        System.out.println("Parsing...");
        doc = parse(bytes);
        System.out.println();
        System.out.println("After parsing:");
        System.out.println();
        //dumpDocument(doc);

        System.out.println("Validating...");
        validate(doc);
    }

    private static void dumpDocument(Node root) throws TransformerException {
        Transformer transformer = TransformerFactory.newInstance().newTransformer();
        transformer.transform(new DOMSource(root), new StreamResult(System.out));
    }

    public static byte[] transform(Document doc) throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        transformer.transform(new DOMSource(doc), new StreamResult(out));

        return out.toByteArray();
    }

    public static Document parse(byte[] bytes) throws Exception {

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        Document doc = dbf.newDocumentBuilder().parse(new ByteArrayInputStream(bytes));

        return doc;
    }

    public static Document createDoc() throws Exception {

        String issuer = "test";
        String destination = "test";
        String assertionConsumerServiceUrl = "test";
        Calendar issueInstant = Calendar.getInstance();

        // create dates
        SimpleDateFormat simpleDf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        simpleDf.setTimeZone(TimeZone.getTimeZone("UTC"));
        String issueInstantS = simpleDf.format(issueInstant.getTime());
        String notBeforeS = issueInstantS;
        issueInstant.add(Calendar.SECOND, 10);
        String notOnOrAfterS = simpleDf.format(issueInstant.getTime());

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        DocumentBuilder docBuilder = dbf.newDocumentBuilder();
        Document doc = docBuilder.newDocument();

        Element authnReqE = doc.createElementNS("urn:oasis:names:tc:SAML:2.0:protocol", "AuthnRequest");
        //Element authnReqE = doc.createElement("AuthnRequest");
        authnReqE.setPrefix("samlp");
        authnReqE.setAttribute("Version", "2.0");
        authnReqE.setAttribute("IssueInstant", issueInstantS);
        authnReqE.setAttribute("ProtocolBinding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
        authnReqE.setAttribute("AssertionConsumerServiceURL", assertionConsumerServiceUrl);
        authnReqE.setAttribute("Destination", destination);
        doc.appendChild(authnReqE);

        Element issuerE = doc.createElementNS("urn:oasis:names:tc:SAML:2.0:assertion", "Issuer");
        //Element issuerE = doc.createElement("Issuer");
        issuerE.setPrefix("saml");
        issuerE.setTextContent(issuer);
        authnReqE.appendChild(issuerE);

        Element conditionsE = doc.createElementNS("urn:oasis:names:tc:SAML:2.0:assertion", "Conditions");
        //Element conditionsE = doc.createElement("Conditions");
        conditionsE.setPrefix("saml");
        conditionsE.setAttribute("NotBefore", notBeforeS);
        conditionsE.setAttribute("NotOnOrAfter", notOnOrAfterS);
        authnReqE.appendChild(conditionsE);

        return doc;
    }

    public static void sign(Document doc) throws Exception {

        String id = "Signed_" + UUID.randomUUID().toString();
        Element rootElement = doc.getDocumentElement();
        rootElement.setAttribute("ID", id);

        // Create a DOM XMLSignatureFactory that will be used to
        // generate the enveloped signature.
        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

        // Create a Reference to the enveloped document (in this case,
        // you are signing the whole document, so a URI of "" signifies
        // that, and also specify the SHA1 digest algorithm and
        // the ENVELOPED Transform.
        List<Transform> transforms = new ArrayList<>();
        transforms.add(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null));
        transforms.add(fac.newTransform(CanonicalizationMethod.EXCLUSIVE, (TransformParameterSpec) null));
        DigestMethod digestMethod = fac.newDigestMethod(DigestMethod.SHA1, null);
        Reference ref = fac.newReference("#" + id, digestMethod, transforms, null, null);
        //Reference ref = fac.newReference("", digestMethod, transforms, null, null);

        // Create the SignedInfo.
        SignedInfo signedInfo = fac.newSignedInfo(
                fac.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE, (C14NMethodParameterSpec) null), //
                fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null), //
                Collections.singletonList(ref));

        // Load the KeyStore and get the signing key and certificate.
        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(new FileInputStream("KeyStore_client.jks"), "changeit".toCharArray());
        KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(ks.aliases().nextElement(),
                new KeyStore.PasswordProtection("changeit".toCharArray()));
        X509Certificate cert = (X509Certificate) keyEntry.getCertificate();

        // Create the KeyInfo containing the X509Data.
        KeyInfoFactory kif = fac.getKeyInfoFactory();
        List<Object> x509Content = new ArrayList<>();
        x509Content.add(cert.getSubjectX500Principal().getName());
        x509Content.add(cert);
        X509Data xd = kif.newX509Data(x509Content);
        KeyInfo keyInfo = kif.newKeyInfo(Collections.singletonList(xd));

        // Create a DOMSignContext and specify the RSA PrivateKey and
        // location of the resulting XMLSignature's parent element.
        DOMSignContext dsc = new DOMSignContext(keyEntry.getPrivateKey(), rootElement);
        //dsc.setDefaultNamespacePrefix("samlp");
        dsc.putNamespacePrefix(XMLSignature.XMLNS, "ds");
        dsc.setIdAttributeNS(rootElement, null, "ID");

        // Create the XMLSignature, but don't sign it yet.
        XMLSignature signature = fac.newXMLSignature(signedInfo, keyInfo);

        // Marshal, generate, and sign the enveloped signature.
        signature.sign(dsc);
    }

    public static void validate(Document doc) throws Exception {

        // Create a DOM XMLSignatureFactory that will be used to
        // generate the enveloped signature.
        XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

        // Find Signature element.
        NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
        if (nl.getLength() == 0) {
            throw new Exception("Cannot find Signature element!");
        } else if (nl.getLength() > 1) {
            throw new Exception("Found multiple Signature elements!");
        }

        // Load the KeyStore and get the signing key and certificate.
        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(new FileInputStream("KeyStore_client.jks"), "changeit".toCharArray());
        KeyStore.PrivateKeyEntry entry = (PrivateKeyEntry) ks.getEntry(ks.aliases().nextElement(),
                new KeyStore.PasswordProtection("changeit".toCharArray()));
        PublicKey publicKey = entry.getCertificate().getPublicKey();

        // Create a DOMValidateContext and specify a KeySelector
        // and document context.
        Node signatureNode = nl.item(0);
        DOMValidateContext valContext = new DOMValidateContext(publicKey, signatureNode);
        valContext.setDefaultNamespacePrefix("samlp");
        valContext.putNamespacePrefix(XMLSignature.XMLNS, "ds");
        valContext.putNamespacePrefix("urn:oasis:names:tc:SAML:2.0:protocol", "samlp");
        valContext.putNamespacePrefix("urn:oasis:names:tc:SAML:2.0:assertion", "saml");
        valContext.setIdAttributeNS(doc.getDocumentElement(), null, "ID");

        // Unmarshal the XMLSignature.
        valContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
        XMLSignature signature = fac.unmarshalXMLSignature(valContext);

        // Validate the XMLSignature.
        boolean coreValidity = signature.validate(valContext);

        // Check core validation status.
        if (coreValidity) {
            System.out.println("Signature passed core validation");
        } else {
            System.err.println("Signature failed core validation");
            boolean sv = signature.getSignatureValue().validate(valContext);
            System.out.println("signature validation status: " + sv);
            if (!sv) {
                // Check the validation status of each Reference.
                Iterator<?> i = signature.getSignedInfo().getReferences().iterator();
                for (int j = 0; i.hasNext(); j++) {
                    boolean refValid = ((Reference) i.next()).validate(valContext);
                    System.out.println("ref[" + j + "] validity status: " + refValid);
                }
            }
            throw new RuntimeException("Uh-oh, failed!");
        }
    }
}

0 个答案:

没有答案