be.e_contract.mycarenet.sts.RequestFactory.java Source code

Java tutorial

Introduction

Here is the source code for be.e_contract.mycarenet.sts.RequestFactory.java

Source

/*
 * Java MyCareNet Project.
 * Copyright (C) 2013 e-Contract.be BVBA.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, see 
 * http://www.gnu.org/licenses/.
 */

package be.e_contract.mycarenet.sts;

import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;

import javax.security.auth.x500.X500Principal;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.crypto.MarshalException;
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.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.joda.time.DateTime;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import be.e_contract.mycarenet.jaxb.saml.AssertionType;
import be.e_contract.mycarenet.jaxb.saml.AttributeDesignatorType;
import be.e_contract.mycarenet.jaxb.saml.AttributeStatementType;
import be.e_contract.mycarenet.jaxb.saml.AttributeType;
import be.e_contract.mycarenet.jaxb.saml.ConditionsType;
import be.e_contract.mycarenet.jaxb.saml.NameIdentifierType;
import be.e_contract.mycarenet.jaxb.saml.ObjectFactory;
import be.e_contract.mycarenet.jaxb.saml.SubjectConfirmationType;
import be.e_contract.mycarenet.jaxb.saml.SubjectType;
import be.e_contract.mycarenet.jaxb.samlp.AttributeQueryType;
import be.e_contract.mycarenet.jaxb.samlp.RequestType;
import be.e_contract.mycarenet.jaxb.xmldsig.KeyInfoType;
import be.e_contract.mycarenet.jaxb.xmldsig.X509DataType;

/**
 * Factory for SAML Request.
 * <p/>
 * We don't use OpenSAML here as this conflicts with the JBoss CXF runtime.
 * 
 * @author Frank Cornelis
 * 
 */
public class RequestFactory {

    private final ObjectFactory samlObjectFactory;
    private final be.e_contract.mycarenet.jaxb.samlp.ObjectFactory samlpObjectFactory;
    private final be.e_contract.mycarenet.jaxb.xmldsig.ObjectFactory xmldsigObjectFactory;
    private final Marshaller marshaller;
    private final DocumentBuilder documentBuilder;
    private final DatatypeFactory datatypeFactory;

    public RequestFactory() {
        this.samlObjectFactory = new ObjectFactory();
        this.samlpObjectFactory = new be.e_contract.mycarenet.jaxb.samlp.ObjectFactory();
        this.xmldsigObjectFactory = new be.e_contract.mycarenet.jaxb.xmldsig.ObjectFactory();

        try {
            JAXBContext jaxbContext = JAXBContext
                    .newInstance(be.e_contract.mycarenet.jaxb.samlp.ObjectFactory.class);
            this.marshaller = jaxbContext.createMarshaller();
        } catch (JAXBException e) {
            throw new RuntimeException("JAXB error: " + e.getMessage(), e);
        }

        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setNamespaceAware(true);
        try {
            this.documentBuilder = documentBuilderFactory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            throw new RuntimeException("DOM error: " + e.getMessage(), e);
        }

        try {
            this.datatypeFactory = DatatypeFactory.newInstance();
        } catch (DatatypeConfigurationException e) {
            throw new RuntimeException(e);
        }
    }

    private RequestType createRequest() {
        RequestType request = this.samlpObjectFactory.createRequestType();
        String requestId = "request-" + UUID.randomUUID().toString();
        request.setRequestID(requestId);
        request.setMajorVersion(BigInteger.ONE);
        request.setMinorVersion(BigInteger.ONE);
        DateTime now = new DateTime();
        request.setIssueInstant(toXMLGregorianCalendar(now));
        return request;
    }

    private AttributeQueryType createAttributeQuery(RequestType request) {
        AttributeQueryType attributeQuery = this.samlpObjectFactory.createAttributeQueryType();
        request.setAttributeQuery(attributeQuery);
        return attributeQuery;
    }

    private SubjectType createSubject(AttributeQueryType attributeQuery) {
        SubjectType subject = this.samlObjectFactory.createSubjectType();
        attributeQuery.setSubject(subject);
        return subject;
    }

    private NameIdentifierType createNameIdentifier(SubjectType subject, X509Certificate authnCertificate) {
        NameIdentifierType nameIdentifier = this.samlObjectFactory.createNameIdentifierType();
        nameIdentifier.setFormat("urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName");
        nameIdentifier.setNameQualifier(authnCertificate.getIssuerX500Principal().getName("RFC1779"));
        nameIdentifier.setValue(authnCertificate.getSubjectX500Principal().getName("RFC1779"));
        subject.getContent().add(this.samlObjectFactory.createNameIdentifier(nameIdentifier));
        return nameIdentifier;
    }

    private SubjectConfirmationType createSubjectConfirmation(SubjectType subject) {
        SubjectConfirmationType subjectConfirmation = this.samlObjectFactory.createSubjectConfirmationType();
        subjectConfirmation.getConfirmationMethod().add("urn:oasis:names:tc:SAML:1.0:cm:holder-of-key");
        subject.getContent().add(this.samlObjectFactory.createSubjectConfirmation(subjectConfirmation));
        return subjectConfirmation;
    }

    private XMLGregorianCalendar toXMLGregorianCalendar(DateTime date) {
        GregorianCalendar calendar = new GregorianCalendar();
        calendar.setTime(date.toDate());
        calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
        return this.datatypeFactory.newXMLGregorianCalendar(calendar);
    }

    private void createSubject(AttributeStatementType attributeStatement, X509Certificate authnCertificate) {
        SubjectType subject = this.samlObjectFactory.createSubjectType();
        attributeStatement.setSubject(subject);
        createNameIdentifier(subject, authnCertificate);
    }

    private void createAttribute(AttributeStatementType attributeStatement, String namespace, String name,
            String value) {
        AttributeType attribute = this.samlObjectFactory.createAttributeType();
        attributeStatement.getAttribute().add(attribute);
        attribute.setAttributeNamespace(namespace);
        attribute.setAttributeName(name);
        attribute.getAttributeValue().add(value);
    }

    private void createAttributeStatement(AssertionType assertion, X509Certificate authnCertificate,
            List<Attribute> attributes) {
        AttributeStatementType attributeStatement = this.samlObjectFactory.createAttributeStatementType();
        assertion.getStatementOrSubjectStatementOrAuthenticationStatement().add(attributeStatement);
        createSubject(attributeStatement, authnCertificate);

        for (Attribute attribute : attributes) {
            String attributeValue;
            if (attribute.isInjectNRNValue()) {
                attributeValue = getUserIdentifier(authnCertificate);
            } else {
                attributeValue = attribute.getValue();
            }
            createAttribute(attributeStatement, attribute.getNamespace(), attribute.getName(), attributeValue);
        }
    }

    private String getUserIdentifier(X509Certificate certificate) {
        X500Principal userPrincipal = certificate.getSubjectX500Principal();
        String name = userPrincipal.toString();
        int serialNumberBeginIdx = name.indexOf("SERIALNUMBER=");
        if (-1 == serialNumberBeginIdx) {
            throw new SecurityException("SERIALNUMBER not found in X509 CN");
        }
        int serialNumberValueBeginIdx = serialNumberBeginIdx + "SERIALNUMBER=".length();
        int serialNumberValueEndIdx = name.indexOf(",", serialNumberValueBeginIdx);
        if (-1 == serialNumberValueEndIdx) {
            serialNumberValueEndIdx = name.length();
        }
        String userId = name.substring(serialNumberValueBeginIdx, serialNumberValueEndIdx);
        return userId;
    }

    private void createConditions(AssertionType assertion) {
        ConditionsType conditions = this.samlObjectFactory.createConditionsType();
        DateTime notBefore = new DateTime();
        conditions.setNotBefore(toXMLGregorianCalendar(notBefore));
        DateTime notAfter = notBefore.plusHours(24);
        conditions.setNotOnOrAfter(toXMLGregorianCalendar(notAfter));
        assertion.setConditions(conditions);
    }

    private AssertionType createAssertion(X509Certificate authnCertificate, List<Attribute> attributes) {
        AssertionType assertion = this.samlObjectFactory.createAssertionType();
        String assertionId = "assertion-" + UUID.randomUUID().toString();
        assertion.setAssertionID(assertionId);
        assertion.setMajorVersion(BigInteger.ONE);
        assertion.setMinorVersion(BigInteger.ONE);
        DateTime now = new DateTime();
        assertion.setIssueInstant(toXMLGregorianCalendar(now));
        assertion.setIssuer(authnCertificate.getSubjectX500Principal().getName("RFC1779"));
        createConditions(assertion);
        createAttributeStatement(assertion, authnCertificate, attributes);
        return assertion;
    }

    private void createSubjectConfirmationData(SubjectConfirmationType subjectConfirmation,
            X509Certificate authnCertificate, List<Attribute> attributes) {
        Document document = this.documentBuilder.newDocument();
        document.appendChild(
                document.createElementNS("urn:oasis:names:tc:SAML:1.0:assertion", "SubjectConfirmationData"));
        AssertionType assertion = createAssertion(authnCertificate, attributes);
        try {
            this.marshaller.marshal(this.samlObjectFactory.createAssertion(assertion),
                    document.getDocumentElement());
        } catch (JAXBException e) {
            throw new RuntimeException("JAXB error: " + e.getMessage(), e);
        }
        subjectConfirmation.setSubjectConfirmationData(document.getDocumentElement());
    }

    private void createX509Data(KeyInfoType keyInfo, X509Certificate hokCertificate) {
        X509DataType x509Data = this.xmldsigObjectFactory.createX509DataType();
        keyInfo.getContent().add(this.xmldsigObjectFactory.createX509Data(x509Data));
        try {
            x509Data.getX509IssuerSerialOrX509SKIOrX509SubjectName()
                    .add(this.xmldsigObjectFactory.createX509DataTypeX509Certificate(hokCertificate.getEncoded()));
        } catch (CertificateEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    private void createKeyInfo(SubjectConfirmationType subjectConfirmation, X509Certificate hokCertificate) {
        KeyInfoType keyInfo = this.xmldsigObjectFactory.createKeyInfoType();
        subjectConfirmation.setKeyInfo(keyInfo);
        createX509Data(keyInfo, hokCertificate);
    }

    private void createAttributeDesignator(AttributeQueryType attributeQuery, String namespace, String name) {
        AttributeDesignatorType attributeDesignator = this.samlObjectFactory.createAttributeDesignatorType();
        attributeDesignator.setAttributeNamespace(namespace);
        attributeDesignator.setAttributeName(name);
        attributeQuery.getAttributeDesignator().add(attributeDesignator);
    }

    private void signRequest(Element requestElement, PrivateKey privateKey, X509Certificate certificate)
            throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, MarshalException,
            XMLSignatureException {
        DOMSignContext domSignContext = new DOMSignContext(privateKey, requestElement,
                requestElement.getFirstChild());
        XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance("DOM");

        String requestId = requestElement.getAttribute("RequestID");
        requestElement.setIdAttribute("RequestID", true);

        List<Transform> transforms = new LinkedList<Transform>();
        transforms.add(xmlSignatureFactory.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null));
        transforms.add(
                xmlSignatureFactory.newTransform(CanonicalizationMethod.EXCLUSIVE, (C14NMethodParameterSpec) null));
        Reference reference = xmlSignatureFactory.newReference("#" + requestId,
                xmlSignatureFactory.newDigestMethod(DigestMethod.SHA1, null), transforms, null, null);

        SignedInfo signedInfo = xmlSignatureFactory.newSignedInfo(
                xmlSignatureFactory.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE,
                        (C14NMethodParameterSpec) null),
                xmlSignatureFactory.newSignatureMethod(SignatureMethod.RSA_SHA1, null),
                Collections.singletonList(reference));

        KeyInfoFactory keyInfoFactory = xmlSignatureFactory.getKeyInfoFactory();
        KeyInfo keyInfo = keyInfoFactory.newKeyInfo(
                Collections.singletonList(keyInfoFactory.newX509Data(Collections.singletonList(certificate))));

        XMLSignature xmlSignature = xmlSignatureFactory.newXMLSignature(signedInfo, keyInfo);
        xmlSignature.sign(domSignContext);
    }

    /**
     * Creates a SAML Request DOM element.
     * 
     * @param authnCertificate
     *            the eID authentication certificate.
     * @param hokPrivateKey
     *            the eHealth holder-of-key authentication private key.
     * @param hokCertificate
     *            the eHealth holder-of-key authentication certificate.
     * @param attributes
     *            the identity attributes.
     * @param attributeDesignators
     *            the required attributes.
     * @return a DOM element containing the signed SAML Request.
     */
    public Element createRequest(X509Certificate authnCertificate, PrivateKey hokPrivateKey,
            X509Certificate hokCertificate, List<Attribute> attributes,
            List<AttributeDesignator> attributeDesignators) {
        RequestType request = createRequest();
        AttributeQueryType attributeQuery = createAttributeQuery(request);
        SubjectType subject = createSubject(attributeQuery);
        createNameIdentifier(subject, authnCertificate);
        SubjectConfirmationType subjectConfirmation = createSubjectConfirmation(subject);
        createSubjectConfirmationData(subjectConfirmation, authnCertificate, attributes);
        createKeyInfo(subjectConfirmation, hokCertificate);

        for (AttributeDesignator attributeDesignator : attributeDesignators) {
            createAttributeDesignator(attributeQuery, attributeDesignator.getNamespace(),
                    attributeDesignator.getName());
        }

        Element requestElement = toDOM(request);
        try {
            signRequest(requestElement, hokPrivateKey, hokCertificate);
        } catch (Exception e) {
            throw new RuntimeException("XML signature error: " + e.getMessage(), e);
        }
        return requestElement;
    }

    private Element toDOM(RequestType request) {
        Document document = this.documentBuilder.newDocument();
        try {
            this.marshaller.marshal(this.samlpObjectFactory.createRequest(request), document);
        } catch (JAXBException e) {
            throw new RuntimeException("JAXB error: " + e.getMessage(), e);
        }
        return document.getDocumentElement();
    }
}