org.ejbca.core.model.ca.certextensions.BasicCertificateExtension.java Source code

Java tutorial

Introduction

Here is the source code for org.ejbca.core.model.ca.certextensions.BasicCertificateExtension.java

Source

/*************************************************************************
 *                                                                       *
 *  EJBCA: The OpenSource Certificate Authority                          *
 *                                                                       *
 *  This software is free software; you can redistribute it and/or       *
 *  modify it under the terms of the GNU Lesser General Public           *
 *  License as published by the Free Software Foundation; either         *
 *  version 2.1 of the License, or any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/

package org.ejbca.core.model.ca.certextensions;

import java.math.BigInteger;
import java.security.PublicKey;
import java.util.Arrays;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERBoolean;
import org.bouncycastle.asn1.DEREncodable;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERPrintableString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.util.encoders.Hex;
import org.ejbca.core.model.InternalResources;
import org.ejbca.core.model.ca.caadmin.CA;
import org.ejbca.core.model.ca.certificateprofiles.CertificateProfile;
import org.ejbca.core.model.ra.ExtendedInformation;
import org.ejbca.core.model.ra.UserDataVO;

/**
 * The default basic certificate extension that has two property.
 * 
 * 'value'    : The value returned
 * 'encoding' : How the value is encoded.
 * 
 * Optionally, a new property can be defined:
 *
 * 'nvalues' : number of values of type 'encoding'
 *
 * Thus, the extension will be of type 'SEQUENCE OF ENCODING'
 * with a size of nvalues. The members will be:
 *  'value1', 'value2' and so on.
 *  
 * Optionally, an other property can be defined:
 * 
 *  'dynamic' : true/false if the extension value(s) should be allowed to be 
 *              overridden by value(s) put as extensiondata in 
 *              ExtendedInformation. Default is 'false'.
 *
 *
 * See documentation for more information.
 * 
 * @author Philip Vendil 2007 jan 5
 * @author Miguel Tormo  2008 oct 24
 *
 * @version $Id: BasicCertificateExtension.java 12665 2011-09-21 14:28:33Z netmackan $
 */

public class BasicCertificateExtension extends CertificateExtension {

    private static final Logger log = Logger.getLogger(BasicCertificateExtension.class);

    private static final InternalResources intres = InternalResources.getInstance();

    private static String ENCODING_DERBITSTRING = "DERBITSTRING";
    private static String ENCODING_DERINTEGER = "DERINTEGER";
    private static String ENCODING_DEROCTETSTRING = "DEROCTETSTRING";
    private static String ENCODING_DERBOOLEAN = "DERBOOLEAN";
    private static String ENCODING_DERPRINTABLESTRING = "DERPRINTABLESTRING";
    private static String ENCODING_DERUTF8STRING = "DERUTF8STRING";
    private static String ENCODING_DERIA5STRING = "DERIA5STRING";
    private static String ENCODING_DERNULL = "DERNULL";
    private static String ENCODING_DEROBJECT = "DEROBJECT";
    private static String ENCODING_DEROID = "DERBOJECTIDENTIFIER";

    /** 
      * The value is expected to by hex encoded and is added as an byte array 
      * as the extension value. 
      **/
    private static String ENCODING_RAW = "RAW";

    // Defined Properties
    private static String PROPERTY_VALUE = "value";
    private static String PROPERTY_ENCODING = "encoding";
    private static String PROPERTY_NVALUES = "nvalues";
    private static String PROPERTY_DYNAMIC = "dynamic";

    /**
     * Returns the defined property 'value' in the encoding 
     * specified in 'encoding'.
     * 
     * @param userData not used
     * @param ca not used
     * @param certProfile not used
     * @see org.ejbca.core.model.ca.certextensions.CertificateExtension#getValue(org.ejbca.core.model.ra.UserDataVO, org.ejbca.core.model.ca.caadmin.CA, org.ejbca.core.model.ca.certificateprofiles.CertificateProfile, PublicKey)
     */
    private DEREncodable parseValue(String encoding, String value)
            throws CertificateExtentionConfigurationException, CertificateExtensionException {

        DEREncodable toret = null;

        if (!encoding.equalsIgnoreCase(ENCODING_DERNULL) && (value == null || value.trim().equals(""))) {
            throw new CertificateExtentionConfigurationException(
                    intres.getLocalizedMessage("certext.basic.incorrectvalue", Integer.valueOf(getId()), getOID()));
        }

        if (encoding.equalsIgnoreCase(ENCODING_DERBITSTRING)) {
            toret = parseDERBitString(value);
        } else if (encoding.equalsIgnoreCase(ENCODING_DERINTEGER)) {
            toret = parseDERInteger(value);
        } else if (encoding.equalsIgnoreCase(ENCODING_DEROCTETSTRING)) {
            toret = parseDEROctetString(value);
        } else if (encoding.equalsIgnoreCase(ENCODING_DERBOOLEAN)) {
            toret = parseDERBoolean(value);
        } else if (encoding.equalsIgnoreCase(ENCODING_DEROID)) {
            toret = parseDEROID(value);
        } else if (encoding.equalsIgnoreCase(ENCODING_DERPRINTABLESTRING)) {
            toret = parseDERPrintableString(value);
        } else if (encoding.equalsIgnoreCase(ENCODING_DERUTF8STRING)) {
            toret = parseDERUTF8String(value);
        } else if (encoding.equalsIgnoreCase(ENCODING_DERIA5STRING)) {
            toret = parseDERIA5String(value);
        } else if (encoding.equalsIgnoreCase(ENCODING_DERNULL)) {
            toret = new DERNull();
        } else if (encoding.equalsIgnoreCase(ENCODING_DEROBJECT)) {
            toret = parseHexEncodedDERObject(value);
        } else if (encoding.equalsIgnoreCase(ENCODING_DEROID)) {
            toret = parseDERBoolean(value);
        } else {
            throw new CertificateExtentionConfigurationException(
                    intres.getLocalizedMessage("certext.basic.incorrectenc", encoding, Integer.valueOf(getId())));
        }
        return toret;
    }

    /**
     * @deprecated use getValueEncoded instead.
     */
    public DEREncodable getValue(UserDataVO userData, CA ca, CertificateProfile certProfile,
            PublicKey userPublicKey, PublicKey caPublicKey)
            throws CertificateExtensionException, CertificateExtentionConfigurationException {
        throw new UnsupportedOperationException("Use getValueEncoded instead");
    }

    /**
     * This certificate extension implementations overrides this method as it 
     * want to be able to return a byte[] with the extension value. Otherwise 
     * the implementation could have been put in the getValue method as the 
     * super class CertificateExtension has a default implementation for 
     * getValueEncoded which calls getValue.
     * @see CertificateExtension#getValueEncoded(UserDataVO, CA, CertificateProfile, PublicKey, PublicKey) 
     */
    @Override
    public byte[] getValueEncoded(UserDataVO userData, CA ca, CertificateProfile certProfile,
            PublicKey userPublicKey, PublicKey caPublicKey)
            throws CertificateExtensionException, CertificateExtentionConfigurationException {
        final byte[] result;
        String encoding = StringUtils.trim(getProperties().getProperty(PROPERTY_ENCODING));
        String[] values = getValues(userData);
        if (log.isDebugEnabled()) {
            log.debug("Got extension values: " + Arrays.toString(values));
        }

        if (values == null || values.length == 0) {
            throw new CertificateExtentionConfigurationException(
                    intres.getLocalizedMessage("certext.basic.incorrectvalue", Integer.valueOf(getId()), getOID()));
        }

        if (encoding.equalsIgnoreCase(ENCODING_RAW)) {
            if (values.length > 1) {
                // nvalues can not be used together with encoding=RAW
                throw new CertificateExtentionConfigurationException(
                        intres.getLocalizedMessage("certext.certextmissconfigured", Integer.valueOf(getId())));
            } else {
                result = parseRaw(values[0]);
            }
        } else {
            if (values.length > 1) {
                ASN1EncodableVector ev = new ASN1EncodableVector();
                for (String value : values) {
                    DEREncodable derval = parseValue(encoding, value);
                    ev.add(derval);
                }
                result = new DERSequence(ev).getDEREncoded();
            } else {
                result = parseValue(encoding, values[0]).getDERObject().getDEREncoded();
            }
        }
        return result;
    }

    /**
     * Get the extension value by first looking in the ExtendedInformation (if 
     * dynamic is enabled) and then in the static configuration.
     * 
     * @param userData The userdata to get the ExtendedInformation from
     * @return The value(s) for the extension (usually 1) or null if no value found
     */
    private String[] getValues(UserDataVO userData) {
        String[] result = null;

        boolean dynamic = Boolean.parseBoolean(
                StringUtils.trim(getProperties().getProperty(PROPERTY_DYNAMIC, Boolean.FALSE.toString())));

        String strnvalues = getProperties().getProperty(PROPERTY_NVALUES);

        int nvalues;

        if (strnvalues == null || strnvalues.trim().equals("")) {
            nvalues = 0;
        } else {
            nvalues = Integer.parseInt(strnvalues);
        }

        if (dynamic) {
            final ExtendedInformation ei = userData.getExtendedinformation();
            if (ei == null) {
                result = null;
            } else {
                if (nvalues < 1) {
                    String value = userData.getExtendedinformation().getExtensionData(getOID());
                    if (value == null || value.trim().isEmpty()) {
                        value = userData.getExtendedinformation().getExtensionData(getOID() + "." + PROPERTY_VALUE);
                    }
                    if (value == null) {
                        result = null;
                    } else {
                        result = new String[] { value };
                    }
                } else {
                    for (int i = 1; i <= nvalues; i++) {
                        String value = userData.getExtendedinformation()
                                .getExtensionData(getOID() + "." + PROPERTY_VALUE + Integer.toString(i));
                        if (value != null) {
                            if (result == null) {
                                result = new String[nvalues];
                            }
                            result[i - 1] = value;
                        }
                    }
                }
            }
        }
        if (result == null) {
            if (nvalues < 1) {
                String value = getProperties().getProperty(PROPERTY_VALUE);
                if (value == null || value.trim().equals("")) {
                    value = getProperties().getProperty(PROPERTY_VALUE + "1");
                }
                result = new String[] { value };
            } else {
                result = new String[nvalues];
                for (int i = 1; i <= nvalues; i++) {
                    result[i - 1] = getProperties().getProperty(PROPERTY_VALUE + Integer.toString(i));
                }
            }
        }
        return result;
    }

    private DEREncodable parseDERBitString(String value) throws CertificateExtentionConfigurationException {
        DEREncodable retval = null;
        try {
            BigInteger bigInteger = new BigInteger(value, 2);
            int padBits = value.length() - 1 - value.lastIndexOf("1");
            if (padBits == 8) {
                padBits = 0;
            }
            byte[] byteArray = bigInteger.toByteArray();
            if (byteArray[0] == 0) {
                // Remove empty extra byte
                byte[] shorterByteArray = new byte[byteArray.length - 1];
                for (int i = 0; i < shorterByteArray.length; i++) {
                    shorterByteArray[i] = byteArray[i + 1];
                }
                byteArray = shorterByteArray;
            }
            retval = new DERBitString(byteArray, padBits);
        } catch (NumberFormatException e) {
            throw new CertificateExtentionConfigurationException(intres
                    .getLocalizedMessage("certext.basic.illegalvalue", value, Integer.valueOf(getId()), getOID()));
        }

        return retval;
    }

    private DEREncodable parseDEROID(String value) throws CertificateExtentionConfigurationException {
        DEREncodable retval = null;
        try {
            retval = new DERObjectIdentifier(value);
        } catch (Exception e) {
            throw new CertificateExtentionConfigurationException(intres
                    .getLocalizedMessage("certext.basic.illegalvalue", value, Integer.valueOf(getId()), getOID()));
        }

        return retval;
    }

    private DEREncodable parseDERInteger(String value) throws CertificateExtentionConfigurationException {
        DEREncodable retval = null;
        try {
            BigInteger intValue = new BigInteger(value, 10);
            retval = new DERInteger(intValue);
        } catch (NumberFormatException e) {
            throw new CertificateExtentionConfigurationException(intres
                    .getLocalizedMessage("certext.basic.illegalvalue", value, Integer.valueOf(getId()), getOID()));
        }

        return retval;
    }

    private DEREncodable parseDEROctetString(String value) throws CertificateExtentionConfigurationException {
        DEREncodable retval = null;
        if (value.matches("^\\p{XDigit}*")) {
            byte[] bytes = Hex.decode(value);
            retval = new DEROctetString(bytes);
        } else {
            throw new CertificateExtentionConfigurationException(intres
                    .getLocalizedMessage("certext.basic.illegalvalue", value, Integer.valueOf(getId()), getOID()));
        }
        return retval;
    }

    /**
     * Tries to read the hex-string as an DERObject. If it contains more than one DEREncodable object, return a DERSequence of the objects.
     */
    private DEREncodable parseHexEncodedDERObject(String value) throws CertificateExtentionConfigurationException {
        DEREncodable retval = null;
        if (value.matches("^\\p{XDigit}*")) {
            byte[] bytes = Hex.decode(value);
            try {
                ASN1InputStream ais = new ASN1InputStream(bytes);
                DEREncodable firstObject = ais.readObject();
                if (ais.available() > 0) {
                    ASN1EncodableVector ev = new ASN1EncodableVector();
                    ev.add(firstObject);
                    while (ais.available() > 0) {
                        ev.add(ais.readObject());
                    }
                    retval = new DERSequence(ev);
                } else {
                    retval = firstObject;
                }
            } catch (Exception e) {
                throw new CertificateExtentionConfigurationException(intres.getLocalizedMessage(
                        "certext.basic.illegalvalue", value, Integer.valueOf(getId()), getOID()));
            }
        } else {
            throw new CertificateExtentionConfigurationException(intres
                    .getLocalizedMessage("certext.basic.illegalvalue", value, Integer.valueOf(getId()), getOID()));
        }
        return retval;
    }

    private DEREncodable parseDERBoolean(String value) throws CertificateExtentionConfigurationException {
        DEREncodable retval = null;
        if (value.equalsIgnoreCase("TRUE")) {
            retval = DERBoolean.TRUE;
        }

        if (value.equalsIgnoreCase("FALSE")) {
            retval = DERBoolean.FALSE;
        }

        if (retval == null) {
            throw new CertificateExtentionConfigurationException(intres
                    .getLocalizedMessage("certext.basic.illegalvalue", value, Integer.valueOf(getId()), getOID()));
        }

        return retval;
    }

    private DEREncodable parseDERPrintableString(String value) throws CertificateExtentionConfigurationException {
        try {
            return new DERPrintableString(value, true);
        } catch (java.lang.IllegalArgumentException e) {
            throw new CertificateExtentionConfigurationException(intres
                    .getLocalizedMessage("certext.basic.illegalvalue", value, Integer.valueOf(getId()), getOID()));
        }
    }

    private DEREncodable parseDERUTF8String(String value) {
        return new DERUTF8String(value);
    }

    private DEREncodable parseDERIA5String(String value) throws CertificateExtentionConfigurationException {
        try {
            return new DERIA5String(value, true);
        } catch (java.lang.IllegalArgumentException e) {
            throw new CertificateExtentionConfigurationException(intres
                    .getLocalizedMessage("certext.basic.illegalvalue", value, Integer.valueOf(getId()), getOID()));
        }
    }

    private byte[] parseRaw(String value) throws CertificateExtentionConfigurationException {
        if (value == null) {
            throw new CertificateExtentionConfigurationException(
                    intres.getLocalizedMessage("certext.basic.incorrectvalue", Integer.valueOf(getId()), getOID()));
        }
        return Hex.decode(value);
    }

}