ca.nrc.cadc.cred.client.CredClient.java Source code

Java tutorial

Introduction

Here is the source code for ca.nrc.cadc.cred.client.CredClient.java

Source

/*
************************************************************************
*******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
**************  CENTRE CANADIEN DE DONNES ASTRONOMIQUES  **************
*
*  (c) 2011.                            (c) 2011.
*  Government of Canada                 Gouvernement du Canada
*  National Research Council            Conseil national de recherches
*  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
*  All rights reserved                  Tous droits rservs
*
*  NRC disclaims any warranties,        Le CNRC dnie toute garantie
*  expressed, implied, or               nonce, implicite ou lgale,
*  statutory, of any kind with          de quelque nature que ce
*  respect to the software,             soit, concernant le logiciel,
*  including without limitation         y compris sans restriction
*  any warranty of merchantability      toute garantie de valeur
*  or fitness for a particular          marchande ou de pertinence
*  purpose. NRC shall not be            pour un usage particulier.
*  liable in any event for any          Le CNRC ne pourra en aucun cas
*  damages, whether direct or           tre tenu responsable de tout
*  indirect, special or general,        dommage, direct ou indirect,
*  consequential or incidental,         particulier ou gnral,
*  arising from the use of the          accessoire ou fortuit, rsultant
*  software.  Neither the name          de l'utilisation du logiciel. Ni
*  of the National Research             le nom du Conseil National de
*  Council of Canada nor the            Recherches du Canada ni les noms
*  names of its contributors may        de ses  participants ne peuvent
*  be used to endorse or promote        tre utiliss pour approuver ou
*  products derived from this           promouvoir les produits drivs
*  software without specific prior      de ce logiciel sans autorisation
*  written permission.                  pralable et particulire
*                                       par crit.
*
*  This file is part of the             Ce fichier fait partie du projet
*  OpenCADC project.                    OpenCADC.
*
*  OpenCADC is free software:           OpenCADC est un logiciel libre ;
*  you can redistribute it and/or       vous pouvez le redistribuer ou le
*  modify it under the terms of         modifier suivant les termes de
*  the GNU Affero General Public        la GNU Affero General Public
*  License as published by the          License? telle que publie
*  Free Software Foundation,            par la Free Software Foundation
*  either version 3 of the              : soit la version 3 de cette
*  License, or (at your option)         licence, soit ( votre gr)
*  any later version.                   toute version ultrieure.
*
*  OpenCADC is distributed in the       OpenCADC est distribu
*  hope that it will be useful,         dans lespoir quil vous
*  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
*  without even the implied             GARANTIE : sans mme la garantie
*  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILIT
*  or FITNESS FOR A PARTICULAR          ni dADQUATION  UN OBJECTIF
*  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
*  General Public License for           Gnrale Publique GNU Affero
*  more details.                        pour plus de dtails.
*
*  You should have received             Vous devriez avoir reu une
*  a copy of the GNU Affero             copie de la Licence Gnrale
*  General Public License along         Publique GNU Affero avec
*  with OpenCADC.  If not, see          OpenCADC ; si ce nest
*  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
*                                       <http://www.gnu.org/licenses/>.
*
*  $Revision: 5 $
*
************************************************************************
*/

package ca.nrc.cadc.cred.client;

import ca.nrc.cadc.auth.AuthMethod;
import ca.nrc.cadc.auth.AuthenticationUtil;
import ca.nrc.cadc.auth.HttpPrincipal;
import ca.nrc.cadc.auth.SSLUtil;
import ca.nrc.cadc.auth.X509CertificateChain;
import ca.nrc.cadc.cred.CertUtil;
import ca.nrc.cadc.net.HttpDownload;
import ca.nrc.cadc.net.NetUtil;
import ca.nrc.cadc.net.ResourceNotFoundException;
import ca.nrc.cadc.profiler.Profiler;
import ca.nrc.cadc.reg.Standards;
import ca.nrc.cadc.reg.client.RegistryClient;
import ca.nrc.cadc.util.Base64;
import org.apache.log4j.Logger;
import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.bouncycastle.openssl.PEMWriter;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import javax.security.auth.Subject;
import javax.security.auth.x500.X500Principal;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.security.AccessControlContext;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Set;

public class CredClient {
    private static Logger LOGGER = Logger.getLogger(CredClient.class);

    // socket factory to use when connecting
    SSLSocketFactory sf;

    private URI serviceID;

    public CredClient(URI serviceID) {
        if (serviceID == null)
            throw new IllegalArgumentException("invalid serviceID: " + serviceID);
        if (serviceID.getFragment() != null)
            throw new IllegalArgumentException("invalid serviceID (fragment not allowed): " + serviceID);
        this.serviceID = serviceID;
    }

    /**
     * Get a proxy certificate for the specified user (subject). This operation is
     * currently a custom feature of the cadcCDP-Server implementation that allows
     * authorized callers to generate short-lived proxy certificates.
     * 
     * @param subject the target user
     * @param daysValid the length of time the proxy certificate should be valid for
     * @return 
     * @throws CertificateException 
     * @throws java.io.IOException 
     * @throws ca.nrc.cadc.net.ResourceNotFoundException 
     */
    public X509CertificateChain getProxyCertificate(Subject subject, double daysValid)
            throws AccessControlException, CertificateException, IOException, ResourceNotFoundException {
        Set<Principal> principals = subject.getPrincipals();
        // get the first available X500, HTTP Principal
        X500Principal x500Principal = null;
        HttpPrincipal httpPrincipal = null;
        for (Principal principal : principals) {
            if (principal instanceof X500Principal && x500Principal == null) {
                x500Principal = (X500Principal) principal;
            } else if (principal instanceof HttpPrincipal && httpPrincipal == null) {
                httpPrincipal = (HttpPrincipal) principal;
            }
        }

        StringBuilder path = new StringBuilder();

        // If an X500 Principal exists, get the certificate based on the
        // DN. Otherwise, if an HTTP Principal exists, get the 
        // certificate based on the userid.

        try {
            if (x500Principal != null) {
                String dn = AuthenticationUtil.canonizeDistinguishedName(x500Principal.getName());
                path.append("/dn/").append(NetUtil.encode(dn));
            } else if (httpPrincipal != null) {
                path.append("/userid/").append(httpPrincipal.getName());
            } else
                throw new UnsupportedOperationException("current subject lacks supported principal type");

            if (daysValid > 0) {
                path.append("?daysValid=").append(String.valueOf(daysValid));
            }

            LOGGER.debug("serviceID: " + this.serviceID);
            URL credUrl = getRegistryClient().getServiceURL(this.serviceID, Standards.CRED_PROXY_10,
                    AuthMethod.CERT);
            LOGGER.debug("credUrl is null: " + (credUrl == null));
            URL url = new URL(credUrl.toExternalForm() + path.toString());
            LOGGER.debug("getCertficate: " + url.toString());
            return downloadCertificate(url);
        } finally {
        }
    }

    /**
     * Delegate current subject credentials to the service (IVOA CDP-1.0).
     * 
     * @param days
     * @throws MalformedURLException
     * @throws IOException
     * @throws InvalidKeyException
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     * @throws SignatureException
     * @throws CertificateEncodingException
     * @throws CertificateParsingException
     * @throws CertificateExpiredException
     * @throws CertificateNotYetValidException 
     * @throws ca.nrc.cadc.net.ResourceNotFoundException 
     */
    public void delegate(double days)
            throws MalformedURLException, IOException, InvalidKeyException, NoSuchProviderException,
            NoSuchAlgorithmException, SignatureException, CertificateEncodingException, CertificateParsingException,
            CertificateExpiredException, CertificateNotYetValidException, ResourceNotFoundException {
        delegate(null, days);
    }

    /**
     * Custom delegation method that allows the caller to have a different DN from
     * the created certificate DN. This feature allows the caller/signer to use the
     * CDP service as a certificate authority (see: cadc-cert-gen).
     * 
     * @param userDN target DN to create; null for self-delegation
     * @param days
     * @throws MalformedURLException
     * @throws IOException
     * @throws InvalidKeyException
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     * @throws SignatureException
     * @throws CertificateEncodingException
     * @throws CertificateParsingException
     * @throws CertificateExpiredException
     * @throws CertificateNotYetValidException 
     * @throws ca.nrc.cadc.net.ResourceNotFoundException 
     */
    public void delegate(X500Principal userDN, double days)
            throws MalformedURLException, IOException, InvalidKeyException, NoSuchProviderException,
            NoSuchAlgorithmException, SignatureException, CertificateEncodingException, CertificateParsingException,
            CertificateExpiredException, CertificateNotYetValidException, ResourceNotFoundException {
        final StringBuilder resourcePath = new StringBuilder(64);
        // user does not have the group created. Through a POST.
        // the server generates one and returns it to the user
        if (userDN != null) {
            resourcePath.append("?DN=");
            resourcePath.append(URLEncoder.encode(userDN.getName(), "UTF-8"));
        }

        URL credUrl = getRegistryClient().getServiceURL(this.serviceID, Standards.CRED_DELEGATE_10,
                AuthMethod.CERT);
        URL resourceURL = new URL(credUrl.toExternalForm() + "/" + resourcePath.toString());

        LOGGER.debug("delegate(), URL=" + resourceURL);
        HttpURLConnection connection = openConnection(resourceURL);
        connection.setRequestMethod("POST");
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setUseCaches(false);
        connection.connect();

        String responseMessage = connection.getResponseMessage();
        int responseCode = connection.getResponseCode();
        LOGGER.debug("create step in delegate(), response code: " + responseCode);
        LOGGER.debug("create step in delegate(), response message: " + responseMessage);

        switch (responseCode) {
        case HttpURLConnection.HTTP_CREATED:
            String location = connection.getHeaderField("Location");
            int sec = (int) days * 24 * 60 * 60; // seconds
            String csr = getEncodedCSR(location, userDN);
            PKCS10CertificationRequest req = readCSR(csr.getBytes());
            X509Certificate cert = generateV3Certificate(req, sec);
            X509Certificate[] chain = createProxyCertChain(cert);
            putSignedCert(location, chain, userDN);

            break;
        case HttpURLConnection.HTTP_UNAUTHORIZED:
            throw new AccessControlException(responseMessage);
        case HttpURLConnection.HTTP_NOT_FOUND:
            // parent node not found
            throw new ResourceNotFoundException(responseMessage);
        case HttpURLConnection.HTTP_OK:
            // break intentionally left out
        case HttpURLConnection.HTTP_CONFLICT:
            // break intentionally left out
        case HttpURLConnection.HTTP_BAD_REQUEST:
            // duplicate group
            throw new IllegalArgumentException(responseMessage);
        default:
            throw new RuntimeException("Unexpected failure mode: " + responseMessage + "(" + responseCode + ")");
        }

    }

    /**
     * Creates the resource (private key, public key, CSR) for userDN. This is the
     * first step of the sequence used by the delegate method.
     * 
     * @param userDN
     * @return URL to the newly create resource
     * @throws IOException
     * @throws ca.nrc.cadc.net.ResourceNotFoundException
     */
    public String createResoure(X500Principal userDN) throws IOException, ResourceNotFoundException {
        final StringBuilder resourcePath = new StringBuilder(64);
        // user does not have the group created. Through a POST.
        // the server generates one and returns it to the user
        if (userDN != null) {
            resourcePath.append("?DN=");
            resourcePath.append(URLEncoder.encode(userDN.getName(), "UTF-8"));
        }

        URL credUrl = getRegistryClient().getServiceURL(this.serviceID, Standards.CRED_DELEGATE_10,
                AuthMethod.CERT);
        URL resourceURL = new URL(credUrl.toExternalForm() + "/" + resourcePath.toString());

        LOGGER.debug("delegate(), URL=" + resourceURL);
        HttpURLConnection connection = openConnection(resourceURL);
        connection.setRequestMethod("POST");
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setUseCaches(false);
        connection.connect();

        String responseMessage = connection.getResponseMessage();
        int responseCode = connection.getResponseCode();
        LOGGER.debug("create step in delegate(), response code: " + responseCode);
        LOGGER.debug("create step in delegate(), response message: " + responseMessage);

        switch (responseCode) {
        case HttpURLConnection.HTTP_CREATED:
            return connection.getHeaderField("Location");
        case HttpURLConnection.HTTP_OK:
            // break intentionally left out
        case HttpURLConnection.HTTP_CONFLICT:
            // break intentionally left out
        case HttpURLConnection.HTTP_NOT_FOUND:
            // parent node not found
            throw new ResourceNotFoundException(responseMessage);
        case HttpURLConnection.HTTP_BAD_REQUEST:
            // duplicate group
            throw new IllegalArgumentException(responseMessage);
        case HttpURLConnection.HTTP_FORBIDDEN:
            throw new AccessControlException(responseMessage);
        default:
            throw new RuntimeException("Unexpected failure mode: " + responseMessage + "(" + responseCode + ")");
        }

    }

    /**
     * Delete the resource (private key, public key, CSR) for userDN.
     * 
     * @param userDN
     * @throws IOException
     * @throws CertificateException
     * @throws ca.nrc.cadc.net.ResourceNotFoundException
     */
    public void deleteResource(X500Principal userDN)
            throws IOException, CertificateException, ResourceNotFoundException {
        String location = getLocation(userDN);
        final StringBuilder resourcePath = new StringBuilder(64);
        resourcePath.append(location);
        if (userDN != null) {
            resourcePath.append("?DN=");
            resourcePath.append(URLEncoder.encode(userDN.getName(), "UTF-8"));
        }

        final URL resourceURL = new URL(resourcePath.toString());

        LOGGER.debug("delegate(), URL=" + resourceURL);
        HttpURLConnection connection = openConnection(resourceURL);
        connection.setRequestMethod("DELETE");
        connection.setDoInput(true);
        connection.setDoOutput(false);
        connection.setUseCaches(false);
        connection.connect();

        String responseMessage = connection.getResponseMessage();
        int responseCode = connection.getResponseCode();
        LOGGER.debug("delete step in delegate(), response code: " + responseCode);
        LOGGER.debug("delete step in delegate(), response message: " + responseMessage);

        switch (responseCode) {
        case HttpURLConnection.HTTP_NO_CONTENT:
            return;
        case HttpURLConnection.HTTP_CONFLICT:
            // break intentionally left out
        case HttpURLConnection.HTTP_NOT_FOUND:
            // parent node not found
            throw new ResourceNotFoundException(responseMessage);
        case HttpURLConnection.HTTP_BAD_REQUEST:
            // duplicate
            throw new IllegalArgumentException(responseMessage);
        case HttpURLConnection.HTTP_FORBIDDEN:
            throw new AccessControlException(responseMessage);
        default:
            throw new RuntimeException("Unexpected failure mode: " + responseMessage + "(" + responseCode + ")");
        }

    }

    /**
    * Accesses the Certificate Signing Request associated with a user
    * 
    * @param userDN
    *            The DN of the user that owns the CSR.
    * @return CSR
    * @throws IOException
    * @throws java.security.InvalidKeyException
    * @throws java.security.cert.CertificateEncodingException
    * @throws java.security.cert.CertificateParsingException
    * @throws java.security.cert.CertificateExpiredException
    * @throws java.security.cert.CertificateNotYetValidException
    * @throws java.security.NoSuchProviderException
    * @throws java.security.NoSuchAlgorithmException
    * @throws java.security.SignatureException
    * @throws ca.nrc.cadc.net.ResourceNotFoundException
    * 
    */
    public String getEncodedCSR(X500Principal userDN)
            throws IOException, InvalidKeyException, CertificateEncodingException, CertificateParsingException,
            CertificateExpiredException, CertificateNotYetValidException, NoSuchProviderException,
            NoSuchAlgorithmException, SignatureException, CertificateException, ResourceNotFoundException {
        String location = getLocation(userDN);
        if (location == null) {
            throw new IllegalArgumentException("No certificate found for " + userDN);
        }
        return getEncodedCSR(location, userDN);
    }

    private String getEncodedCSR(String location, X500Principal userDN)
            throws IOException, InvalidKeyException, NoSuchProviderException, NoSuchAlgorithmException,
            SignatureException, CertificateEncodingException, CertificateParsingException,
            CertificateExpiredException, CertificateNotYetValidException, ResourceNotFoundException {
        final URL resourceURL = new URL(location + "/CSR");
        LOGGER.debug("get CSR step in delegate(), URL=" + resourceURL);
        HttpURLConnection connection = openConnection(resourceURL);
        connection.setRequestMethod("GET");
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setUseCaches(false);
        connection.connect();

        String responseMessage = connection.getResponseMessage();
        int responseCode = connection.getResponseCode();
        LOGGER.debug("get CSR step in delegate(), response code: " + responseCode);
        LOGGER.debug("get CSR step in delegate(), response message: " + responseMessage);

        switch (responseCode) {
        case HttpURLConnection.HTTP_OK:
            try {
                byte[] csr = null;
                InputStream in = connection.getInputStream();
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                int bytesRead;
                byte[] buffer = new byte[1024];
                while ((bytesRead = in.read(buffer, 0, buffer.length)) != -1) {
                    out.write(buffer, 0, bytesRead);
                }
                out.flush();
                csr = out.toByteArray();
                in.close();
                LOGGER.debug("Downloaded CSR of size: " + csr.length);
                return new String(csr);

            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("UTF-8 encoding not supported");
            }
        case HttpURLConnection.HTTP_CONFLICT:
            // break intentionally left out
        case HttpURLConnection.HTTP_NOT_FOUND:
            throw new ResourceNotFoundException(responseMessage);
        case HttpURLConnection.HTTP_BAD_REQUEST:
            // duplicate group
            throw new IllegalArgumentException(responseMessage);
        case HttpURLConnection.HTTP_FORBIDDEN:
            throw new AccessControlException(responseMessage);
        default:
            throw new RuntimeException("Unexpected failure mode: " + responseMessage + "(" + responseCode + ")");
        }

    }

    /**
     * Accesses the certificate associated with a user/location.
     * 
     * @param userDN
     * @return X509Certificate from the CDP URL of the resource that owns
     *         the certificate
     * @throws IOException
     * @throws java.security.InvalidKeyException
     * @throws java.security.NoSuchProviderException
     * @throws java.security.NoSuchAlgorithmException
     * @throws java.security.SignatureException
     * @throws java.security.cert.CertificateEncodingException
     * @throws java.security.cert.CertificateParsingException
     * @throws ca.nrc.cadc.net.ResourceNotFoundException
     */
    public X509Certificate[] getCertificate(X500Principal userDN) throws IOException, InvalidKeyException,
            NoSuchProviderException, NoSuchAlgorithmException, SignatureException, CertificateEncodingException,
            CertificateParsingException, CertificateException, ResourceNotFoundException {
        String location = getLocation(userDN);
        final URL resourceURL = new URL(location + "/certificate");
        LOGGER.debug("get certificate, URL=" + resourceURL);
        HttpURLConnection connection = openConnection(resourceURL);
        connection.setRequestMethod("GET");
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setUseCaches(false);
        connection.connect();

        String responseMessage = connection.getResponseMessage();
        int responseCode = connection.getResponseCode();
        LOGGER.debug("get certificate, response code: " + responseCode);
        LOGGER.debug("get certificate, response message: " + responseMessage);

        switch (responseCode) {
        case HttpURLConnection.HTTP_OK:
            try {
                InputStream in = connection.getInputStream();
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                int bytesRead;
                byte[] buffer = new byte[1024];
                while ((bytesRead = in.read(buffer, 0, buffer.length)) != -1) {
                    out.write(buffer, 0, bytesRead);
                }
                out.flush();
                byte[] certBuf = out.toByteArray();
                in.close();

                X509Certificate[] certs = SSLUtil.readCertificateChain(SSLUtil.getCertificates(certBuf));
                return certs;

            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("UTF-8 encoding not supported");
            }
        case HttpURLConnection.HTTP_CONFLICT:
            // break intentionally left out
        case HttpURLConnection.HTTP_NOT_FOUND:
            throw new ResourceNotFoundException(responseMessage);
        case HttpURLConnection.HTTP_BAD_REQUEST:
            // duplicate group
            throw new IllegalArgumentException(responseMessage);
        case HttpURLConnection.HTTP_UNAUTHORIZED:
            throw new AccessControlException(responseMessage);
        default:
            throw new RuntimeException("Unexpected failure mode: " + responseMessage + "(" + responseCode + ")");
        }

    }

    /**
     * Gets the URL corresponding to the resource in CDP
     * 
     * @param userDN
     * @return hash code used to access subject's resource in CDP
     * @throws IOException
     * @throws java.security.cert.CertificateException
     * @throws ca.nrc.cadc.net.ResourceNotFoundException
     */
    public String getLocation(X500Principal userDN)
            throws IOException, CertificateException, ResourceNotFoundException {
        final StringBuilder resourcePath = new StringBuilder(64);
        // user does not have the group created. Through a POST.
        // the server generates one and returns it to the user
        if (userDN != null) {
            resourcePath.append("?DN=");
            resourcePath.append(URLEncoder.encode(userDN.getName(), "UTF-8"));
        }

        URL credUrl = getRegistryClient().getServiceURL(this.serviceID, Standards.CRED_DELEGATE_10,
                AuthMethod.CERT);
        URL resourceURL = new URL(credUrl.toExternalForm() + "/" + resourcePath.toString());

        LOGGER.debug("get hash, URL=" + resourceURL);
        HttpURLConnection connection = openConnection(resourceURL);
        connection.setRequestMethod("GET");
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setUseCaches(false);
        connection.connect();

        String responseMessage = connection.getResponseMessage();
        int responseCode = connection.getResponseCode();
        LOGGER.debug("get hash, response code: " + responseCode);
        LOGGER.debug("get hash, response message: " + responseMessage);

        switch (responseCode) {
        case HttpURLConnection.HTTP_OK:
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String hash = reader.readLine();
                if (reader.readLine() != null) {
                    throw new CertificateException("Only one hash expected");
                }
                String hashPath = "/" + hash;
                URL u = new URL(credUrl.toExternalForm() + "/" + hashPath);
                return u.toExternalForm();
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("UTF-8 encoding not supported");
            }
        case HttpURLConnection.HTTP_CONFLICT:
            // break intentionally left out
        case HttpURLConnection.HTTP_NOT_FOUND:
            // parent node not found
            throw new ResourceNotFoundException(responseMessage);
        case HttpURLConnection.HTTP_BAD_REQUEST:
            // duplicate group
            throw new IllegalArgumentException(responseMessage);
        case HttpURLConnection.HTTP_UNAUTHORIZED:
            throw new AccessControlException(responseMessage);
        default:
            throw new RuntimeException("Unexpected failure mode: " + responseMessage + "(" + responseCode + ")");
        }

    }

    /**
     * Puts a signed certificate associated with a user/location.
     * 
     * @param chain
     * @throws IOException
     * @throws java.security.InvalidKeyException
     * @throws java.security.NoSuchProviderException
     * @throws java.security.NoSuchAlgorithmException
     * @throws java.security.SignatureException
     * @throws CertificateException
     * @throws ca.nrc.cadc.net.ResourceNotFoundException
     */
    public void putSignedCert(X509Certificate[] chain)
            throws IOException, InvalidKeyException, NoSuchProviderException, NoSuchAlgorithmException,
            SignatureException, CertificateException, ResourceNotFoundException {
        X500Principal delegatedUser = chain[0].getSubjectX500Principal();
        String location = getLocation(delegatedUser);
        putSignedCert(location, chain, delegatedUser);
    }

    // append certficate chain with the specified cert to make a valid proxy cert
    private X509Certificate[] createProxyCertChain(X509Certificate cert) {
        AccessControlContext ac = AccessController.getContext();
        Subject subject = Subject.getSubject(ac);
        if (subject != null) {
            Set<X509CertificateChain> cc = subject.getPublicCredentials(X509CertificateChain.class);
            if (cc.size() > 0) {
                X509CertificateChain xcc = cc.iterator().next();
                X509Certificate[] chain = xcc.getChain();
                X509Certificate[] ret = new X509Certificate[chain.length + 1];
                ret[0] = cert;
                for (int i = 0; i < chain.length; i++) {
                    ret[i + 1] = chain[i];
                }
                return ret;
            }
        }
        throw new IllegalStateException("current Subject does not contain a certficate chain");

    }

    /**
     * Puts a signed certificate associated with a user/location
     * 
     * @param location
     *            URL of the resource that owns the certificate
     * @param certs
     *            Signed certificate to put.
     * @throws IOException
     */
    private void putSignedCert(String location, X509Certificate[] certs, X500Principal userDN) throws IOException,
            InvalidKeyException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException,
            CertificateEncodingException, CertificateParsingException, ResourceNotFoundException {
        LOGGER.debug("putSignedCert: " + userDN + " chain length: " + certs.length);

        final StringBuilder resourcePath = new StringBuilder(64);
        // user does not have the group created. Through a POST.
        // the server generates one and returns it to the user
        resourcePath.append(location);
        resourcePath.append("/certificate");
        if (userDN != null) {
            resourcePath.append("?DN=");
            resourcePath.append(URLEncoder.encode(userDN.getName(), "UTF-8"));
        }

        final URL resourceURL = new URL(resourcePath.toString());
        LOGGER.debug("put certificate step in delegate(), URL=" + resourceURL);
        HttpURLConnection connection = openConnection(resourceURL);
        connection.setRequestMethod("PUT");
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setUseCaches(false);

        OutputStream os = connection.getOutputStream();
        PEMWriter writer = new PEMWriter(new OutputStreamWriter(os));
        for (X509Certificate c : certs) {
            writer.writeObject(c);
        }

        writer.flush();
        writer.close();

        String responseMessage = connection.getResponseMessage();
        int responseCode = connection.getResponseCode();
        LOGGER.debug("put certificate step in delegate(), response code: " + responseCode);
        LOGGER.debug("put certificate step in delegate(), response message: " + responseMessage);

        switch (responseCode) {
        case HttpURLConnection.HTTP_OK:
            LOGGER.debug("Certificate uploaded");
            break;

        case HttpURLConnection.HTTP_CONFLICT:
            // break intentionally left out
        case HttpURLConnection.HTTP_NOT_FOUND:
            // parent node not found
            throw new ResourceNotFoundException(responseMessage);
        case HttpURLConnection.HTTP_BAD_REQUEST:
            // duplicate group
            throw new IllegalArgumentException(responseMessage);
        case HttpURLConnection.HTTP_UNAUTHORIZED:
            throw new AccessControlException(responseMessage);
        default:
            throw new RuntimeException("Unexpected failure mode: " + responseMessage + "(" + responseCode + ")");
        }

    }

    /**
     * Open a HttpsURLConnection with a SocketFactory created based on
     * user credentials.
     * 
     * @param url
     * @return UTLConnection returns an open https connection to URL
     * @throws IOException
     */
    protected HttpsURLConnection openConnection(final URL url) throws IOException {
        if (!url.getProtocol().equals("https")) {
            throw new IllegalArgumentException(
                    "Wrong protocol: " + url.getProtocol() + ". GMS works on https only");
        }
        if (sf == null) {
            // lazy initialization of socket factory
            AccessControlContext ac = AccessController.getContext();
            Subject subject = Subject.getSubject(ac);
            sf = SSLUtil.getSocketFactory(subject);
        }
        HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
        if (sf != null)
            con.setSSLSocketFactory(sf);
        return con;
    }

    public X509Certificate generateV3Certificate(PKCS10CertificationRequest csr, int lifetime)
            throws InvalidKeyException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException,
            CertificateEncodingException, CertificateParsingException, CertificateExpiredException,
            CertificateNotYetValidException {

        AccessControlContext ac = AccessController.getContext();
        Subject subject = Subject.getSubject(ac);
        X509CertificateChain chain = null;
        if (subject != null) {
            Set<X509CertificateChain> certs = subject.getPublicCredentials(X509CertificateChain.class);
            if (certs.size() > 0)
                chain = certs.iterator().next();
        }
        if (chain == null) {
            throw new AccessControlException("Subject not authorized");
        }

        return CertUtil.generateCertificate(csr, lifetime, chain);
    }

    /**
     * Parses a byte array and constructs the corresponding
     * PKCS10CertificationRequest
     * 
     * @param code
     *            bytes containing the CSR
     * @return PKCS10CertificationRequest
     * @throws IOException
     */
    public static PKCS10CertificationRequest readCSR(byte[] code) throws IOException {
        byte[] crt = getCSR(code);
        return new PKCS10CertificationRequest(crt);
    }

    protected RegistryClient getRegistryClient() {
        return new RegistryClient();
    }

    static byte[] getCSR(byte[] certBuf) throws IOException {
        BufferedReader rdr = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(certBuf)));
        String line = rdr.readLine();
        StringBuilder base64 = new StringBuilder();
        while (line != null) {
            if (line.startsWith("-----BEGIN CERTIFICATE REQUEST-")) {
                LOGGER.debug(line);
                line = rdr.readLine();
                while (line != null && !line.startsWith("-----END CERTIFICATE REQUEST-")) {
                    LOGGER.debug(line + " (" + line.length() + ")");
                    base64.append(line.trim());
                    line = rdr.readLine();
                }
                LOGGER.debug(line);
                line = null; // break from outer loop
            } else
                line = rdr.readLine();
        }
        rdr.close();
        String encoded = base64.toString();
        LOGGER.debug("CERTIFICATE REQUEST: " + encoded);
        // now: base64 -> byte[]
        byte[] ret = Base64.decode(encoded);
        LOGGER.debug("RSA private key: " + ret.length + " bytes");

        return ret;
    }

    /**
     * Accesses the Certificate from the specified location.
     * 
     * @param location
     *            URL of the resource for obtaining the certificate.
     * @throws IOException
     */
    private X509CertificateChain downloadCertificate(URL location)
            throws AccessControlException, IOException, ResourceNotFoundException {
        Profiler profiler = new Profiler(this.getClass());

        byte[] certificate = null;
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            HttpDownload get = new HttpDownload(location, bos);

            get.run();
            if (get.getThrowable() != null) {
                if (get.getThrowable() instanceof IOException)
                    throw (IOException) get.getThrowable();

                if (get.getThrowable() instanceof AccessControlException)
                    throw (AccessControlException) get.getThrowable();

                if (get.getThrowable() instanceof FileNotFoundException)
                    throw new ResourceNotFoundException(get.getThrowable().getMessage(), get.getThrowable());

                throw new RuntimeException("unexpected failure download certificate", get.getThrowable());
            }

            certificate = bos.toByteArray();
            LOGGER.debug("Downloaded Certificate of size: " + certificate.length);
        } finally {
            profiler.checkpoint("downloadCertificate");
        }

        if (certificate != null && certificate.length > 0) {
            try {
                return SSLUtil.readPemCertificateAndKey(certificate);
            } catch (GeneralSecurityException e) {
                LOGGER.warn(e);
                throw new IllegalStateException("Could not parse the certificate", e);
            } finally {
                profiler.checkpoint("parseCertificate");
            }
        }

        throw new RuntimeException("No content in certificate");
    }
}