org.glite.authz.pep.pip.provider.ExtractorX509GenericPIP.java Source code

Java tutorial

Introduction

Here is the source code for org.glite.authz.pep.pip.provider.ExtractorX509GenericPIP.java

Source

// Copyright (c) FOM-Nikhef 2016-
// 
// 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.
//
// Authors:
// 2016-
// Rens Visser <rensv@nikhef.nl>
// NIKHEF Amsterdam, the Netherlands
// <grid-mw-security@nikhef.nl>

package org.glite.authz.pep.pip.provider;

import org.glite.authz.common.model.Attribute;
import org.glite.authz.common.model.Request;
import org.glite.authz.common.model.Subject;
import org.glite.authz.common.util.LazyList;
import org.glite.authz.pep.pip.PIPProcessingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.security.cert.X509Certificate;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x509.PolicyInformation;
import org.bouncycastle.util.encoders.Base64;

//import org.bouncycastle.asn1.x509.X509Extension;
//import org.bouncycastle.asn1.x509.Extension;

import eu.emi.security.authn.x509.impl.CertificateUtils;
import eu.emi.security.authn.x509.impl.CertificateUtils.Encoding;
import eu.emi.security.authn.x509.proxy.ProxyUtils;
import eu.emi.security.authn.x509.impl.OpensslNameUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStoreException;
import java.security.cert.CertificateException;
import java.util.List;

/**
 * @author Rens Visser
 * @version 1.0
 * @since 1.0
 * 
 *        The X509PIPPolicyOIDExtractor PIP extracts Policy OIDs and the issuer
 *        DN from incoming authorization requests. After extracting the required
 *        information, a existing XACML request is populated with the extracted
 *        information.
 * 
 *        PIP fails running when subjectid is used instead of Keyinfo.
 */
public class ExtractorX509GenericPIP extends AbstractPolicyInformationPoint {
    /**
     * Class logger used for debugging.
     */
    private final Logger log = LoggerFactory.getLogger(ExtractorX509GenericPIP.class);

    /**
     * String array of attribute(s) found in the incoming request that are
     * accepted: {@value}
     */
    private String[] acceptedAttributes_ = null;

    /**
     * Default String of CA policy OIDs attribute(s): {@value}
     */
    private final static String ATTRIBUTE_IDENTIFIER_CA_POLICY_OID = "http://authz-interop.org/xacml/subject/ca-policy-oid";

    /**
     * Default String of Issuer DN attribute(s): {@value}
     */
    private final static String ATTRIBUTE_IDENTIFIER_X509_ISSUER = "http://authz-interop.org/xacml/subject/subject-x509-issuer";

    /**
     * Default String of key-info attribute(s): {@value}
     */
    private final static String ATTRIBUTE_IDENTIFIER_KEY_INFO = "urn:oasis:names:tc:xacml:1.0:subject:key-info";

    /**
     * When acceptedAttributes is filled, then it makes the local variable
     * acceptedAttributes_ available to the whole class. If acceptedAttributes
     * is empty, an Exception is thrown and the PIP will not run.
     *
     * @param pipid
     *            The PIP identifier name
     * 
     * @param acceptedAttributes
     *            String array of accepted attributes.
     * 
     */
    public ExtractorX509GenericPIP(String pipid, String[] acceptedAttributes) {
        super(pipid);

        // Check to see if the String array String[] acceptedAttributes is not
        // empty.
        if (acceptedAttributes.length == 0) {
            new Exception("No accepted attributes have been supplied.");
        }

        acceptedAttributes_ = acceptedAttributes;
    }

    /**
     * The method that does all the work. The Argus framework makes sure that
     * when a PIP does apply to a request, the populateRequest(Request request)
     * method is always run.
     * 
     * When the incoming request has no subjects then this PIP will NOT run.
     * This PIP will throw an Exception when an attribute is not configured in
     * the pepd.ini configuration file. This PIP takes an incoming request, it
     * extracts the Issuer DN and the CA policy OIDs from the incoming request.
     *
     * 
     * @param request
     *            Request object containing all information of the incoming
     *            request.
     * 
     * @throws PIPProcessingException
        in case of errors
     * 
     * @return boolean
     */
    public boolean populateRequest(Request request) throws PIPProcessingException {
        // Declaration of used variables
        X509Certificate cert = null;
        Set<Subject> subjects = request.getSubjects();
        Set<Attribute> subjectAttributes = null;
        Boolean PIP_applied = false;

        // If subjects is empty, the PIP will not run.
        if (subjects.isEmpty()) {
            log.debug("Request has no subject!");
            return false;
        }

        try {

            for (Subject subject : subjects) {
                String acceptedID = null;
                subjectAttributes = subject.getAttributes();
                Attribute caPolicyOIDsInformation = new Attribute(ATTRIBUTE_IDENTIFIER_CA_POLICY_OID);
                caPolicyOIDsInformation.setDataType(Attribute.DT_STRING);

                Attribute issuerDNInformation = new Attribute(ATTRIBUTE_IDENTIFIER_X509_ISSUER);
                issuerDNInformation.setDataType(Attribute.DT_STRING);

                // Get the end-entity X509 certificate.
                cert = ProxyUtils.getEndUserCertificate(findPEMAttributeForConverson(subjectAttributes));

                // Loop over each accepted attribute .
                for (int i = 0; i < acceptedAttributes_.length; i++) {
                    acceptedID = acceptedAttributes_[i];
                    // Check if its an CA policy oid
                    if (acceptedID.equals(ATTRIBUTE_IDENTIFIER_CA_POLICY_OID)) {
                        PIP_applied = true;
                        List<String> policyOIDs = getPolicyOIDs(cert);

                        // List all found policy IDs
                        for (String str : policyOIDs) {
                            caPolicyOIDsInformation.getValues().add(str);
                        }
                        subjectAttributes.add(caPolicyOIDsInformation);

                        // Check if its an Issuer DN
                    } else if (acceptedID.equals(ATTRIBUTE_IDENTIFIER_X509_ISSUER)) {
                        PIP_applied = true;
                        String str = cert.getIssuerX500Principal().getName();
                        // Grab, convert and store the Issuer DN.
                        issuerDNInformation.getValues().add(OpensslNameUtils.convertFromRfc2253(str, false));
                        subjectAttributes.add(issuerDNInformation);
                        // If none of the above, abort!
                    } else {
                        throw new Exception("Non-handled attribute specified in ini file: " + acceptedID);
                    }
                }
            }
        } catch (Exception e) {
            log.debug(e.getMessage());
            throw new PIPProcessingException(e.getMessage());
            // e.printStackTrace();
        }

        return PIP_applied;
    }

    /**
     * Gets the policy OIDs from a {@link X509Certificate} and returns a list of
     * policy OIds in String object format.
     * 
     * @param cert
     *            The x509Certificate where the Policy OID(s) are extracted
     *            from.
     * @return a List of String instance. The list is filled with Policy OIDs
     *         strings.
     * 
     * @throws IOException
     *             Thrown when readObject method does not work.
     */
    @SuppressWarnings("resource") // Added to supres errors that are not useful
    protected List<String> getPolicyOIDs(X509Certificate cert) throws IOException {
        List<String> oidList = new LazyList<String>();

        String certPolicies = null;
        try {
            Class<?> extension = Class.forName("org.bouncycastle.asn1.x509.Extension");
            //          java.lang.reflect.Field field = extension.getField("certificatePolicies");
            //          Object fieldvalue = field.get(extension);
            //          certPolicies = ((org.bouncycastle.asn1.ASN1ObjectIdentifier)fieldvalue).toString();
            certPolicies = extension.getField("certificatePolicies").get(extension).toString();
        } catch (Exception e) { // NoSuchFieldException or ClassNotFoundException
            certPolicies = org.bouncycastle.asn1.x509.X509Extension.certificatePolicies.toString();
        }
        byte[] extvalue = cert.getExtensionValue(certPolicies);

        if (extvalue == null) {
            log.warn("No valid certificate policies found!");
            return null;
        }

        // Convert extension blob into DER octet string
        DEROctetString oct = (DEROctetString) (new ASN1InputStream(new ByteArrayInputStream(extvalue))
                .readObject());
        // ANS1 sequence generated from the DER octet string
        ASN1Sequence seq = (ASN1Sequence) new ASN1InputStream(new ByteArrayInputStream(oct.getOctets()))
                .readObject();

        /* Loop over all policy OIDs */
        for (int pos = 0; pos < seq.size(); pos++) {
            if (PolicyInformation.getInstance(seq.getObjectAt(pos)).getPolicyIdentifier().getId() != null) {
                oidList.add(PolicyInformation.getInstance(seq.getObjectAt(pos)).getPolicyIdentifier().getId());
            } else {
                throw new IOException("Policy does not exist!");
            }
        }

        return oidList;
    }

    /**
     * Creates a X509Certificate chain from a Attribute indicated by element
     * from a Set of Attributes. Does this by finding the Attribute ID
     * corresponding to the required attribute. Quits PIP when no PEM string is
     * found in the content. The incoming request must have a
     * urn:oasis:names:tc:xacml:1.0:subject:key-info attribute, if not quits the
     * PIP.
     *
     * @param attributes
     *            A {@link Set} filled with {@link Attribute}
     * 
     * @return a X509Certificate[] objects instance
     * @throws PIPProcessingException in case of error
     */
    protected X509Certificate[] findPEMAttributeForConverson(Set<Attribute> attributes)
            throws CertificateException, KeyStoreException, IOException, PIPProcessingException {
        //         throws PIPProcessingException {
        X509Certificate[] certificateChain = null;
        boolean hasExecuted = false;
        String checkThisStr = null;

        if (attributes.size() < 1) {
            throw new PIPProcessingException("Request without subject element!");
        }

        // Contains all certificates as a string
        for (Attribute attribute : attributes) {
            // Check whether the attribute ID is
            // "urn:oasis:names:tc:xacml:1.0:subject:key-info". When true
            // execute, else continue to next element.
            if (attribute.getId().equals(ATTRIBUTE_IDENTIFIER_KEY_INFO)) {
                Set<Object> attributeValues = attribute.getValues();
                // Used for other values
                for (Object attributeValue : attributeValues) {

                    // Checks if string contains pem formatted content
                    try {
                        checkThisStr = (String) attributeValue;
                        if (!isPEMString(checkThisStr)) {
                            checkThisStr = getCorrectPEMStingFromMallFormedPemString(checkThisStr);
                        }
                        certificateChain = pemConvertToX509CertificateChain(checkThisStr);
                        if (certificateChain.length == 0)
                            throw new PIPProcessingException("Empty certificate chain");
                        hasExecuted = true;
                    } catch (Exception e) {
                        throw new PIPProcessingException("The PEM string is not correct!");
                    }
                }
            }
        }

        if (hasExecuted == false) {
            throw new PIPProcessingException(
                    "No pem String content in request! PIP ExtractorX509GenericPIP quited running...");
        }
        return certificateChain;
    }

    /**
     * Converts a PEM String to a X509Certificate object array. Utterly fails
     * and throws an exception when NO PEM String is provided.
     *
     * @param pem
     *            A PEM formatted String
     * @return a X509Certificate[] chain
     * 
     * @throws IOException
     * @throws KeyStoreException
     * @throws CertificateException
     */
    private X509Certificate[] pemConvertToX509CertificateChain(String pem)
            throws CertificateException, IOException, KeyStoreException {
        // Convert string to a UTF-8 encoded InputStream PEM object.
        InputStream pemReader = new ByteArrayInputStream(pem.getBytes(StandardCharsets.UTF_8));
        // Convert InputStream PEM object to X509certificate object chain
        return CertificateUtils.loadCertificateChain(pemReader, Encoding.PEM);
    }

    private Boolean isPEMString(String str) {
        if (str.contains("-----BEGIN CERTIFICATE-----")) {
            return true;
        }
        return false;
    }

    private String getCorrectPEMStingFromMallFormedPemString(String str) {
        StringBuilder strBuilder = new StringBuilder();
        String[] strArray = null;
        strArray = str.split(",");

        for (String strTMP : strArray) {
            strBuilder.append("-----BEGIN CERTIFICATE-----\n" + strTMP + "\n-----END CERTIFICATE-----");
        }

        return strBuilder.toString();
    }
}