Commit 8e069361 authored by Patrick Dowler's avatar Patrick Dowler
Browse files

moved CDP command-line client from internal codebase, added bcprov.jar to classpath

parent 9279e74c
Loading
Loading
Loading
Loading
+14 −4
Original line number Diff line number Diff line
@@ -90,20 +90,30 @@

  <!-- JAR files to be included in classpath and war file -->
  <property name="lib.cadcUtil" value="${lib}/cadcUtil.jar" />
  <property name="lib.cadcRegistryClient" value="${lib}/cadcRegistryClient.jar" />
  <property name="ext.log4j"       value="${ext.lib}/log4j.jar" />
  <property name="ext.bouncy"       value="${ext.lib}/bcprov.jar" />
  <!-- end of project properties -->

  <!-- JAR files to be included in classpath for compilation -->
  <property name="jars"
            value="${lib.cadcUtil}:${ext.log4j}" />
  <property name="jars" value="${lib.cadcUtil}:${lib.cadcRegistryClient}:${ext.log4j}:${ext.bouncy}" />
  <property name="manifest.jars" value="${lib.cadcUtil} ${lib.cadcRegistryClient} ${ext.log4j} ${ext.bouncy}" />
  
  <target name="build" depends="compile">

  <target name="build" depends="compile,manifest">
      <jar jarfile="${build}/lib/${project}.jar"
                    basedir="${build}/class"
                    update="no">
                    update="no" manifest="${build}/tmp/${project}.mf">
      </jar>
	  
  </target>
  
  <target name="manifest">
    <manifest file="${build}/tmp/${project}.mf" mode="replace">
      <attribute name="Main-Class" value="ca.nrc.cadc.cred.client.Main"/>
      <attribute name="Class-Path" value="${manifest.jars}" />
    </manifest>
  </target>
	<target name="test">
		<echo message="WARNING: no tests implemented" />
	</target>
+230 −0
Original line number Diff line number Diff line

package ca.nrc.cadc.cred.client;

import java.io.IOException;
import java.io.Writer;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Random;
import java.util.TimeZone;

import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;

import ca.nrc.cadc.auth.X509CertificateChain;

/**
 * Utilities for certificate operations
 */
public class CertUtil
{

    /**
     * Method that generates an X509 proxy certificate
     * 
     * @param csr
     *            CSR for the certificate
     * @param lifetime
     *            lifetime of the certificate in SECONDS
     * @param chain
     *            certificate used to sign the proxy certificate
     * @return generated proxy certificate
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeyException
     * @throws CertificateParsingException
     * @throws CertificateEncodingException
     * @throws SignatureException
     * @throws CertificateNotYetValidException
     * @throws CertificateExpiredException
     */
    public static X509Certificate generateCertificate(
            PKCS10CertificationRequest csr, int lifetime,
            X509CertificateChain chain) throws NoSuchAlgorithmException,
            NoSuchProviderException, InvalidKeyException,
            CertificateParsingException, CertificateEncodingException,
            SignatureException, CertificateExpiredException,
            CertificateNotYetValidException
    {
        X509Certificate issuerCert = chain.getChain()[0];
        PrivateKey issuerKey = chain.getPrivateKey();

        Security.addProvider(new BouncyCastleProvider());

        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();

        certGen.setSerialNumber(BigInteger.valueOf(System
                .currentTimeMillis()));
        certGen.setIssuerDN(issuerCert.getSubjectX500Principal());

        // generate the proxy DN as the issuerDN + CN=random number
        Random rand = new Random();
        String issuerDN = issuerCert.getSubjectX500Principal().getName(
                X500Principal.RFC2253);
        String delegDN = String.valueOf(Math.abs(rand.nextInt()));
        String proxyDn = "CN=" + delegDN + "," + issuerDN;
        certGen.setSubjectDN(new X500Principal(proxyDn));

        // set validity
        GregorianCalendar date = new GregorianCalendar(TimeZone
                .getTimeZone("GMT"));
        // Start date. Allow for a sixty five minute clock skew here.
        date.add(Calendar.MINUTE, -65);
        Date beforeDate = date.getTime();
        for (X509Certificate currentCert : chain.getChain())
        {
            if (beforeDate.before(currentCert.getNotBefore()))
            {
                beforeDate = currentCert.getNotBefore();
            }
        }
        certGen.setNotBefore(beforeDate);

        // End date.
        // If hours = 0, then cert lifetime is set to that of user cert
        if (lifetime <= 0)
        {
            // set the validity of certificates as the minimum
            // of the certificates in the chain
            Date afterDate = issuerCert.getNotAfter();
            for (X509Certificate currentCert : chain.getChain())
            {
                if (afterDate.after(currentCert.getNotAfter()))
                {
                    afterDate = currentCert.getNotAfter();
                }
            }
            certGen.setNotAfter(afterDate);
        }
        else
        {
            // check the validity of the signing certificate
            date.add(Calendar.MINUTE, 5);
            date.add(Calendar.SECOND, lifetime);
            for (X509Certificate currentCert : chain.getChain())
            {
                currentCert.checkValidity(date.getTime());
            }

            certGen.setNotAfter(date.getTime());
        }

        certGen.setPublicKey(csr.getPublicKey());
        certGen.setSignatureAlgorithm(issuerCert.getSigAlgName());

        // extensions
        // add ProxyCertInfo extension to the new cert

        certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(
                KeyUsage.digitalSignature | KeyUsage.keyEncipherment));

        certGen.addExtension(X509Extensions.AuthorityKeyIdentifier,
                false, new AuthorityKeyIdentifierStructure(issuerCert));

        certGen.addExtension(X509Extensions.SubjectKeyIdentifier,

        false, new SubjectKeyIdentifierStructure(csr.getPublicKey("BC")));

        certGen.addExtension(X509Extensions.BasicConstraints, true,
                new BasicConstraints(false));

        // add the Proxy Certificate Information
        // I expect this code to be removed once support to proxy
        // certificates is provided in Bouncy Castle.

        // create a proxy policy
        // types of proxy certificate policies - see RFC3820
        // impersonates the user
        final DERObjectIdentifier IMPERSONATION = new DERObjectIdentifier(
                "1.3.6.1.5.5.7.21.1");
        // independent
        // final DERObjectIdentifier INDEPENDENT = new
        // DERObjectIdentifier(
        // "1.3.6.1.5.5.7.21.2");
        // defined by a policy language
        // final DERObjectIdentifier LIMITED = new DERObjectIdentifier(
        // "1.3.6.1.4.1.3536.1.1.1.9");

        ASN1EncodableVector policy = new ASN1EncodableVector();
        policy.add(IMPERSONATION);

        // pathLengthConstr (RFC3820)
        // The pCPathLenConstraint field, if present, specifies the
        // maximum
        // depth of the path of Proxy Certificates that can be signed by
        // this
        // Proxy Certificate. A pCPathLenConstraint of 0 means that this
        // certificate MUST NOT be used to sign a Proxy Certificate. If
        // the
        // pCPathLenConstraint field is not present then the maximum proxy
        // path
        // length is unlimited. End entity certificates have unlimited
        // maximum
        // proxy path lengths.
        // DERInteger pathLengthConstr = new DERInteger(100);

        // create the proxy certificate information
        ASN1EncodableVector vec = new ASN1EncodableVector();
        // policy.add(pathLengthConstr);
        vec.add(new DERSequence(policy));

        // OID
        final DERObjectIdentifier OID = new DERObjectIdentifier(
                "1.3.6.1.5.5.7.1.14");
        certGen.addExtension(OID, true, new DERSequence(vec));

        return certGen.generate(issuerKey, "BC");
    }

    /**
     * @param chain certificate
     * @param writer writer use to write the generated PEM certificate
     * @throws IOException
     */
    public static void writePEMCertificateAndKey(
            X509CertificateChain chain, Writer writer)
            throws IOException
    {
        if (chain == null)
            throw new IllegalArgumentException("Null certificate chain");
        if (writer == null)
            throw new IllegalArgumentException("Null writer");

        PEMWriter pemWriter = new PEMWriter(writer);
        // write the first certificate first
        pemWriter.writeObject(chain.getChain()[0]);
        // then the key
        pemWriter.writeObject(chain.getPrivateKey());
        // and finally the rest of the certificates in the chain
        for (int i = 1; i < chain.getChain().length; i++)
        {
            pemWriter.writeObject(chain.getChain()[i]);
        }        
        pemWriter.flush();
    }
}
+753 −0

File added.

Preview size limit exceeded, changes collapsed.

+311 −0
Original line number Diff line number Diff line

package ca.nrc.cadc.cred.client;

import java.net.URI;
import java.net.URL;
import java.security.PrivilegedAction;
import java.security.cert.X509Certificate;

import javax.security.auth.Subject;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import ca.nrc.cadc.auth.CertCmdArgUtil;
import ca.nrc.cadc.reg.client.RegistryClient;
import ca.nrc.cadc.util.ArgumentMap;
import ca.nrc.cadc.util.Log4jInit;

public class Main implements PrivilegedAction<Boolean>
{
    private static Logger logger = Logger.getLogger(Main.class);

    public static final String ARG_HELP = "help";
    public static final String ARG_VERBOSE = "verbose";
    public static final String ARG_DEBUG = "debug";
    public static final String ARG_H = "h";
    public static final String ARG_V = "v";
    public static final String ARG_D = "d";
    public static final String ARG_VIEW_CMD = "view";
    public static final String ARG_DELEGATE_CMD = "delegate";
    public static final String ARG_VALID_DAYS = "daysvalid";

    // authenticated subject
    private static Subject subject;

    private String baseURL;
    private RegistryClient registryClient = new RegistryClient();
    private CredPublicClient client;

    private int daysValid;

    private static final int INIT_STATUS = 1; // exit code for
    // initialisation failure
    private static final int NET_STATUS = 2; // exit code for

    // client-server failures

    // Operations on Cred client
    public enum Operation {
        DELEGATE, VIEW
    };

    private Operation operation; // current operation on Cred client

    public static final String SERVICE_ID = "ivo://cadc.nrc.ca/cred";

    /**
     * Main class for accessing CDP
     * 
     * @param args
     */
    public static void main(String[] args)
    {
        ArgumentMap argMap = new ArgumentMap(args);

        if (argMap.isSet(ARG_HELP) || argMap.isSet(ARG_H))
        {
            usage();
            System.exit(0);
        }

        // Set debug mode
        if (argMap.isSet(ARG_DEBUG) || argMap.isSet(ARG_D))
        {
            Log4jInit.setLevel("ca.nrc.cadc.cred.client", Level.DEBUG);
        }
        else if (argMap.isSet(ARG_VERBOSE) || argMap.isSet(ARG_V))
        {
            Log4jInit.setLevel("ca.nrc.cadc.cred.client", Level.INFO);
        }
        else
            Log4jInit.setLevel("ca", Level.WARN);

        Main command = new Main();
        
        try
        {
            command.validateCommand(argMap);
        }
        catch (IllegalArgumentException ex)
        {
            msg("illegal argument(s): " + ex.getMessage());
            msg("");
            usage();
            System.exit(INIT_STATUS);
        }

        try
        {
            command.init(argMap);
            Subject.doAs(subject, command);
        }
        catch (Throwable t)
        {
            logger.error("unexpected failure", t);
            System.exit(NET_STATUS);
        }
        System.exit(0);

    }

    /**
     * Runs the task in Subject's context. Needed by the PrivilegedAction
     * interface
     * 
     * @return true if successfull, false otherwise
     */
    public Boolean run()
    {
        logger.info("run - START");
        if (this.operation.equals(Operation.DELEGATE))
        {
            doDelegate();
        }
        if (this.operation.equals(Operation.VIEW))
        {
            doView();
        }
        logger.info("run - DONE");
        return new Boolean(true);
    }

    /**
     * Executes delegate command
     */
    private void doDelegate()
    {
        try
        {
            client.delegate(null, daysValid);
            msg("Certificate updated");
        }
        catch (Exception e)
        {
e.printStackTrace();
            logger.error("failed to delegate");
            logger.error("reason: " + e.getMessage());
            System.exit(NET_STATUS);
        }

    }

    /**
     * Executes view command
     */
    private void doView()
    {
        try
        {
            X509Certificate[] certs = client.getCertificate(null);
            certs[0].checkValidity();
            msg("Found valid certificate");
            msg("Certificate Subject DN: "
                    + certs[0].getSubjectX500Principal().getName());
            msg("Certificate Expiry Date: " + certs[0].getNotAfter());
            msg("Certificate Details: " + certs[0].toString());
        }
        catch (Exception e)
        {
            logger.error("failed to delegate");
            logger.error("reason: " + e.getMessage());
            System.exit(NET_STATUS);
        }

    }

    /**
     * Validates the command line operations
     * 
     * @param argMap
     */
    private void validateCommand(ArgumentMap argMap)
            throws IllegalArgumentException
    {
        int numOp = 0;
        if (argMap.isSet(ARG_VIEW_CMD))
        {
            operation = Operation.VIEW;
            numOp++;
        }
        if (argMap.isSet(ARG_DELEGATE_CMD))
        {
            operation = Operation.DELEGATE;
            numOp++;
            String validDaysStr = argMap.getValue(ARG_VALID_DAYS);
            if (validDaysStr != null)
            {
                boolean valid = true;
                try
                {
                    daysValid = Integer.parseInt(validDaysStr);
                    if (daysValid < 1)
                    {
                        valid = false;
                    }
                }
                catch (NumberFormatException ex)
                {
                    valid = false;
                }
                if (valid == false)
                {
                    logger.error(ARG_VALID_DAYS
                            + " must be a positive integer value");
                    usage();
                    System.exit(INIT_STATUS);
                }
            }
            else
            {
                logger.error(ARG_VALID_DAYS
                        + " argument missing");
                usage();
                System.exit(INIT_STATUS);
            }
        }
        if (numOp != 1)
        {
            logger.error("Must specify one operation");
            usage();
            System.exit(INIT_STATUS);
        }

        return;
    }

    /**
     * Initializes of the base URL for the service
     * 
     * @param argMap
     */
    private void init(ArgumentMap argMap)
    {
        try
        {
            subject = CertCmdArgUtil.initSubject(argMap);
        }
        catch (Exception ex)
        {
            logger.error("failed to initialise SSL from certificates: "
                    + ex.getMessage());
            if (logger.getLevel() == Level.DEBUG)
            {
                ex.printStackTrace();
            }
            if (ex instanceof IllegalArgumentException)
            {
                usage();
            }
            System.exit(INIT_STATUS);
        }

        try
        {
            URL baseURL = registryClient.getServiceURL(
                    new URI(SERVICE_ID), "https");
            if (baseURL == null)
            {
                logger.error("failed to find service URL for "
                        + SERVICE_ID);
                System.exit(INIT_STATUS);
            }
            this.baseURL = baseURL.toString();
            this.client = new CredPublicClient(new URL(this.baseURL));
        }
        catch (Exception e)
        {
            logger.error("failed to find service URL for " + SERVICE_ID);
            logger.error("reason: " + e.getMessage());
            System.exit(INIT_STATUS);
        }

        logger.info("server uri: " + SERVICE_ID);
        logger.info("base url: " + this.baseURL);
    }

    /**
     * Formats the usage message.
     */
    public static void usage()
    {
        String[] um = {
                "Usage: java -jar cadcCDP.jar --view|(--delegate --daysvalid=<days>) [-v|--verbose|-d|--debug]",
                CertCmdArgUtil.getCertArgUsage(),
                "                                                                                                  ",
                "Help:                                                                                             ",
                "java -jar cadcCDP.jar <-h | --help>                                                        ",
                "                                                                                                  " };

        for (String line : um)
            msg(line);

    }

    // encapsulate all messages to console here
    private static void msg(String s)
    {
        System.out.println(s);
    }

}