All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cesecore.certificates.util.dn.DNFieldsUtil Maven / Gradle / Ivy

/*************************************************************************
 *                                                                       *
 *  CESeCore: CE Security Core                                           *
 *                                                                       *
 *  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.cesecore.certificates.util.dn;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.cesecore.util.CeSecoreNameStyle;

/**
 * DN string utilities.
 * 
 * Optimized to lower object instantiation by performing manipulations "in-place" using StringBuilder and char[].
 * 
 * Not built to handle '+' char separators or take special consideration to Unicode.
 * Current implementation will treat unescaped '=' in values as ok (backwards compatible).
 * 
 * @version $Id: DNFieldsUtil.java 25580 2017-03-22 14:30:19Z anatom $
 */
public abstract class DNFieldsUtil {

	private static final Logger LOG = Logger.getLogger(DNFieldsUtil.class);
	private static final int EMPTY = -1;
	private static final String MSG_ERROR_MISSING_EQUAL = "DN field definition is missing the '=': ";
	private static final String ATTRIBUTE_SEPARATOR = ",";
	private static final String KEY_VALUE_SEPARATOR = "=";
	
	/**
     * The method splits an DN string into a map of it's attributes and values.
     * 
     * @param dnString the DN String to split (i.e.: 'C=DE,CN=test,SN=1').
     * @return a map containing the attributes and values.
     */
    public static final Map dnStringToMap(final String dnString) {
        final String[] dnTokens = dnString.split(ATTRIBUTE_SEPARATOR);
        final Map result = new HashMap();
        if (StringUtils.isNotBlank(dnString)) {
            String[] tokens;
            for (int i = 0; i 1) {
                    result.put(tokens[0], tokens[1]);
                } else {
                    result.put(tokens[0], StringUtils.EMPTY);
                }
            }
        }
        return result;
    }
    
    /**
     * The method checks if the subject-DN contains both C and CN attributes
     * (in a potential CVCA/CSCA certificate at least the attributes C and CN must be set).
     * 
     * @param map the map of DN attributes and values.
     * @return true if the DN map contains non-empty values for both C and CN.
     */
    public static final boolean mapContainsCountryAndCN(final Map map) {
        boolean result = false;
        if (map.size() >= 2) {
            result = StringUtils.isNotBlank(map.get(CeSecoreNameStyle.DefaultSymbols.get(CeSecoreNameStyle.C))) 
                  && StringUtils.isNotBlank(CeSecoreNameStyle.DefaultSymbols.get(CeSecoreNameStyle.CN));
        }
        return result;
    }

    /**
     * The method checks if the two subject-DN maps are equal except the 'SN' attribute.
     * @param map1 the left side subject-DN map.
     * @param map2 the right side subject-DN map.
     * @return true if both subject-DN are equal except the 'SN' attribute (accepts null or empty Strings as values, but not for 'SN' attribute).
     */
    public static final boolean dnEqualsWithOtherSerialNumber(final Map map1, final Map map2) {
        if (map1.size() < 2 || map2.size() < 2 || map1.size() != map2.size()) {
            return false;
        }
        String key, value1, value2;
        final String snAttributeKey = CeSecoreNameStyle.DefaultSymbols.get(CeSecoreNameStyle.SN);
        boolean result = true;
        for (Map.Entry entry : map1.entrySet()) {
            key = entry.getKey();
            value1 = entry.getValue();
            value2 = map2.get(key);
            if (snAttributeKey.equals( key)) { // check that serial numbers are not blank and not equal. 
                if (StringUtils.isBlank(value1) || StringUtils.isBlank(value2) || value1 == value2) {
                    result = false;
                }
            } else { // All other DN attributes must be equal.
                if (!StringUtils.equals(value1, value2)) {
                	result = false;
                }
            }
        }
        return result;
    }


	/** Invoke removeEmpties and only return the fully clean dn String. */
	public static String removeAllEmpties(final String dn) {
		if (dn==null) {
			return null;
		}
    	final StringBuilder removedAllEmpties = new StringBuilder(dn.length());
    	DNFieldsUtil.removeEmpties(dn, removedAllEmpties, false);
		return removedAllEmpties.toString();
	}

	/**
	 * This method will take the supplied string and fill the two provided empty StringBuilders.
	 * 
	 * removedTrailingEmpties is produced by:
	 * Removes fields (key=value) where the value is empty if it is the last value with the same key.
	 * Example: "CN=abc,CN=,CN=def,O=,O=abc,O=" will become "CN=abc,CN=,CN=def,O=,O=abc".
	 * Example: "CN=abc,DC=,O=,CN=def,O=,O=abc,O=" will become "CN=abc,O=,CN=def,O=,O=abc".
	 * 
	 * removedAllEmpties is produced by:
	 * Removes all fields (key=value) where the value is empty.
	 * Example: "CN=abc,CN=,O=,CN=def,O=,O=abc,O=" will become "CN=abc,CN=def,O=abc".
	 * 
	 * Since the algorithms are very similar for these two it makes sense to calculate them both at
	 * the same time for use in EndEntityInformation.
	 * 
	 * @param sDN the String to clean.
	 * @param processTrailing true is removedTrailingEmpties should be considered.
	 * @return removedTrailingEmpties StringBuilder if both types of cleaning give different results or null if they are the same.
	 */
	public static StringBuilder removeEmpties(final String sDN, final StringBuilder removedAllEmpties, final boolean processTrailing) {
		StringBuilder removedTrailingEmpties = null;
    	// First make a list of where all the key=value pairs start and if they are empty or not
    	final List startOfPairs = new ArrayList();
    	final List startOfValues = new ArrayList();
    	final char[] buf = sDN.toCharArray();
    	populatePositionLists(startOfPairs, startOfValues, buf);
    	boolean areStringBuildersEqual = true;
    	// Go through all the pairs from first to last
    	for (int i=0; i 0) {
            for (int i = sb.length() - 1; i >= 0; i--) {
                final char c = sb.charAt(i);
                if (c == ' ' || c == ',') {
                    if (sb.charAt(i - 1) == '\\') {
                        break;
                    } else {
                        sb.deleteCharAt(i);
                    }
                } else {
                    break;
                }
            }
        }
    }

	/** Populates the two lists with starting positions in the character buffer where the value=key pair begins and keys begin. */
    private static void populatePositionLists(final List startOfPairs, final List startOfValues, final char[] buf) {
    	if (buf.length>0) {
        	startOfPairs.add(Integer.valueOf(0));
    	}
    	boolean notEscaped = true;	// Keep track of what is escapes and not
    	for (int i=0; i startOfValues.size()) {
        				// We are missing a '=' in the DN!
        				LOG.info(MSG_ERROR_MISSING_EQUAL + new String(buf));
        			}
    				int j = i+1;
    				while (j startOfValues.size()) {
        				int j = i+1;
        				while (j=buf.length || buf[j] == ',') {
        					startOfValues.add(Integer.valueOf(EMPTY));	// Use -1 to mark that the value is empty
        				} else {
        					startOfValues.add(Integer.valueOf(j));
        				}
        			}
    			} else {
    				notEscaped = true;
    			}
    			break;
    		default:
    			notEscaped=true;
    		}
    	}
    }

    /** Compares the two character sequences in the buffer at the positions until a not escaped '=' is found. */
    private static boolean hasSameKey(final char[] sb, int pos1, int pos2) {
    	final int len = sb.length;
    	boolean notEscaped = true;	// Keep track of what is escapes and not
    	while (len>pos1 && len>pos2 ) {
        	final char c1 = sb[pos1];
        	switch (c1) {
        	case '\\':
    			notEscaped ^= true;
    			break;
        	case '=':
        		if (notEscaped && c1 == sb[pos2]) {
        			return true;
        		} // else.. continue with the default action..
        	default:
            	if (c1 != sb[pos2]) {
            		return false;
            	}
    			notEscaped=true;
        	}
        	pos1++;
        	pos2++;
    	}
    	return false;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy