org.italiangrid.voms.asn1.VOMSACUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.italiangrid.voms.asn1.VOMSACUtils.java

Source

/**
 * Copyright (c) Istituto Nazionale di Fisica Nucleare, 2006-2014.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.italiangrid.voms.asn1;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DERObject;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x509.Attribute;
import org.bouncycastle.asn1.x509.AttributeCertificate;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.IetfAttrSyntax;
import org.bouncycastle.asn1.x509.Target;
import org.bouncycastle.asn1.x509.TargetInformation;
import org.bouncycastle.asn1.x509.Targets;
import org.bouncycastle.asn1.x509.X509CertificateStructure;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.cert.X509AttributeCertificateHolder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.provider.X509CertificateObject;
import org.italiangrid.voms.VOMSAttribute;
import org.italiangrid.voms.VOMSError;
import org.italiangrid.voms.VOMSGenericAttribute;
import org.italiangrid.voms.ac.impl.VOMSAttributesImpl;
import org.italiangrid.voms.ac.impl.VOMSGenericAttributeImpl;

/**
 * A set of VOMS AC handling utilities.
 * 
 * @author Andrea Ceccanti
 *
 */
public class VOMSACUtils implements VOMSConstants {

    public static final String POLICY_AUTHORITY_SEP = "://";

    /**
     * Returns the VOMS extension, if present, in a given certificate
     * 
     * @param cert
     *          the {@link X509Certificate} where the extension will be searched
     * @return the DER-encoded octet string of the extension value or null if it
     *         is not present.
     */
    public static byte[] getVOMSExtensionFromCertificate(X509Certificate cert) {

        return cert.getExtensionValue(VOMSConstants.VOMS_EXTENSION_OID.getId());
    }

    /**
     * Deserializes the VOMS Attribute certificates in a given certificate
     * extension
     * 
     * @param vomsExtension
     *          the VOMS extension
     * @return the possibly empty {@link List} of {@link AttributeCertificate}
     *         extracted from a given extension
     * @throws IOException
     *          in case of deserialization errors
     */
    public static List<AttributeCertificate> getACsFromVOMSExtension(byte[] vomsExtension) throws IOException {

        List<AttributeCertificate> acs = null;

        if (vomsExtension == null)
            return Collections.emptyList();

        acs = new ArrayList<AttributeCertificate>();

        // Convert extension to a DEROctetString
        ASN1InputStream asn1Stream = new ASN1InputStream(new ByteArrayInputStream(vomsExtension));
        byte[] payload = ((DEROctetString) asn1Stream.readObject()).getOctets();

        asn1Stream.close();

        asn1Stream = new ASN1InputStream(new ByteArrayInputStream(payload));

        // VOMS extension is SEQUENCE of SET of AttributeCertificate
        // now, SET is an ordered sequence, and an AC is a sequence as
        // well -- thus the three nested ASN.1 sequences below...
        ASN1Sequence baseSequence = (ASN1Sequence) asn1Stream.readObject();
        asn1Stream.close();

        @SuppressWarnings("unchecked")
        Enumeration<ASN1Sequence> setSequence = baseSequence.getObjects();

        while (setSequence.hasMoreElements()) {

            ASN1Sequence acSequence = setSequence.nextElement();

            @SuppressWarnings("unchecked")
            Enumeration<ASN1Sequence> theACs = acSequence.getObjects();

            while (theACs.hasMoreElements()) {

                AttributeCertificate parsedAC = new AttributeCertificate(theACs.nextElement());
                acs.add(parsedAC);
            }
        }

        return acs;
    }

    /**
     * Deserializes the VOMS Attribute certificates, if present, in a given
     * certificate passed as argument
     * 
     * @param cert
     *          the {@link X509Certificate} where the ACs will be searched
     * @return the possibly empty {@link List} of {@link AttributeCertificate}
     *         objects extracted from the VOMS extension
     * @throws IOException
     *           in case of deserialization errors
     */
    public static List<AttributeCertificate> getACsFromCertificate(X509Certificate cert) throws IOException {

        return getACsFromVOMSExtension(getVOMSExtensionFromCertificate(cert));
    }

    /**
     * Deserializes the FQANs contained in a {@link IetfAttrSyntax} object
     * 
     * @param attr
     *          the {@link IetfAttrSyntax} attribute syntax object containing the
     *          VOMS extension
     * @return a {@link List} of FQANs
     */
    private static List<String> deserializeFQANs(IetfAttrSyntax attr) {

        if (attr.getValueType() != IetfAttrSyntax.VALUE_OCTETS)
            raiseACNonConformantError("unsupported attribute values encoding.");

        List<String> fqans = new ArrayList<String>();

        ASN1OctetString[] values = (ASN1OctetString[]) attr.getValues();

        for (ASN1OctetString s : values)
            fqans.add(new String(s.getOctets()));

        return fqans;
    }

    @SuppressWarnings("rawtypes")
    private static List<String> deserializeACTargets(X509AttributeCertificateHolder ac) {

        List<String> targets = new ArrayList<String>();

        X509Extension targetExtension = ac.getExtension(X509Extension.targetInformation);

        if (targetExtension == null)
            return targets;

        TargetInformation ti = TargetInformation.getInstance((ASN1Sequence) targetExtension.getParsedValue());

        // Only one Targets according to RFC 3281
        Targets asn1TargetContainer = ti.getTargetsObjects()[0];

        // The deserialization has to be done by hand since it seems VOMS
        // does not correctly encode the ACTargets extension...
        ASN1Sequence targetSequence = (ASN1Sequence) asn1TargetContainer.getDERObject();
        Target[] asn1Targets = new Target[targetSequence.size()];

        int count = 0;

        for (Enumeration e = targetSequence.getObjects(); e.hasMoreElements();) {

            // There's one sequence more than expected here that makes
            // the bc constructor fail...
            ASN1Sequence seq = (ASN1Sequence) e.nextElement();
            ASN1TaggedObject val = (ASN1TaggedObject) seq.getObjectAt(0);
            asn1Targets[count++] = Target.getInstance(val);
        }

        // Extract the actual string
        for (Target t : asn1Targets) {

            GeneralName targetURI = t.getTargetName();

            if (targetURI.getTagNo() != GeneralName.uniformResourceIdentifier)
                raiseACNonConformantError("wrong AC target extension encoding. Only URI targets are supported.");

            String targetString = ((DERIA5String) targetURI.getName()).getString();
            targets.add(targetString);
        }
        return targets;
    }

    private static void raiseACNonConformantError(String errorString) {

        throw new VOMSError("Non conformant VOMS Attribute certificate: " + errorString);
    }

    /**
     * Peforms some sanity checks on the format of the policy authority field
     * found in a VOMS extension. The enforced format is: vo://host:port
     * 
     * @param attr
     *          the {@link IetfAttrSyntax} attribute syntax object containing the
     *          VOMS extension
     * @return the validated policy authority as a {@link String}
     */
    private static String policyAuthoritySanityChecks(IetfAttrSyntax attr) {

        // The policy authority value is encoded as a DERIA5String
        String policyAuthority = ((DERIA5String) attr.getPolicyAuthority().getNames()[0].getName()).getString();

        // PolicyAuthority scheme: <vo name>://<hostname>:<port>
        int index = policyAuthority.indexOf(POLICY_AUTHORITY_SEP);

        if ((index < 0) || (index == policyAuthority.length() - 1))
            raiseACNonConformantError("unsupported policy authority encoding '" + policyAuthority + "'");

        return policyAuthority;

    }

    /**
     * Deserializes the information in a list of VOMS attribute certificates.
     * 
     * @param acs
     *          a {@link List} of VOMS acs
     * @return a possibly empty list of {@link VOMSAttribute}
     */
    public static List<VOMSAttribute> deserializeVOMSAttributes(List<AttributeCertificate> acs) {

        if (acs == null || acs.size() == 0)
            return Collections.emptyList();

        List<VOMSAttribute> attributes = new ArrayList<VOMSAttribute>();
        for (AttributeCertificate a : acs) {
            attributes.add(deserializeVOMSAttributes(a));
        }

        return attributes;
    }

    /**
     * Deserializes the information in a VOMS attribute certificate.
     * 
     * @param ac
     *          a VOMS {@link AttributeCertificate}
     * @return a {@link VOMSAttribute} object which provides more convenient
     *         access to the VOMS authorization information
     */
    public static VOMSAttribute deserializeVOMSAttributes(AttributeCertificate ac) {

        VOMSAttributesImpl attrs = new VOMSAttributesImpl();

        X509AttributeCertificateHolder acHolder = new X509AttributeCertificateHolder(ac);
        Attribute[] asn1Attrs = acHolder.getAttributes(VOMS_FQANS_OID);

        for (Attribute a : asn1Attrs) {
            DERObject theVOMSDerObject = a.getAttributeValues()[0].getDERObject();
            IetfAttrSyntax attrSyntax = new IetfAttrSyntax(ASN1Sequence.getInstance(theVOMSDerObject));

            String policyAuthority = policyAuthoritySanityChecks(attrSyntax);

            // The policy authority string has the following format:
            // <vo name>://<hostname>:<port>

            attrs.setVO(policyAuthority.substring(0, policyAuthority.indexOf(POLICY_AUTHORITY_SEP)));
            attrs.setHost(policyAuthority.substring(policyAuthority.indexOf(POLICY_AUTHORITY_SEP) + 3,
                    policyAuthority.lastIndexOf(":")));
            attrs.setPort(Integer.parseInt(policyAuthority.substring(policyAuthority.lastIndexOf(":") + 1)));

            attrs.setFQANs(deserializeFQANs(attrSyntax));

            attrs.setNotBefore(acHolder.getNotBefore());
            attrs.setNotAfter(acHolder.getNotAfter());
            attrs.setSignature(acHolder.getSignature());
            attrs.setGenericAttributes(deserializeGAs(acHolder));
            attrs.setAACertificates(deserializeACCerts(acHolder));
            attrs.setTargets(deserializeACTargets(acHolder));

            attrs.setVOMSAC(acHolder);

            try {

                attrs.setIssuer(new X500Principal(acHolder.getIssuer().getNames()[0].getEncoded()));
                attrs.setHolder(new X500Principal(acHolder.getHolder().getIssuer()[0].getEncoded()));
                attrs.setHolderSerialNumber(acHolder.getHolder().getSerialNumber());

            } catch (IOException e) {
                throw new VOMSError("Error parsing attribute certificate issuer  or holder name: " + e.getMessage(),
                        e);
            }
        }

        return attrs;
    }

    /**
     * Deserializes the VOMS generic attributes
     * 
     * @param ac
     *          the VOMS {@link X509AttributeCertificateHolder}
     * @return the {@link List} of {@link VOMSGenericAttribute} contained in the
     *         ac
     */
    private static List<VOMSGenericAttribute> deserializeGAs(X509AttributeCertificateHolder ac) {

        List<VOMSGenericAttribute> gas = new ArrayList<VOMSGenericAttribute>();

        X509Extension gasExtension = ac.getExtension(VOMS_GENERIC_ATTRS_OID);

        if (gasExtension == null)
            return gas;

        // SEQUENCE of TagList - contains just one taglist element
        ASN1Sequence tagContainerSeq = (ASN1Sequence) gasExtension.getParsedValue();
        if (tagContainerSeq.size() != 1)
            raiseACNonConformantError("unsupported generic attributes container format.");

        // TagList - this also should be a sigle element sequence
        ASN1Sequence tagListSeq = (ASN1Sequence) tagContainerSeq.getObjectAt(0);
        if (tagListSeq.size() > 1)
            raiseACNonConformantError("unsupported taglist format.");

        // This TagList sequence is empty, gLite 3.2 VOMS versions had a bug
        // that added the extension even there were no attributes encoded...
        if (tagListSeq.size() == 0)
            return gas;

        // Down one level
        tagListSeq = (ASN1Sequence) tagListSeq.getObjectAt(0);

        // TODO: check policyAuthority!!
        // GeneralNames policyAuthority =
        // GeneralNames.getInstance(tagListSeq.getObjectAt(0));

        // tags SEQUENCE OF Tag
        ASN1Sequence tags = (ASN1Sequence) tagListSeq.getObjectAt(1);

        @SuppressWarnings("unchecked")
        Enumeration<ASN1Sequence> e = tags.getObjects();
        while (e.hasMoreElements()) {

            ASN1Sequence theActualTag = e.nextElement();

            if (theActualTag.size() != 3)
                raiseACNonConformantError("unsupported tag format.");

            VOMSGenericAttributeImpl attribute = new VOMSGenericAttributeImpl();

            attribute.setName(new String(DEROctetString.getInstance(theActualTag.getObjectAt(0)).getOctets()));
            attribute.setValue(new String(DEROctetString.getInstance(theActualTag.getObjectAt(1)).getOctets()));
            attribute.setContext(new String(DEROctetString.getInstance(theActualTag.getObjectAt(2)).getOctets()));

            gas.add(attribute);
        }

        return gas;
    }

    /**
     * Deserializes the VOMS ACCerts extension
     * 
     * @param ac
     *          the VOMS {@link X509AttributeCertificateHolder}
     * @return the parsed array of {@link X509Certificate}
     */
    private static X509Certificate[] deserializeACCerts(X509AttributeCertificateHolder ac) {

        List<X509Certificate> certs = new ArrayList<X509Certificate>();

        X509Extension e = ac.getExtension(VOMS_CERTS_OID);

        if (e == null)
            return null;

        ASN1Sequence certSeq = (ASN1Sequence) e.getParsedValue();
        if (certSeq.size() != 1)
            raiseACNonConformantError("unsupported accerts format.");

        // Down one level
        certSeq = (ASN1Sequence) certSeq.getObjectAt(0);

        @SuppressWarnings("unchecked")
        Enumeration<DERSequence> encodedCerts = certSeq.getObjects();

        CertificateFactory cf = null;

        try {
            cf = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
        } catch (Exception ex) {
            throw new VOMSError("Certificate factory creation error: " + ex.getMessage(), ex);
        }

        while (encodedCerts.hasMoreElements()) {

            DERSequence s = encodedCerts.nextElement();
            X509CertificateObject certObj = null;
            byte[] certData = null;
            X509Certificate theCert = null;

            try {

                certObj = new X509CertificateObject(
                        X509CertificateStructure.getInstance(ASN1Sequence.getInstance(s)));
                certData = certObj.getEncoded();
                theCert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certData));

            } catch (CertificateParsingException ex) {
                throw new VOMSError("Certificate parsing error: " + ex.getMessage(), ex);
            } catch (CertificateEncodingException ex) {
                throw new VOMSError("Certificate encoding error: " + ex.getMessage(), ex);
            } catch (CertificateException ex) {
                throw new VOMSError("Error generating certificate from parsed data: " + ex.getMessage(), ex);
            }

            certs.add(theCert);
        }

        return certs.toArray(new X509Certificate[certs.size()]);
    }

    private VOMSACUtils() {

    }
}