![JAR search and dependency download from the Maven repository](/logo.png)
org.jivesoftware.openfire.keystore.CertificateUtils Maven / Gradle / Ivy
The newest version!
package org.jivesoftware.openfire.keystore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.Principal;
import java.security.cert.*;
import java.util.*;
/**
* Utility methods for working with {@link javax.security.cert.Certificate} instances.
*
* @author Guus der Kinderen, [email protected]
*/
public class CertificateUtils
{
private static final Logger Log = LoggerFactory.getLogger( CertificateUtils.class );
/**
* Returns all valid certificates from the provided input, where validity references the notBefore and notAfter
* dates of each certificate.
*
* This method returns all certificates from the input for which {@link X509Certificate#checkValidity()} returns
* true.
*
* The return value of this method is a Set, which means that duplicate certificates in the input are implicitly
* being removed from the result.
*
* @param certificates An array of certificates (possibly empty, possibly null).
* @return A Set of valid certificates (possibly empty, but never null).
*/
public static Set filterValid( X509Certificate... certificates )
{
final Set results = new HashSet<>();
if (certificates != null)
{
for ( X509Certificate certificate : certificates )
{
if ( certificate == null )
{
continue;
}
try
{
certificate.checkValidity();
}
catch ( CertificateExpiredException | CertificateNotYetValidException e )
{
// Not yet or no longer valid. Don't include in result.
continue;
}
results.add( certificate );
}
}
return results;
}
/**
* Returns all valid certificates from the provided input, where validity references the notBefore and notAfter
* dates of each certificate.
*
* This method returns all certificates from the input for which {@link X509Certificate#checkValidity()} returns
* true.
*
* The return value of this method is a Set, which means that duplicate certificates in the input are implicitly
* being removed from the result.
*
* @param certificates A Collection of certificates (possibly empty, possibly null).
* @return A Set of valid certificates (possibly empty, but never null).
*/
public static Set filterValid( Collection certificates )
{
if ( certificates == null )
{
return Collections.emptySet();
}
return filterValid( certificates.toArray( new X509Certificate[ certificates.size() ] ) );
}
/**
* Transforms an array of certificates into TrustAnchor instances.
*
* This method does not set the nameConstraints parameter of the generated TrustAnchors.
*
* The return value of this method is a Set, which means that duplicate certificates in the input are implicitly
* being removed from the result.
*
* @param certificates An array of certificates (possibly empty, possibly null).
* @return A Set of valid certificates (possibly empty, but never null).
*/
public static Set toTrustAnchors( X509Certificate... certificates )
{
final Set result = new HashSet<>();
for ( X509Certificate certificate : certificates )
{
if ( certificate == null)
{
continue;
}
result.add( new TrustAnchor( certificate, null ) );
}
return result;
}
/**
* Transforms a collection of certificates into TrustAnchor instances.
*
* This method does not set the nameConstraints parameter of the generated TrustAnchors.
*
* The return value of this method is a Set, which means that duplicate certificates in the input are implicitly
* being removed from the result.
*
* @param certificates An array of certificates (possibly empty, possibly null).
* @return A Set of valid certificates (possibly empty, but never null).
*/
public static Set toTrustAnchors( Collection certificates )
{
if ( certificates == null )
{
return Collections.emptySet();
}
return toTrustAnchors( certificates.toArray( new X509Certificate[ certificates.size() ] ) );
}
/**
* Orders certificates, starting from the entity to be validated and progressing back toward the CA root.
*
* This implementation matches "issuers" to "subjects" of certificates in such a way that "issuer" value of a
* certificate matches the "subject" value of the next certificate.
*
* When certificates are provided that do not belong to the same chain, a CertificateException is thrown.
*
* @param certificates an unordered collection of certificates (cannot be null).
* @return An ordered list of certificates (possibly empty, but never null).
*/
public static List order( Collection certificates ) throws CertificateException
{
final LinkedList orderedResult = new LinkedList<>();
if ( certificates.isEmpty() ) {
return orderedResult;
}
if (certificates.size() == 1) {
orderedResult.addAll( certificates );
return orderedResult;
}
final Map byIssuer = new HashMap<>();
final Map bySubject = new HashMap<>();
for ( final X509Certificate certificate : certificates ) {
final Principal issuer = certificate.getIssuerDN();
final Principal subject = certificate.getSubjectDN();
// By issuer
if ( issuer.equals( subject ))
{
// self-signed: use null key.
final X509Certificate sameIssuer = byIssuer.put( null, certificate );
if ( sameIssuer != null )
{
throw new CertificateException( "The provided input should not contain multiple root CA certificates. Issuer of first detected Root CA certificate: " + issuer + " Issuer of second detected Root CA certificate: : " + sameIssuer );
}
}
else
{
// regular issuer
if ( byIssuer.put( issuer, certificate ) != null )
{
throw new CertificateException( "The provided input should not contain multiple certificates with identical issuerDN values. Offending value: " + issuer );
}
}
// By subject
if ( bySubject.put( subject, certificate ) != null ) {
throw new CertificateException( "The provided input should not contain multiple certificates with identical subjectDN values. Offending value: " + subject );
}
}
// The first certificate will have a 'subject' value that's not an 'issuer' of any other chain.
X509Certificate first = null;
for ( Map.Entry entry : bySubject.entrySet() ) {
final Principal subject = entry.getKey();
final X509Certificate certificate = entry.getValue();
if ( ! byIssuer.containsKey( subject ) ) {
if (first == null) {
first = certificate;
} else {
throw new CertificateException( "The provided input should not contain more than one certificates that has a subjectDN value that's not equal to the issuerDN value of another certificate." );
}
}
}
if (first == null) {
throw new CertificateException( "The provided input should contain a certificate that has a subjectDN value that's not equal to the issuerDN value of any other certificate." );
}
orderedResult.add( first );
// With the first certificate in hand, every following certificate should have a subject that's equal to the previous issuer value.
X509Certificate next = bySubject.remove( first.getIssuerDN() );
while (next != null) {
orderedResult.add( next );
next = bySubject.remove( next.getIssuerDN() );
}
// final check
if (orderedResult.size() != certificates.size()) {
throw new CertificateException( "Unable to recreate a certificate chain from the provided input." );
}
return orderedResult;
}
/**
* Identifies the End Entity (or 'target') certificate in a chain. In an ordered chain, this is the certificate on
* the opposite end of the CA / Root Certificate.
*
* This implementation can work with incomplete and unordered chains, as long as the provided certificates are all
* part of the same chain (or chain segment). Each certificate in the chain is expected to have issued another
* certificate from the chain, except for one. That one certificate is returned.
*
* When ordering the chain fails (for example, when the collection of certificates do not belong to one linear list)
* the first certificate from the chain is returned.
*
* @param chain The chain (possibly incomplete or unordered, but not null, empty or malformed).
* @return The end entity certificate (never null).
* @throws CertificateException When no valid chain was provided.
*/
public static X509Certificate identifyEndEntityCertificate( Collection chain ) throws CertificateException
{
if ( chain.isEmpty() )
{
throw new CertificateException();
}
try
{
return order( chain ).get( 0 );
}
catch ( CertificateException ex )
{
Log.warn( "Unable to order the provided chain. As a fallback, the end entity certificate is assumed to be the first certificate of the input.", ex );
return chain.iterator().next();
}
}
/**
* Attempts to find a point in time on which each of the certificates in the chain will pass
* {@link X509Certificate#checkValidity(Date)}
*
* @param chain The chain for which to find a valid point in time (cannot be null, or empty).
* @return A date on which all certificates in the chain are valid, or null of no such date is available.
*/
public static Date findValidPointInTime( X509Certificate... chain )
{
Date earliestNotAfter = null;
Date latestNotBefore = null;
for ( final X509Certificate certificate : chain )
{
if ( certificate == null ) continue; // ignore nulls.
// Find the earliest 'notAfter'
final Date notAfter = certificate.getNotAfter();
if (earliestNotAfter == null || ( notAfter != null && notAfter.before( earliestNotAfter ) ) )
{
earliestNotAfter = notAfter;
}
// Find the latest 'notBefore'
final Date notBefore = certificate.getNotBefore();
if (latestNotBefore == null || ( notBefore != null && notBefore.after( latestNotBefore ) ) )
{
latestNotBefore = notBefore;
}
}
if ( latestNotBefore != null && earliestNotAfter != null && latestNotBefore.before( earliestNotAfter ) )
{
return latestNotBefore;
}
else
{
// There's no single point in time in which all certificates in this chain are valid.
return null;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy