CertificateInfo.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 Link.it srl (https://link.it). 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.openspcoop2.utils.certificate;

import java.io.Serializable;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertStore;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.PKIXParameters;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;

import javax.security.auth.x500.X500Principal;

import org.openspcoop2.utils.LoggerWrapperFactory;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.date.DateManager;
import org.openspcoop2.utils.io.Base64Utilities;

/**
 * CertificateInfo
 *
 * @author Poli Andrea (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class CertificateInfo implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	private java.security.cert.X509Certificate certificate;

	private String name;
	
	private byte[] digest;
	private String digestBase64Encoded;
	
	public CertificateInfo(java.security.cert.X509Certificate certificate, String name) {
		this.certificate = certificate;
		this.name = name;
	}

	public String getName() {
		return this.name;
	}
	
	public CertificatePrincipal getSubject() {
		if(this.certificate.getSubjectX500Principal()!=null) {
			return new CertificatePrincipal(this.certificate.getSubjectX500Principal(), PrincipalType.SUBJECT);
		}
		return null;
	}
	
	public CertificatePrincipal getIssuer() {
		if(this.certificate.getIssuerX500Principal()!=null) {
			return new CertificatePrincipal(this.certificate.getIssuerX500Principal(), PrincipalType.ISSUER);
		}
		return null;
	}
	
	public String getSerialNumber() {
		if(this.certificate.getSerialNumber()!=null) {
			return this.certificate.getSerialNumber().toString();
		}
		else {
			return null;
		}
	}
	public String getSerialNumberHex() {
		if(this.certificate.getSerialNumber()!=null) {
			return this.certificate.getSerialNumber().toString(16);
		}
		else {
			return null;
		}
	}
	public String getSerialNumberHex(String delimiter) {
		if(this.certificate.getSerialNumber()!=null) {
			return formatSerialNumberHex(this.certificate.getSerialNumber(), delimiter);
		}
		else {
			return null;
		}
	}
	public static String formatSerialNumberHex(String serialNumber) {
		return (new BigInteger(serialNumber)).toString(16);
	}
	public static String formatSerialNumberHex(String serialNumber, String delimiter) {
		return formatSerialNumberHex(new BigInteger(serialNumber), delimiter);
	}
	public static String formatSerialNumberHex(BigInteger bi, String delimiter) {
		byte[] bytes = bi.toByteArray();
	    StringBuilder sb = new StringBuilder();
	    for (byte b : bytes) {
	    	String f = "%02X"+delimiter;
	        sb.append(String.format(f, b));
	    }
	    String s = sb.toString();
	    if(s.endsWith(delimiter) && s.length()>delimiter.length()) {
	    	return s.substring(0, (s.length()-delimiter.length()));
	    }
	    else {
	    	return s;
	    }
	}
	
	public java.security.cert.X509Certificate getCertificate() {
		return this.certificate;
	}
	
	public String getPEMEncoded() throws UtilsException {
		return CertificateUtils.toPEM(this.certificate);
	}
	public byte[] getEncoded() throws UtilsException {
		try {
			return this.certificate.getEncoded();
		}catch(Exception e) {
			throw new UtilsException(e.getMessage(), e);
		}
	}
	
	public byte[] digest() throws CertificateException {
		return this.digest("MD5");
	}
	public byte[] digest(String algoritmo) throws CertificateException {
		if(this.digest==null) {
			this.initDigest(algoritmo);
		}
		return this.digest;
	}
	private synchronized void initDigest(String algoritmo) throws CertificateException {
		try {
			if(this.digest==null) {
				MessageDigest digestEngine = org.openspcoop2.utils.digest.MessageDigestFactory.getMessageDigest(algoritmo);
				digestEngine.update(this.certificate.getEncoded());
				this.digest = digestEngine.digest();
			}
		}catch(Exception e) {
			throw new CertificateException(e.getMessage(),e);
		}
	}
	
	public String digestBase64Encoded() throws CertificateException {
		return this.digestBase64Encoded("MD5");
	}
	public String digestBase64Encoded(String algoritmo) throws CertificateException {
		if(this.digestBase64Encoded==null) {
			this.initDigestBase64Encoded(algoritmo);
		}
		return this.digestBase64Encoded;
	}
	private synchronized void initDigestBase64Encoded(String algoritmo) throws CertificateException {
		try {
			if(this.digestBase64Encoded==null) {
				this.digestBase64Encoded = Base64Utilities.encodeAsString(this.digest(algoritmo));
			}
		}catch(Exception e) {
			throw new CertificateException(e.getMessage(),e);
		}
	}
	
	public Date getNotAfter() {
		return this.certificate.getNotAfter();
	}
	
	public Date getNotBefore() {
		return this.certificate.getNotBefore();
	}
	
	public String getSigAlgName() {
		return this.certificate.getSigAlgName();
	}
	
	public String getType() {
		return this.certificate.getType();
	}
	
	public int getVersion() {
		return this.certificate.getVersion();
	}
	
	public List<KeyUsage> getKeyUsage(){
		return KeyUsage.getKeyUsage(this.certificate);
	}
	public boolean[] getKeyUsageAsMap() {
		return this.certificate.getKeyUsage();
	}
	public boolean hasKeyUsage(KeyUsage keyUsage) {
		return keyUsage.hasKeyUsage(this.certificate);
	}
	public boolean hasKeyUsage(String keyUsage) {
		return hasKeyUsage(KeyUsage.valueOf(keyUsage.toUpperCase()));
	}
	public boolean hasKeyUsageByArrayBooleanPosition(int arrayBooleanPosition) {
		return KeyUsage.existsKeyUsageByArrayBooleanPosition(this.certificate, arrayBooleanPosition);
	}
	public boolean hasKeyUsageByBouncycastleCode(int bouncycastelValue) throws CertificateEncodingException {
		return KeyUsage.existsKeyUsageByBouncycastleCode(this.certificate.getEncoded(), bouncycastelValue);
	}
	
	public List<ExtendedKeyUsage> getExtendedKeyUsage() throws CertificateParsingException{
		return ExtendedKeyUsage.getKeyUsage(this.certificate);
	}
	public List<String> getExtendedKeyUsageByOID() throws CertificateParsingException {
		return this.certificate.getExtendedKeyUsage();
	}
	public boolean hasExtendedKeyUsage(ExtendedKeyUsage keyUsage) throws CertificateParsingException {
		return keyUsage.hasKeyUsage(this.certificate);
	}
	public boolean hasExtendedKeyUsage(String keyUsage) throws CertificateParsingException {
		return hasExtendedKeyUsage(ExtendedKeyUsage.valueOf(keyUsage.toUpperCase()));
	}
	public boolean hasExtendedKeyUsageByOID(String oid) throws CertificateParsingException {
		return ExtendedKeyUsage.existsKeyUsageByOID(this.certificate,oid);
	}
	public boolean hasExtendedKeyUsageByBouncycastleKeyPurposeId(String oid) throws CertificateEncodingException {
		return ExtendedKeyUsage.existsKeyUsageByBouncycastleKeyPurposeId(this.certificate.getEncoded(),oid);
	}
	
	public List<CertificatePolicy> getCertificatePolicies() throws CertificateEncodingException {
		return CertificatePolicy.getCertificatePolicies(this.certificate.getEncoded());
	}
	public CertificatePolicy getCertificatePolicy(String oid) throws CertificateParsingException, CertificateEncodingException {
		return getCertificatePolicyByOID(oid);
	}
	public CertificatePolicy getCertificatePolicyByOID(String oid) throws CertificateParsingException, CertificateEncodingException {
		if(oid==null) {
			throw new CertificateParsingException("Param oid undefined");
		}
		List<CertificatePolicy> l = getCertificatePolicies();
		if(l!=null && !l.isEmpty()) {
			for (CertificatePolicy certificatePolicy : l) {
				if(oid.equals(certificatePolicy.getOID())) {
					return certificatePolicy;
				}
			}
		}
		return null;
	}
	public boolean hasCertificatePolicy(String oid) throws CertificateParsingException, CertificateEncodingException {
		if(oid==null) {
			throw new CertificateParsingException("Param oid undefined");
		}
		return this.getCertificatePolicyByOID(oid)!=null;
	}
	
	public BasicConstraints getBasicConstraints() throws CertificateParsingException, CertificateEncodingException {
		return BasicConstraints.getBasicConstraints(this.certificate.getEncoded());
	}
	public boolean isCA() throws CertificateParsingException, CertificateEncodingException {
		BasicConstraints bc = this.getBasicConstraints();
		return bc !=null && bc.isCA();
	}
	public long getPathLen() throws CertificateParsingException, CertificateEncodingException {
		BasicConstraints bc = this.getBasicConstraints();
		return bc !=null ? bc.getPathLen() : -1;
	}
	
	public AuthorityKeyIdentifier getAuthorityKeyIdentifier() throws CertificateParsingException, CertificateEncodingException {
		return AuthorityKeyIdentifier.getAuthorityKeyIdentifier(this.certificate.getEncoded());
	}
	
	public AuthorityInformationAccess getAuthorityInformationAccess() throws CertificateParsingException, CertificateEncodingException {
		return AuthorityInformationAccess.getAuthorityInformationAccess(this.certificate.getEncoded());
	}
	
	public CRLDistributionPoints getCRLDistributionPoints() throws CertificateEncodingException {
		return CRLDistributionPoints.getCRLDistributionPoints(this.certificate.getEncoded());
	}
	
	public SubjectAlternativeNames getSubjectAlternativeNames() throws CertificateEncodingException {
		return SubjectAlternativeNames.getSubjectAlternativeNames(this.certificate.getEncoded());
	}
	public List<String> getAlternativeNames() throws CertificateEncodingException {
		SubjectAlternativeNames subjectAlternativeNames = this.getSubjectAlternativeNames();
		return subjectAlternativeNames !=null ? subjectAlternativeNames.getAlternativeNames() : null;
	}
	
	public Extensions getExtensions() throws CertificateEncodingException {
		return Extensions.getExtensions(this.certificate.getEncoded());
	}
	
    /**
     * Utility method to test if a certificate is self-issued. This is
     * the case iff the subject and issuer X500Principals are equal.
     */
    public boolean isSelfIssued() {
    	X500Principal subject = this.certificate.getSubjectX500Principal();
        X500Principal issuer = this.certificate.getIssuerX500Principal();
        return subject.equals(issuer);
    }

    /**
     * Utility method to test if a certificate is self-signed. This is
     * the case iff the subject and issuer X500Principals are equal
     * AND the certificate's subject public key can be used to verify
     * the certificate. In case of exception, returns false.
     */
    public boolean isSelfSigned() {
    	return this.isSelfSigned(null);
    }
    public boolean isSelfSigned(String sigProvider) {
        if (isSelfIssued()) {
            try {
                if (sigProvider == null) {
                	this.certificate.verify(this.certificate.getPublicKey());
                } else {
                	this.certificate.verify(this.certificate.getPublicKey(), sigProvider);
                }
                return true;
            } catch (Exception e) {
                // In case of exception, return false
            }
        }
        return false;
    }
	
	public boolean isValid() {
		try {
			this.certificate.checkValidity(DateManager.getDate());
			return true;
		}catch(Exception e) {
			return false;
		}
	}
	public void checkValid() throws CertificateExpiredException, CertificateNotYetValidException {
		checkValid(DateManager.getDate());
	}
	public void checkValid(Date date) throws CertificateExpiredException, CertificateNotYetValidException {
		this.certificate.checkValidity(date!=null ? date : DateManager.getDate());
	}
	
	public boolean isValid(CertStore crlCertstore, KeyStore trustStore) {
		try {
			this.checkValid(crlCertstore, trustStore);
			return true;
		}catch(Exception e) {
			return false;
		}
	}
	public void checkValid(CertStore crlCertstore, KeyStore trustStore) throws CertPathValidatorException, InvalidAlgorithmParameterException, CertificateException, KeyStoreException, SecurityException, NoSuchAlgorithmException {
		checkValid(crlCertstore, trustStore, DateManager.getDate());
	}
	public void checkValid(CertStore crlCertstore, KeyStore trustStore, Date date) throws CertPathValidatorException, InvalidAlgorithmParameterException, CertificateException, KeyStoreException, SecurityException, NoSuchAlgorithmException {
		PKIXParameters pkixParameters = new PKIXParameters(trustStore.getKeystore());
		pkixParameters.setDate(date!=null ? date : DateManager.getDate()); // per validare i certificati scaduti
		pkixParameters.addCertStore(crlCertstore);
		pkixParameters.setRevocationEnabled(true);
		CertPathValidator certPathValidator = org.openspcoop2.utils.certificate.CertificateFactory.getCertPathValidator();
		List<java.security.cert.Certificate> lCertificate = new ArrayList<>();
		lCertificate.add(this.certificate);
		CertificateFactory certificateFactory = org.openspcoop2.utils.certificate.CertificateFactory.getCertificateFactory();
		certPathValidator.validate(certificateFactory.generateCertPath(lCertificate), pkixParameters);
	}
	
	public boolean isVerified(KeyStore trustStore, boolean checkSameCertificateInTrustStore) {
		try {
			Enumeration<String> aliasesEnum = trustStore.aliases();
			while (aliasesEnum.hasMoreElements()) {
				String alias = aliasesEnum.nextElement();
				java.security.cert.Certificate certificateReaded = trustStore.getCertificate(alias);
				if(checkSameCertificateInTrustStore && this.equalsEngine(certificateReaded)) {
					return true;
				}
				if(this.isVerified(certificateReaded)) {
					return true;
				}
			}
		}catch(Exception e) {
			// ignore
		}
		return false;
	}
	public boolean isVerified(CertificateInfo caCert) {
		return this.isVerified(caCert.getCertificate());
	}
	public boolean isVerified(java.security.cert.Certificate caCert) {
		try {
			this.certificate.verify(caCert.getPublicKey());
			return true;
		}catch(Exception e) {
			return false;
		}
	}
	public void verify(CertificateInfo caCert) throws InvalidKeyException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException {
		verify(caCert.getCertificate());
	}
	public void verify(java.security.cert.Certificate caCert) throws InvalidKeyException, CertificateException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException {
		this.certificate.verify(caCert.getPublicKey());
	}
	
	@Override
	public boolean equals(Object certificate) {
		return this.equalsEngine(certificate);
	}
	private boolean equalsEngine(Object certificate) {
		if(certificate instanceof java.security.cert.X509Certificate) {
			java.security.cert.X509Certificate x509 = (java.security.cert.X509Certificate) certificate;
			return this.equals(x509, true);
		}
		else if(certificate instanceof java.security.cert.Certificate) {
			if(this.getCertificate()!=null) {
				java.security.cert.Certificate cer = (java.security.cert.Certificate) certificate;
				return this.getCertificate().equals(cer);
			}
		}
		else if(certificate instanceof CertificateInfo) {
			CertificateInfo c = (CertificateInfo) certificate;
			return this.equals(c.getCertificate(), true);
		}
		return false;
	}
	/** GiĆ  gestite nell'equals(Object)
	public boolean equals(java.security.cert.X509Certificate certificate) {
		return this.equals(certificate, true);
	}
	public boolean equals(CertificateInfo certificate) {
		return this.equals(certificate.getCertificate(), true);
	}*/
	
	public boolean equals(java.security.cert.X509Certificate certificate, boolean strictVerifier) {
		CertificateInfo certificateCheck = new CertificateInfo(certificate, "check");
		return this.equals(certificateCheck, strictVerifier);
	}
	public boolean equals(CertificateInfo certificate, boolean strictVerifier) {
		if(strictVerifier) {
			return this.getCertificate().equals(certificate.getCertificate());
		}
		else {
			try {
				boolean checkIssuer = this.getIssuer().getNameNormalized().equals(certificate.getIssuer().getNameNormalized());
				boolean checkSubject = this.getSubject().getNameNormalized().equals(certificate.getSubject().getNameNormalized());
				return checkIssuer && checkSubject;
			}catch(Exception e) {
				LoggerWrapperFactory.getLogger(CertificateInfo.class.getName()).error(e.getMessage(),e);
				return false;
			}
		}
	}
	
    public boolean equals(X500Principal principal) {
    	if(principal==null) {
    		return false;
    	}
    	X500Principal subject = this.certificate.getSubjectX500Principal();
    	return principal.equals(subject);
    }
	
	@Override
	public String toString() {
		StringBuilder bf = new StringBuilder();
		CertificateUtils.printCertificate(bf, this.certificate, this.name);
		return bf.toString();
	}
	
	@Override
	public int hashCode() {
		return this.toString().hashCode();
	}
}