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

org.cesecore.certificates.ca.internal.CertificateValidity 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.ca.internal;

import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Date;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1GeneralizedTime;
import org.bouncycastle.asn1.x509.PrivateKeyUsagePeriod;
import org.cesecore.certificates.ca.CAOfflineException;
import org.cesecore.certificates.ca.IllegalValidityException;
import org.cesecore.certificates.certificateprofile.CertificateProfile;
import org.cesecore.certificates.endentity.EndEntityInformation;
import org.cesecore.certificates.endentity.ExtendedInformation;
import org.cesecore.config.CesecoreConfiguration;
import org.cesecore.internal.InternalResources;
import org.cesecore.util.CertTools;
import org.cesecore.util.SimpleTime;
import org.cesecore.util.ValidityDate;

/** Class used to construct validity times based on a range of different input parameters and configuration. 
 * 
 * @version $Id: CertificateValidity.java 28114 2018-01-26 14:51:20Z samuellb $
 */
public class CertificateValidity {

	/** Class logger. */
    private static final Logger log = Logger.getLogger(CertificateValidity.class);
    
    /** Internal localization of logs and errors */
    private static final InternalResources intres = InternalResources.getInstance();
    
    /** Issuing certificates with 'notAfter' greater than this value throws an exception. */
    private static Date TOO_LATE_EXPIRE_DATE;
    static {
        final String value = CesecoreConfiguration.getCaTooLateExpireDate();
        try {
            TOO_LATE_EXPIRE_DATE = ValidityDate.parseCaLatestValidDateTime(value);
        } catch (Exception e) {
            final String newValue = ValidityDate.formatAsISO8601(new Date(Long.MAX_VALUE), ValidityDate.TIMEZONE_SERVER);
            TOO_LATE_EXPIRE_DATE = ValidityDate.parseCaLatestValidDateTime(newValue);
            log.warn("cesecore.properties ca.toolateexpiredate '" + value + "' could not be parsed Using default value '"+newValue+"'.", e);
        }
    }
    
    /** 
     * Validity offset in milliseconds (offset for the 'notBefore' value)
     * The default start date is set 10 minutes back to avoid some problems with unsynchronized clocks.
     */
    private static long DEFAULT_VALIDITY_OFFSET;
    static {
        final String value = CesecoreConfiguration.getCertificateValidityOffset();
        try {
            DEFAULT_VALIDITY_OFFSET = SimpleTime.getSecondsFormat().parseMillis( value);
        } catch (Exception e) {
            // Use old value for compatibility reasons!
            DEFAULT_VALIDITY_OFFSET = -10L * 60 * 1000;
            log.warn("cesecore.properties certificate.validityoffset '" + value + "' could not be parsed as relative time string. Using default value '-10m' = -60000ms", e);
        }
    }
    
    /**
     * Gets the default validity offset.
     * @return the offset as relative time.
     * @See {@link org.cesecore.util.SimpleTime SimpleTime}
     */
    public static final long getValidityOffset() {
        return DEFAULT_VALIDITY_OFFSET;
    }
    
    /**
     * Gets the maximum possible value for the certificates 'notAfter' value.
     * @return ISO8601 date
     */
    public static Date getToolLateExpireDate() {
        return TOO_LATE_EXPIRE_DATE;
    }
    
    /**
     * Sets the maximum possible value for the certificates 'notAfter' value. This method MUST NOT BE CALLED, except for unit testing.
     * @param date the date to set.
     */
    public static void setTooLateExpireDate(final Date date) {
        TOO_LATE_EXPIRE_DATE = date;
    }
    
    /** The certificates 'notAfter' value. */
    private Date lastDate;

    /** The certificates 'notBefore' value. */
    private Date firstDate;
   
    public CertificateValidity(final EndEntityInformation subject, final CertificateProfile certProfile, 
            final Date notBefore, final Date notAfter, final Certificate cacert, final boolean isRootCA, final boolean isLinkCertificate) throws IllegalValidityException {
        this(new Date(), subject, certProfile, notBefore, notAfter, cacert, isRootCA, isLinkCertificate);
    }
   
    /** Constructor that injects the reference point (now). This constructor mainly is used for unit testing. */
	public CertificateValidity(Date now, final EndEntityInformation subject, final CertificateProfile certProfile, 
			final Date notBefore, final Date notAfter, final Certificate cacert, final boolean isRootCA, final boolean isLinkCertificate) throws IllegalValidityException {
		if (log.isDebugEnabled()) {
		    log.debug("Requested notBefore: "+notBefore);
			log.debug("Requested notAfter: "+notAfter);
			if (null != subject.getExtendedInformation()) {
			    log.debug("End entity extended information 'notBefore': "+subject.getExtendedInformation().getCustomData(ExtendedInformation.CUSTOM_STARTTIME));
			}
			if (null != subject.getExtendedInformation()) {
                log.debug("End entity extended information 'notAfter': "+subject.getExtendedInformation().getCustomData(ExtendedInformation.CUSTOM_ENDTIME));
            }
			log.debug("Default validty offset: "+DEFAULT_VALIDITY_OFFSET);
			log.debug("Certificate profile validty: "+certProfile.getEncodedValidity());
			log.debug("Certificate profile use validty offset: "+certProfile.getUseCertificateValidityOffset());
			log.debug("Certificate profile validty offset: "+certProfile.getCertificateValidityOffset());
			log.debug("Certificate profile use expiration restrictions for weekdays: "+certProfile.getUseExpirationRestrictionForWeekdays());
			log.debug("Certificate profile expiration restrictions weekdays: "+Arrays.toString(certProfile.getExpirationRestrictionWeekdays()));
			log.debug("Certificate profile expiration restrictions for weekdays before: "+certProfile.getExpirationRestrictionForWeekdaysExpireBefore());
		}
		if ( TOO_LATE_EXPIRE_DATE==null ) {
		    throw new IllegalStateException("ca.toolateexpiredate in cesecore.properties is not a valid date.");
		}
        
		// ECA-3554 add the offset
		now = getNowWithOffset(now, certProfile);
		if (log.isDebugEnabled()) {
		    log.debug("Using new start time including offset: " + now);
		}
         
		// Find out what start and end time to actually use..
        if (certProfile.getAllowValidityOverride()) {
            // First Priority has information supplied in Extended information object. This allows RA-users to set the time-span.
            // Second Priority has the information supplied in the method arguments
            firstDate = getExtendedInformationStartTime(now, subject);
            if (firstDate == null) {
            	firstDate = notBefore;
            }
            if ((lastDate = getExtendedInformationEndTime(now, subject)) == null) {
            	lastDate = notAfter;
            }    	
            if (log.isDebugEnabled()) {
                log.debug("Allow validity override, notBefore: "+firstDate);
                log.debug("Allow validity override, notAfter: "+lastDate);
            }
        }
        // Third priority: If nothing could be set by external information have the default  3 is default values
        if (firstDate == null) {
        	firstDate = now;
        }
        Date certProfileLastDate = new Date(getCertificateProfileValidtyEndDate(certProfile, firstDate));
        // Limit validity: ECA-5330 Apply expiration restriction for weekdays 
        if (certProfile.getUseExpirationRestrictionForWeekdays() && isRelativeTime(certProfile.getEncodedValidity())) {
            log.info("Applying expiration restrictions for weekdays: " + Arrays.asList(certProfile.getExpirationRestrictionWeekdays()));
            try {
                final Date newDate = ValidityDate.applyExpirationRestrictionForWeekdays(certProfileLastDate, 
                    certProfile.getExpirationRestrictionWeekdays(), certProfile.getExpirationRestrictionForWeekdaysExpireBefore());
                if (!firstDate.before(newDate)) {
                    log.warn("Expiration restriction of certificate profile could not be applied because it's before start date!");    
                } else if (!TOO_LATE_EXPIRE_DATE.after(newDate)) {
                    log.warn("Expiration restriction of certificate profile could not be applied because it's after latest possible end date!");
                } else {
                    certProfileLastDate = newDate;
                }
            } catch(Exception e) {
                log.warn("Expiration restriction of certificate profile could not be applied!");
            }
        }
        //If it is a link certificate that we create, we use the old ca's expire date, as requested, as link certificate expire date
        if (isLinkCertificate) {
            lastDate = notAfter;
        }
        if (lastDate == null) {
        	lastDate = certProfileLastDate;
        }
        // Limit validity: Do not allow last date to be before first date
        if (lastDate.before(firstDate)) {
			log.info(intres.getLocalizedMessage("createcert.errorinvalidcausality",firstDate,lastDate));
        	Date tmp = lastDate;
        	lastDate = firstDate;
        	firstDate = tmp;
        }
		// Limit validity: We do not allow a certificate to be valid before the current date, i.e. not back dated start dates
        // Unless allowValidityOverride is set, then we allow everything
        // So this check is probably completely unneeded and can never be true
    	if (firstDate.before(now) && !certProfile.getAllowValidityOverride()) {
			log.error(intres.getLocalizedMessage("createcert.errorbeforecurrentdate",firstDate,subject.getUsername()));
    		firstDate = now;
    		// Update valid length from the profile since the starting point has changed
			certProfileLastDate = new Date(getCertificateProfileValidtyEndDate(certProfile, firstDate));
    		// Update lastDate if we use maximum validity
    	}
		// Limit validity: We do not allow a certificate to be valid after the the validity of the certificate profile
    	if (lastDate.after(certProfileLastDate)) {
    		log.info(intres.getLocalizedMessage("createcert.errorbeyondmaxvalidity",lastDate,subject.getUsername(),certProfileLastDate));
    		lastDate = certProfileLastDate;
    	}
		// Limit validity: We do not allow a certificate to be valid after the the validity of the CA (unless it's RootCA during renewal)
    	if (cacert != null && !isRootCA) {
    	    final Date caNotAfter = CertTools.getNotAfter(cacert);
    	    if (lastDate.after(caNotAfter)) {
    	        log.info(intres.getLocalizedMessage("createcert.limitingvalidity", lastDate.toString(), caNotAfter));
    	        lastDate = caNotAfter;
    	    }
    	}
    	// Limit validity: We do not allow a certificate to be valid before the the CA becomes valid (unless it's RootCA during renewal)
    	if (cacert != null && !isRootCA) {
    	    final Date caNotBefore = CertTools.getNotBefore(cacert);
    	    if (firstDate.before(caNotBefore)) {
    	        log.info(intres.getLocalizedMessage("createcert.limitingvaliditystart", firstDate.toString(), caNotBefore));
    	        firstDate = caNotBefore;
    	    }
        }
        if ( !lastDate.before(CertificateValidity.TOO_LATE_EXPIRE_DATE) ) {
        	String msg = intres.getLocalizedMessage("createcert.errorbeyondtoolateexpiredate", lastDate.toString(), CertificateValidity.TOO_LATE_EXPIRE_DATE.toString()); 
        	log.info(msg);
            throw new IllegalValidityException(msg);
        }
	}

	/** 
	 * Gets the certificates 'notAter' value.
	 * @return the 'notAfter' date.
	 */
	public Date getNotAfter() {
		return lastDate;
	}

	/** 
     * Gets the certificates 'notBefore' value.
     * @return the 'notBefore' date.
     */
	public Date getNotBefore() {
		return firstDate;
	}
	
	/**
     * Gets the validity end date for the certificate using the certificate profiles encoded validity.
     * @param profile the certificate profile
     * @param firstDate the start time.
     * @return the encoded validity.
     */
	@SuppressWarnings("deprecation")
    private long getCertificateProfileValidtyEndDate(CertificateProfile profile, Date firstDate) {
        final String encodedValidity = profile.getEncodedValidity();
        Date date = null;
        if (StringUtils.isNotBlank(encodedValidity)) {
            date = ValidityDate.getDate( encodedValidity, firstDate);
        } else {
            date = ValidityDate.getDateBeforeVersion661(profile.getValidity(),firstDate);
        }
        return date.getTime();
	}
	
	/**
	 * Offsets the certificates 'notBefore' (reference point) with the global offset or the offset of the certificate profile.
	 * @param now the reference point
	 * @param profile the certificate profile
	 * @return the offset reference point
	 */
	private Date getNowWithOffset(final Date now, final CertificateProfile profile) {
        Date result = null;
        if (profile.getUseCertificateValidityOffset()) {
            final String offset = profile.getCertificateValidityOffset();
            try {
                result = new Date(now.getTime() + SimpleTime.parseMillies(offset));
                if (log.isDebugEnabled()) {
                    log.debug( "Using validity offset by certificate profile: " + offset);
                }
            } catch(NumberFormatException e) {
                log.warn("Could not parse certificate validity offset " + offset + "; using default " + DEFAULT_VALIDITY_OFFSET);
            }
        } else {
            result = new Date(now.getTime() + DEFAULT_VALIDITY_OFFSET);
            if (log.isDebugEnabled()) {
                log.debug( "Using validity offset by cesecore.properties: " + SimpleTime.toString(DEFAULT_VALIDITY_OFFSET, SimpleTime.TYPE_DAYS));
            }
        }
        return result;
	}
	 
	/**
	 * Gets the start time by the extended entity information.
	 * @param now the reference point.
	 * @param subject the end entity information.
	 */
	private Date getExtendedInformationStartTime(final Date now, final EndEntityInformation subject) {
	    Date result = null;
        final ExtendedInformation extendedInformation = subject.getExtendedInformation();
        if (extendedInformation != null) {
            result = parseExtendedInformationEncodedValidity(now, extendedInformation.getCustomData(ExtendedInformation.CUSTOM_STARTTIME));
        }
        if (log.isDebugEnabled()) {
            log.debug("Using ExtendedInformationStartTime: " + result);
        }
        return result;
	}
	
	/**
     * Gets the end time by the extended entity information.
     * @param now the reference point.
     * @param subject the end entity information.
     */
	private Date getExtendedInformationEndTime(final Date now, final EndEntityInformation subject) {
        Date result = null;
        final ExtendedInformation extendedInformation = subject.getExtendedInformation();
        if (extendedInformation != null) {
            result = parseExtendedInformationEncodedValidity(now, extendedInformation.getCustomData(ExtendedInformation.CUSTOM_ENDTIME));
        }
        if (log.isDebugEnabled()) {
            log.debug("Using ExtendedInformationEndTime: " + result);
        }
        return result;
	}
	
	
	/**
	 * Checks that the PrivateKeyUsagePeriod of the certificate is valid at this time
	 * @param cacert

	 * @throws CAOfflineException if PrivateKeyUsagePeriod either is not valid yet or has expired, exception message gives details
	 */
	public static void checkPrivateKeyUsagePeriod(final X509Certificate cert) throws CAOfflineException {
	    checkPrivateKeyUsagePeriod(cert, new Date());
	}
	
    public static void checkPrivateKeyUsagePeriod(final X509Certificate cert, final Date checkDate) throws CAOfflineException {
        if (cert != null) {
            final PrivateKeyUsagePeriod pku = CertTools.getPrivateKeyUsagePeriod(cert);
            if (pku != null) {
                final ASN1GeneralizedTime notBefore = pku.getNotBefore();
                final Date pkuNotBefore;
                final Date pkuNotAfter;
                try {
                    if (notBefore == null) {
                        pkuNotBefore = null;
                    } else {
                        pkuNotBefore = notBefore.getDate();
                    }
                    if (log.isDebugEnabled()) {
                        log.debug("PrivateKeyUsagePeriod.notBefore is " + pkuNotBefore);
                    }
                    if (pkuNotBefore != null && checkDate.before(pkuNotBefore)) {
                        final String msg = intres.getLocalizedMessage("createcert.privatekeyusagenotvalid", pkuNotBefore.toString(), cert
                                .getSubjectDN().toString());
                        if (log.isDebugEnabled()) {
                            log.debug(msg);
                        }
                        throw new CAOfflineException(msg);
                    }
                    final ASN1GeneralizedTime notAfter = pku.getNotAfter();

                    if (notAfter == null) {
                        pkuNotAfter = null;
                    } else {
                        pkuNotAfter = notAfter.getDate();
                    }
                } catch (ParseException e) {
                    throw new IllegalStateException("Could not parse dates.", e);
                } 
                if (log.isDebugEnabled()) {
                    log.debug("PrivateKeyUsagePeriod.notAfter is " + pkuNotAfter);
                }
                if (pkuNotAfter != null && checkDate.after(pkuNotAfter)) {
                    final String msg = intres.getLocalizedMessage("createcert.privatekeyusageexpired", pkuNotAfter.toString(), cert.getSubjectDN().toString());
                    if (log.isDebugEnabled()) {
                        log.debug(msg);
                    }
                    throw new CAOfflineException(msg);
                }
            } else if (log.isDebugEnabled()) {
                log.debug("No PrivateKeyUsagePeriod available in certificate.");
            }
        } else if (log.isDebugEnabled()) {
            log.debug("No CA certificate available, not checking PrivateKeyUsagePeriod.");       
        }
    }
    
    /**
     * Checks if the encoded validity is an ISO8601 date or a relative time.
     * @param encodedValidity the validity
     * @return Boolean.TRUE if it is a relative time, Boolean.FALSE if it is an ISO8601 date, otherwise NULL.
     * @See {@link org.cesecore.util.ValidityDate ValidityDate}
     * @See {@link org.cesecore.util.SimpleTime SimpleTime}
     */
    private static final Boolean isRelativeTime(final String encodedValidity) {
        try {
        	// Try the most likely setting first, for fail-fast
            SimpleTime.parseMillies(encodedValidity);
            return Boolean.TRUE;
        } catch(NumberFormatException nfe) {
            // NOOP
        }
        try {
            ValidityDate.parseAsIso8601(encodedValidity);
            return Boolean.FALSE;
        } catch(ParseException e) {
            return null;
        }
    }
    
    /**
     * Parses the entity extended information start and end time format and offsets it with the reference point.
     * @param now the reference point
     * @param timeString the value in form of 'days:hours:minutes'
     * @return the parse value offset with now (reference point).
     */
    private static final Date parseExtendedInformationEncodedValidity(final Date now, final String timeString) {
        Date result = null;
        if (timeString != null) {
            if (timeString.matches("^\\d+:\\d?\\d:\\d?\\d$")) {
                final String[] endTimeArray = timeString.split(":");
                long relative = (Long.parseLong(endTimeArray[0])*24*60 
                              + Long.parseLong(endTimeArray[1])*60 
                              + Long.parseLong(endTimeArray[2])) * 60 * 1000;
                result = new Date(now.getTime() + relative);
            } else {
                try {
                    // Try parsing data as "yyyy-MM-dd HH:mm" assuming UTC
                    result = ValidityDate.parseAsUTC(timeString);
                } catch (ParseException e) {
                    log.error(intres.getLocalizedMessage("createcert.errorinvalidstarttime",timeString));
                }
            }
            if ((log.isDebugEnabled())) {
                log.debug("Time string by end entity extended Information: "+result);
            }
        }
        return result;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy