ch.swing.gridcertlib.SLCSRequestor.java Source code

Java tutorial

Introduction

Here is the source code for ch.swing.gridcertlib.SLCSRequestor.java

Source

/**
 * @file SLCSRequestor.java
 * @author peter.kunszt@systemsx.ch, riccardo.murri@gmail.com, valery.tschopp@switch.ch
 *
 * Source code for class SLCSRequestor; part of this code imported
 * originally from the {@link org.glite.slcs.ui} package.
 *
 */
/* 
 * Copyright (c) 2010, ETH Zurich and University of Zurich.  All rights reserved.
 * 
 * This file is part of the GriCertLib software project.
 * You may copy, distribute and modify this file under the terms of
 * the LICENSE.txt file at the root of the project directory tree.
 *
 * $Id$
 */

package ch.swing.gridcertlib;

import ch.SWITCH.aai.idwsf.ecp.DelegationContext;
import ch.SWITCH.aai.idwsf.ecp.DelegationException;
import ch.SWITCH.aai.idwsf.ecp.ECPException;
import ch.SWITCH.aai.idwsf.ecp.WebServiceClient;
import ch.SWITCH.aai.idwsf.token.SAML2AssertionURLResolver;
import ch.SWITCH.aai.idwsf.token.TokenResolverException;
import ch.SWITCH.httpclient.tls.PEMTLSCredentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.opensaml.saml2.core.Assertion;
import org.glite.slcs.AuthException;
import org.glite.slcs.SLCSException;
import org.glite.slcs.ServiceException;
import org.glite.slcs.jericho.html.Element;
import org.glite.slcs.jericho.html.Source;
import org.glite.slcs.pki.CertificateExtension;
import org.glite.slcs.pki.CertificateExtensionFactory;
import org.glite.slcs.pki.CertificateKeys;
import org.glite.slcs.pki.CertificateRequest;
import org.glite.slcs.pki.Certificate;
import org.glite.slcs.pki.bouncycastle.Codec;

import org.joda.time.DateTime;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.security.cert.X509Certificate;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * Utility class to perform all steps to generate a SLCS certificate.
 * <p>
 * Methods in this class cover all steps of the procedure to obtain a
 * SLCS certificate.
 * <p>
 * Example usage: <code>
 * SCLSRequestor slcs = new SLCSRequestor(samlAssertionUrl);
 * slcs.login();
 * slcs.generateCertificateKeys(password);
 * slcs.generateCertificateRequest();
 * slcs.requestSlcsCertificate();
 * </code>
 * The {@link #performSlcsInit} method is provided to perform all of the above in one go.
 */
class SLCSRequestor {

    /**
     * Logging
     */
    static Logger LOG = LoggerFactory.getLogger(SLCSRequestor.class);
    /**
     * Default number of backup file to keep
     */
    static int MAX_BACKUP = 3;
    /**
     * Delegation Context
     */
    private DelegationContext context_ = null;
    /**
     * Web Service Client
     */
    private WebServiceClient wsc_ = null;
    /**
     * Authorization Token
     */
    private String authorizationToken_ = null;
    /**
     * URL to post certificate requests
     */
    private String certificateRequestUrl_ = null;
    /**
     * Certificate subject
     */
    private String certificateSubject_ = null;
    /**
     * List of required certificate extensions
     */
    private List<CertificateExtension> certificateExtensions_ = null;
    /**
     * Private key size
     */
    private int keySize_ = 1024;
    /**
     * Private and public keys
     */
    private CertificateKeys certificateKeys_ = null;
    /**
     * Certificate request
     */
    private CertificateRequest certificateRequest_ = null;
    /**
     * X.509 certificate
     */
    private Certificate certificate_ = null;

    /**
     * SLCS service login URL
     */
    private final String slcsLoginUrl_;

    /**
     * Constructor, taking the SAML assertion URL and a properties
     * object with server-wide configuration parameters.
     * <p>
     * The resulting SLCSRequestor instance will be functional as long
     * as the SAML assertion pointed by {@code assertionUrl} is valid.
     *
     * @param wsc                    A {@link ch.SWITCH.aai.idwsf.ecp.WebServiceClient} instance to use for Shibboleth/HTTP negotiations
     * @param assertionUrl URL to the SAML assertion resulting from the Shibboleth login process.
     * @param wspSessionInitiatorUrl URL to the WSP Session Initiator (typically ends in `.../Shibboleth.sso/WSP`)
     * @param slcsLoginUrl           URL to the SLCS service login
     *
     */
    public SLCSRequestor(final WebServiceClient wsc, final String assertionUrl, final String wspSessionInitiatorUrl,
            final String slcsLoginUrl)
            throws GeneralSecurityException, IOException, TokenResolverException, AssertionExpiredError {
        // get the delegated client through the idwsf library
        SAML2AssertionURLResolver resolver = new SAML2AssertionURLResolver(assertionUrl);
        Assertion assertion;
        try {
            assertion = resolver.resolveToken();
        } catch (ch.SWITCH.aai.idwsf.token.AssertionException x) {
            // XXX: are there other cases where `AssertionException` can be thrown?
            throw new AssertionExpiredError("Assertion expired, please log out and then in again");
        }
        LOG.debug("SLCSRequestor: retrieved assertion ID: " + assertion.getID());
        LOG.debug(
                "SLCSRequestor: retrieved assertion for subject: " + assertion.getSubject().getNameID().getValue());
        LOG.debug("SLCSRequestor: retrieved assertion issued by: " + assertion.getIssuer().getValue());
        LOG.debug("SLCSRequestor: retrieved assertion is valid until: "
                + assertion.getConditions().getNotOnOrAfter().toLocalDateTime().toString());
        // create the delegation context
        DelegationContext context = new DelegationContext(assertion);
        // set the WSP session initiator URL
        LOG.debug("SLCSRequestor: using WSP session initiator URL '" + wspSessionInitiatorUrl + "'");
        context.setWSPSessionIntiatorURL(wspSessionInitiatorUrl);
        setContext(context);
        setWsc(wsc);
        // remember the SLCS login URL for use in `login()`
        slcsLoginUrl_ = slcsLoginUrl;
    }

    /**
     * Constructor, taking the SAML assertion URL and a properties object with server-wide configuration parameters.
     * <p>
     * The resulting SLCSRequestor instance will be functional as long
     * as the SAML assertion pointed by {@code assertionUrl} is valid.
     * <p>
     * Some server-wide configuration parameters are necessary for the
     * Shibboleth negotiation over HTTP.  These are provided by the
     * {@code props} parameter.  The following properties are read off
     * the {@code props} object (prefix names with {@code gridcertlib.}):
     * <dl>
     * <dt>slcsLoginURL           <dd>URL to the SLCS service login
     * <dt>wspSessionInitiatorURL <dd>URL to the WSP Session Initiator (typically ends with `.../Shibboleth.sso/WSP`)
     * </dl>
     *
     * @param wsc                    A {@link ch.SWITCH.aai.idwsf.ecp.WebServiceClient} instance to use for Shibboleth/HTTP negotiations
     * @param assertionUrl URL to the SAML assertion resulting from the Shibboleth login process.
     * @param props        properties used in the Shibbolethised negotiation: SP ID, server X.509 certificate paths, etc.
     *
     * @throw InvalidConfigurationException if any of the required properties (see above) is missing.
     */
    public SLCSRequestor(final WebServiceClient wsc, final String assertionUrl, final Properties props)
            throws InvalidConfigurationException, GeneralSecurityException, IOException, TokenResolverException {
        this(wsc, assertionUrl, getRequiredProperty(props, "gridcertlib.wspSessionInitiatorURL"),
                getRequiredProperty(props, "gridcertlib.slcsLoginURL"));
    }

    protected static String getRequiredProperty(final Properties props, final String name)
            throws InvalidConfigurationException {
        String value = props.getProperty(name);
        if (null == value)
            throw new InvalidConfigurationException("Missing required property '" + name + "'");
        return value;
    }

    /**
     * Carry out the full SLCS negotiation in one go.
     * <p>
     * If successful, the certificate and private key can be
     * retrieved using the {@link #getCertificate()} and
     * {@link #getPrivateKey()} methods.
     *
     * @param password
     * @throws java.security.GeneralSecurityException
     *
     * @throws org.glite.slcs.SLCSException
     */
    public void performSlcsInit(final String password) throws SLCSException, GeneralSecurityException {
        login();
        generateCertificateKeys(password.toCharArray());
        generateCertificateRequest();
        requestSlcsCertificate();
    }

    /**
     * Login to the SLCS service.
     * <p>
     * As a side effect, fills the {@code certificateRequestUrl_},
     * {@code certificateSubject_}, and {@code certificateExtensions_}
     * member variables.
     */
    public void login() throws SLCSException {
        GetMethod getLoginMethod = new GetMethod(slcsLoginUrl_);
        try {
            LOG.info("GET login: " + slcsLoginUrl_);
            int status = wsc_.executeMethod(context_, getLoginMethod);
            LOG.debug(getLoginMethod.getStatusLine().toString());
            // XXX: do we need to handle 30x (redirect) codes?
            if (status != 200) {
                LOG.error("SLCS login failed: " + getLoginMethod.getStatusLine());
                if (status == 401) {
                    throw new AuthException(
                            "SLCS authorization failed: " + getLoginMethod.getStatusLine() + ": " + slcsLoginUrl_);
                } else {
                    throw new AuthException("SLCS login failed: " + getLoginMethod.getStatusLine());

                }
            }
            // read response
            InputStream is = getLoginMethod.getResponseBodyAsStream();
            Source source = new Source(is);
            checkSLCSResponse(source, "SLCSLoginResponse");
            parseSLCSLoginResponse(source);
        } catch (IOException e) {
            final String message = "Failed to request DN: " + e.getMessage();
            LOG.error(message, e);
            throw new SLCSException(message, e);
        } catch (ECPException e) {
            final String message = "SLCS login failed, ECP error: " + e.getMessage();
            LOG.error(message, e);
            throw new SLCSException(message, e);
        } catch (DelegationException e) {
            final String message = "SLCS login failed, delegation error: " + e.getMessage();
            LOG.error(message, e);
            throw new SLCSException(message, e);
        } finally {
            getLoginMethod.releaseConnection();
        }
    }

    /**
     * Check the return status in an SLCS transaction.
     *
     * @param source The XML document outputted as the result of a SLCS HTTP method invocation
     * @param name   name of the XML element that contains the status/error elements
     */
    private void checkSLCSResponse(Source source, String name) throws IOException, SLCSException {
        // optimization !?!
        source.fullSequentialParse();

        int pos = 0;
        Element reponseElement = source.findNextElement(pos, name);
        if (reponseElement == null || reponseElement.isEmpty()) {
            LOG.error(name + " element not found");
            throw new ServiceException(name + " element not found in SLCS response");
        }
        // read status
        Element statusElement = source.findNextElement(pos, "status");
        if (statusElement == null || statusElement.isEmpty()) {
            LOG.error("Status element not found");
            throw new ServiceException("Status element not found in SLCS response");
        }
        String status = statusElement.getContent().toString();
        LOG.info("Status=" + status);
        if (status != null && status.equalsIgnoreCase("Error")) {
            pos = statusElement.getEnd();
            Element errorElement = source.findNextElement(pos, "error");
            if (errorElement == null || errorElement.isEmpty()) {
                LOG.error("Error element not found");
                throw new SLCSException("Error element not found in SLCS error response");
            }
            String error = errorElement.getContent().toString();
            // is there a stack trace?
            pos = errorElement.getEnd();
            Element traceElement = source.findNextElement(pos, "stacktrace");
            if (traceElement != null && !traceElement.isEmpty()) {
                String stackTrace = traceElement.getContent().toString();
                throw new ServiceException(error + "\nRemote error:\n" + stackTrace);
            }
            throw new ServiceException(error);
        } else if (status == null || !status.equalsIgnoreCase("Success")) {
            LOG.error("Unknown Status: " + status);
            throw new ServiceException("Unknown Status:" + status);
        }
    }

    /**
     * Auxiliary method to {@code slcsLogin}.
     * <p>
     * Fills the {@code authorizationToken_}, {@code certificateRequestUrl_},
     * {@code certificateSubject_}, and {@code certificateExtensions_}
     * member variables.
     */
    private void parseSLCSLoginResponse(Source source) throws SLCSException {
        // get AuthorizationToken
        int pos = 0;
        Element tokenElement = source.findNextElement(pos, "AuthorizationToken");
        if (tokenElement == null || tokenElement.isEmpty()) {
            LOG.error("AuthorizationToken element not found");
            throw new SLCSException("AuthorizationToken element not found in SLCS response");
        }
        authorizationToken_ = tokenElement.getContent().toString();
        LOG.info("AuthorizationToken=" + authorizationToken_);
        // get the certificate request URL
        pos = tokenElement.getEnd();
        Element certificateRequestElement = source.findNextElement(pos, "CertificateRequest");
        if (certificateRequestElement == null || certificateRequestElement.isEmpty()) {
            LOG.error("CertificateRequest element not found");
            throw new SLCSException("CertificateRequest element not found in SLCS response");
        }
        certificateRequestUrl_ = certificateRequestElement.getAttributeValue("url");
        if (certificateRequestUrl_ == null) {
            LOG.error("CertificateRequest url attribute not found");
            throw new SLCSException("CertificateRequest url attribute not found in SLCS response");
        } else if (!certificateRequestUrl_.startsWith("http")) {
            LOG.error("CertificateRequest url attribute doesn't starts with http: " + certificateRequestUrl_);
            throw new SLCSException("CertificateRequest url attribute is not valid: " + certificateRequestUrl_);
        }
        LOG.info("CertificateRequest url=" + certificateRequestUrl_);

        // get certificate subject
        Element subjectElement = source.findNextElement(pos, "Subject");
        if (subjectElement == null || subjectElement.isEmpty()) {
            LOG.error("Subject element not found");
            throw new SLCSException("Subject element not found in SLCS response");
        }
        certificateSubject_ = subjectElement.getContent().toString();
        LOG.info("CertificateRequest.Subject=" + certificateSubject_);
        // any certificate extensions?
        certificateExtensions_ = new ArrayList<CertificateExtension>();
        pos = subjectElement.getEnd();
        Element extensionElement = null;
        while ((extensionElement = source.findNextElement(pos, "certificateextension")) != null) {
            pos = extensionElement.getEnd();
            String extensionName = extensionElement.getAttributeValue("name");
            String extensionValues = extensionElement.getContent().toString();
            LOG.info("CertificateRequest.CertificateExtension: " + extensionName + "=" + extensionValues);
            CertificateExtension extension = CertificateExtensionFactory.createCertificateExtension(extensionName,
                    extensionValues);
            if (extension != null) {
                certificateExtensions_.add(extension);
            }
        }
    }

    /**
     * Request a certificate from the SLCS service and store the returned one.
     *
     * @throws org.glite.slcs.SLCSException
     */
    public void requestSlcsCertificate() throws SLCSException {
        PostMethod postCertificateRequestMethod = new PostMethod(certificateRequestUrl_);
        postCertificateRequestMethod.addParameter("AuthorizationToken", authorizationToken_);
        postCertificateRequestMethod.addParameter("CertificateSigningRequest", certificateRequest_.getPEMEncoded());
        try {
            LOG.info("POST CSR: " + certificateRequestUrl_);
            int status = wsc_.executeMethod(postCertificateRequestMethod);
            LOG.debug(postCertificateRequestMethod.getStatusLine().toString());
            // check status
            if (status != 200) {
                LOG.error("SLCS certificate request failed: " + postCertificateRequestMethod.getStatusLine());
                throw new ServiceException(
                        "SLCS certificate request failed: " + postCertificateRequestMethod.getStatusLine());
            }
            // read response
            InputStream is = postCertificateRequestMethod.getResponseBodyAsStream();
            Source source = new Source(is);
            checkSLCSResponse(source, "SLCSCertificateResponse");
            parseSLCSCertificateResponse(source);
        } catch (IOException e) {
            final String message = "Failed to request certificate, I/O error: " + e.getMessage();
            LOG.error(message, e);
            throw new SLCSException(message, e);
        } finally {
            postCertificateRequestMethod.releaseConnection();
        }
    }

    private void parseSLCSCertificateResponse(Source source) throws SLCSException, IOException {
        Element certificateElement = source.findNextElement(0, "Certificate");
        if (certificateElement == null || certificateElement.isEmpty()) {
            final String message = "Certificate element not found in SLCS response";
            LOG.error(message);
            throw new SLCSException(message);
        }
        String pemCertificate = certificateElement.getContent().toString();
        LOG.info("Certificate element found");
        LOG.debug("Certificate=" + pemCertificate);
        StringReader reader = new StringReader(pemCertificate);
        try {
            certificate_ = Certificate.readPEM(reader);
        } catch (GeneralSecurityException e) {
            final String message = "Failed to reconstitute the certificate: " + e.getMessage();
            LOG.error(message, e);
            throw new SLCSException(message, e);
        }
    }

    /**
     * Creates the certificate keys.
     *
     * @param password The private key password.
     * @throws java.security.GeneralSecurityException
     *          If an error occurs while creating the object.
     */
    public void generateCertificateKeys(char[] password) throws GeneralSecurityException {
        LOG.debug("generating keys...");
        certificateKeys_ = new CertificateKeys(getKeySize(), password);
    }

    /**
     * Creates the CeriticateRequest object.
     *
     * @throws java.security.GeneralSecurityException
     *          If an error occurs while creating the object.
     */
    public void generateCertificateRequest() throws GeneralSecurityException {
        LOG.debug("generate CSR: " + certificateSubject_);
        certificateRequest_ = new CertificateRequest(certificateKeys_, certificateSubject_, certificateExtensions_);
    }

    private void setContext(DelegationContext context) {
        this.context_ = context;
    }

    private void setWsc(WebServiceClient wsc) {
        this.wsc_ = wsc;
    }

    public void setKeySize(int size) {
        // TODO check valid size
        keySize_ = size;
    }

    public Certificate getCertificate() {
        return certificate_;
    }

    public CertificateKeys getCertificateKeys() {
        return certificateKeys_;
    }

    public int getKeySize() {
        return keySize_;
    }
}