SamlWriter.java 16.5 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  org.joda.time.format.DateTimeFormatter
 */
package com.adobe.granite.auth.saml.util;

import com.adobe.granite.auth.saml.model.AbstractRequest;
import com.adobe.granite.auth.saml.model.AuthnRequest;
import com.adobe.granite.auth.saml.model.Issuer;
import com.adobe.granite.auth.saml.model.LogoutRequest;
import com.adobe.granite.auth.saml.model.LogoutResponse;
import com.adobe.granite.auth.saml.model.Message;
import com.adobe.granite.auth.saml.model.NameIdPolicy;
import com.adobe.granite.auth.saml.model.Status;
import com.adobe.granite.auth.saml.model.xml.SamlXmlConstants;
import com.adobe.granite.auth.saml.util.SamlWriterException;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
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.XMLSignContext;
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.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.DigestMethodParameterSpec;
import javax.xml.crypto.dsig.spec.SignatureMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.joda.time.format.DateTimeFormatter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

public class SamlWriter {
    private static final String DIGEST_METHOD = "http://www.w3.org/2001/04/xmlenc#sha256";
    private static final String SIGNATURE_METHOD = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
    private DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();

    public SamlWriter() {
        this.builderFactory.setNamespaceAware(true);
    }

    public void write(Message message, OutputStream out, Key privateKey) throws SamlWriterException {
        Document requestDoc;
        Transformer trans;
        if (message instanceof AuthnRequest) {
            requestDoc = this.createRequestDocument((AuthnRequest)message, privateKey);
        } else if (message instanceof LogoutRequest) {
            requestDoc = this.createRequestDocument((LogoutRequest)message, privateKey);
        } else if (message instanceof LogoutResponse) {
            requestDoc = this.createResponseDocument((LogoutResponse)message, privateKey);
        } else {
            throw new RuntimeException("Messages of type " + message.getClass().getName() + " are not supported yet.");
        }
        TransformerFactory transfac = TransformerFactory.newInstance();
        try {
            trans = transfac.newTransformer();
        }
        catch (TransformerConfigurationException e) {
            throw new SamlWriterException("Unable to create a new Transformer instance", e);
        }
        trans.setOutputProperty("indent", "no");
        StreamResult result = new StreamResult(out);
        DOMSource source = new DOMSource(requestDoc);
        try {
            trans.transform(source, result);
        }
        catch (TransformerException e) {
            throw new SamlWriterException("An error occurred writing xml to output stream", e);
        }
    }

    protected Document createResponseDocument(LogoutResponse response, Key privateKey) throws SamlWriterException {
        DocumentBuilder builder;
        try {
            builder = this.builderFactory.newDocumentBuilder();
        }
        catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        }
        Document document = builder.newDocument();
        this.createLogoutResponseElement(response, document, privateKey);
        return document;
    }

    protected Document createRequestDocument(AuthnRequest request, Key privateKey) throws SamlWriterException {
        DocumentBuilder builder;
        try {
            builder = this.builderFactory.newDocumentBuilder();
        }
        catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        }
        Document document = builder.newDocument();
        this.createAuthnRequestElement(request, document, privateKey);
        return document;
    }

    protected Document createRequestDocument(LogoutRequest request, Key privateKey) throws SamlWriterException {
        DocumentBuilder builder;
        try {
            builder = this.builderFactory.newDocumentBuilder();
        }
        catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        }
        Document document = builder.newDocument();
        this.createLogoutRequestElement(request, document, privateKey);
        return document;
    }

    protected void createLogoutResponseElement(LogoutResponse logoutResponse, Node parent, Key privateKey) throws SamlWriterException {
        Document ownerDocument = parent instanceof Document ? (Document)parent : parent.getOwnerDocument();
        Element logoutResponseElement = ownerDocument.createElementNS("urn:oasis:names:tc:SAML:2.0:protocol", "samlp:LogoutResponse");
        logoutResponseElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol");
        logoutResponseElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion");
        if (logoutResponse.getId() != null) {
            logoutResponseElement.setAttribute("ID", logoutResponse.getId());
            logoutResponseElement.setIdAttribute("ID", true);
        }
        if (logoutResponse.getVersion() != null) {
            logoutResponseElement.setAttribute("Version", logoutResponse.getVersion());
        }
        if (logoutResponse.getIssueInstant() != null) {
            logoutResponseElement.setAttribute("IssueInstant", SamlXmlConstants.XML_DATE_FORMATTER.print(logoutResponse.getIssueInstant().getTimeInMillis()));
        }
        if (logoutResponse.getDestination() != null) {
            logoutResponseElement.setAttribute("Destination", logoutResponse.getDestination());
        }
        if (logoutResponse.getIssuer() != null) {
            this.createIssuerElement(logoutResponse.getIssuer(), logoutResponseElement);
        }
        Element statusElement = null;
        if (logoutResponse.getStatus() != null) {
            statusElement = this.createStatusElement(logoutResponse.getStatus(), logoutResponseElement);
        }
        parent.appendChild(logoutResponseElement);
        this.signDocument(ownerDocument, statusElement, privateKey, logoutResponse.getId());
    }

    protected Element createStatusElement(Status status, Element parent) {
        Document ownerDoc = parent.getOwnerDocument();
        Element statusElement = ownerDoc.createElementNS("urn:oasis:names:tc:SAML:2.0:protocol", "samlp:Status");
        statusElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol");
        Element statusCodeElement = ownerDoc.createElementNS("urn:oasis:names:tc:SAML:2.0:protocol", "samlp:StatusCode");
        statusCodeElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol");
        statusCodeElement.setAttribute("Value", status.getStatusCode());
        statusElement.appendChild(statusCodeElement);
        parent.appendChild(statusElement);
        return statusElement;
    }

    protected void createAuthnRequestElement(AuthnRequest authnRequest, Node parent, Key privateKey) throws SamlWriterException {
        Document ownerDocument = parent instanceof Document ? (Document)parent : parent.getOwnerDocument();
        Element authnRequestElement = ownerDocument.createElementNS("urn:oasis:names:tc:SAML:2.0:protocol", "samlp:AuthnRequest");
        authnRequestElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol");
        parent.appendChild(authnRequestElement);
        this.handleAbstractRequest(authnRequestElement, authnRequest);
        if (authnRequest.hasAssertionConsumerServiceURL()) {
            authnRequestElement.setAttribute("AssertionConsumerServiceURL", authnRequest.getAssertionConsumerServiceUrl());
        } else if (authnRequest.hasAssertionConsumerServiceIndex()) {
            authnRequestElement.setAttribute("AssertionConsumerServiceIndex", authnRequest.getAssertionConsumerServiceIndex());
        }
        if (authnRequest.hasProtocolBinding()) {
            authnRequestElement.setAttribute("ProtocolBinding", authnRequest.getProtocolBinding());
        }
        if (authnRequest.hasIssuer()) {
            this.createIssuerElement(authnRequest.getIssuer(), authnRequestElement);
        }
        Node nameIdPolicyNode = null;
        if (authnRequest.hasNameIdPolicy()) {
            nameIdPolicyNode = this.createNameIdPolicyElement(authnRequest.getNameIdPolicy(), authnRequestElement);
        }
        this.signDocument(ownerDocument, nameIdPolicyNode, privateKey, authnRequest.getId());
    }

    protected void createIssuerElement(Issuer issuer, Node parent) {
        Document ownerDoc = parent.getOwnerDocument();
        Element issuerElement = ownerDoc.createElementNS("urn:oasis:names:tc:SAML:2.0:assertion", "saml:Issuer");
        issuerElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion");
        parent.appendChild(issuerElement);
        issuerElement.setTextContent(issuer.getValue());
    }

    protected Node createNameIdPolicyElement(NameIdPolicy nameIdPolicy, Node parent) {
        Document ownerDoc = parent.getOwnerDocument();
        Element nameIdPolicyElement = ownerDoc.createElementNS("urn:oasis:names:tc:SAML:2.0:protocol", "samlp:NameIDPolicy");
        nameIdPolicyElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol");
        if (nameIdPolicy.hasFormat()) {
            nameIdPolicyElement.setAttribute("Format", nameIdPolicy.getFormat());
        }
        if (nameIdPolicy.hasAllowCreate()) {
            nameIdPolicyElement.setAttribute("AllowCreate", Boolean.toString(nameIdPolicy.isAllowCreate()));
        }
        if (nameIdPolicy.hasSpNameQualifier()) {
            nameIdPolicyElement.setAttribute("SPNameQualifier", nameIdPolicy.getSpNameQualifier());
        }
        parent.appendChild(nameIdPolicyElement);
        return nameIdPolicyElement;
    }

    protected void handleAbstractRequest(Element requestElement, AbstractRequest request) {
        requestElement.setAttribute("Version", request.getVersion());
        requestElement.setAttribute("ID", request.getId());
        requestElement.setIdAttribute("ID", true);
        requestElement.setAttribute("IssueInstant", SamlXmlConstants.XML_DATE_FORMATTER.print(request.getIssueInstant().getTimeInMillis()));
        if (request.hasConsent()) {
            requestElement.setAttribute("Consent", request.getConsent());
        }
        if (request.hasDestination()) {
            requestElement.setAttribute("Destination", request.getDestination());
        }
    }

    protected void createLogoutRequestElement(LogoutRequest request, Node parent, Key privateKey) throws SamlWriterException {
        Document ownerDocument = parent instanceof Document ? (Document)parent : parent.getOwnerDocument();
        Element requestElement = ownerDocument.createElementNS("urn:oasis:names:tc:SAML:2.0:protocol", "samlp:LogoutRequest");
        requestElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol");
        parent.appendChild(requestElement);
        this.handleAbstractRequest(requestElement, request);
        Element issuerElement = ownerDocument.createElementNS("urn:oasis:names:tc:SAML:2.0:assertion", "saml:Issuer");
        issuerElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion");
        issuerElement.setTextContent(request.getIssuer().getValue());
        requestElement.appendChild(issuerElement);
        Element nameIdElement = null;
        if (request.getNameId() != null) {
            nameIdElement = ownerDocument.createElementNS("urn:oasis:names:tc:SAML:2.0:assertion", "saml:NameID");
            nameIdElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion");
            nameIdElement.setTextContent(request.getNameId().toString());
            if (request.getNameIdFormat() != null) {
                nameIdElement.setAttribute("Format", request.getNameIdFormat());
            }
            if (request.getNameQualifier() != null) {
                nameIdElement.setAttribute("NameQualifier", request.getNameQualifier());
            }
            if (request.getSpNameQualifier() != null) {
                nameIdElement.setAttribute("SPNameQualifier", request.getSpNameQualifier());
            }
            requestElement.appendChild(nameIdElement);
        }
        for (String sessionIndex : request.getSessionIndices()) {
            Element sessionIndexElement = ownerDocument.createElementNS("urn:oasis:names:tc:SAML:2.0:protocol", "samlp:SessionIndex");
            sessionIndexElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol");
            sessionIndexElement.setTextContent(sessionIndex);
            requestElement.appendChild(sessionIndexElement);
        }
        this.signDocument(ownerDocument, nameIdElement, privateKey, request.getId());
    }

    protected void signDocument(Document ownerDocument, Node placeSignatureBefore, Key privateKey, String messageId) throws SamlWriterException {
        if (privateKey != null) {
            DOMSignContext domSignContext = null;
            domSignContext = placeSignatureBefore != null ? new DOMSignContext(privateKey, (Node)ownerDocument.getDocumentElement(), placeSignatureBefore) : new DOMSignContext(privateKey, (Node)ownerDocument.getDocumentElement());
            XMLSignatureFactory signatureFactory = XMLSignatureFactory.getInstance("DOM");
            try {
                LinkedList<Transform> transforms = new LinkedList<Transform>();
                transforms.add(signatureFactory.newTransform("http://www.w3.org/2000/09/xmldsig#enveloped-signature", (TransformParameterSpec)null));
                transforms.add(signatureFactory.newTransform("http://www.w3.org/2001/10/xml-exc-c14n#", (TransformParameterSpec)null));
                Reference reference = signatureFactory.newReference("#" + messageId, signatureFactory.newDigestMethod("http://www.w3.org/2001/04/xmlenc#sha256", null), transforms, null, null);
                SignedInfo signedInfo = signatureFactory.newSignedInfo(signatureFactory.newCanonicalizationMethod("http://www.w3.org/2001/10/xml-exc-c14n#", (C14NMethodParameterSpec)null), signatureFactory.newSignatureMethod("http://www.w3.org/2000/09/xmldsig#rsa-sha1", null), Collections.singletonList(reference));
                XMLSignature xmlSignature = signatureFactory.newXMLSignature(signedInfo, null);
                xmlSignature.sign(domSignContext);
            }
            catch (XMLSignatureException e) {
                throw new SamlWriterException("XMLSignature exception while signing document.", e);
            }
            catch (MarshalException e) {
                throw new SamlWriterException("MarshalException while signing document.", e);
            }
            catch (NoSuchAlgorithmException e) {
                throw new SamlWriterException("Signature Algorithm not available.", e);
            }
            catch (InvalidAlgorithmParameterException e) {
                throw new SamlWriterException("Invalid parameter for signature algorithm.", e);
            }
        }
    }
}