/*
* DSS - Digital Signature Services
*
* Copyright (C) 2013 European Commission, Directorate-General Internal Market and Services (DG MARKT), B-1049 Bruxelles/Brussel
*
* Developed by: 2013 ARHS Developments S.A. (rue Nicolas Bové 2B, L-1253 Luxembourg) http://www.arhs-developments.com
*
* This file is part of the "DSS - Digital Signature Services" project.
*
* "DSS - Digital Signature Services" 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 (at your option) any later version.
*
* DSS 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* "DSS - Digital Signature Services". If not, see <http://www.gnu.org/licenses/>.
*/
package eu.europa.ec.markt.dss.validation102853;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x509.Extension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.europa.ec.markt.dss.DSSASN1Utils;
import eu.europa.ec.markt.dss.DSSPKUtils;
import eu.europa.ec.markt.dss.DSSUtils;
import eu.europa.ec.markt.dss.DigestAlgorithm;
import eu.europa.ec.markt.dss.OID;
import eu.europa.ec.markt.dss.SignatureAlgorithm;
import eu.europa.ec.markt.dss.exception.DSSException;
import eu.europa.ec.markt.dss.validation102853.certificate.CertificateSourceType;
import eu.europa.ec.markt.dss.validation102853.condition.ServiceInfo;
/**
* Whenever the signature validation process encounters an {@link java.security.cert.X509Certificate} a certificateToken is created.<br>
* This class encapsulates some frequently used information: a certificate comes from a certain context (Trusted List,
* CertStore, Signature), has revocation data... To expedite the processing of such information, they are kept in cache.
*
* @version $Revision: 1837 $ - $Date: 2013-03-14 21:07:50 +0100 (Thu, 14 Mar 2013) $
*/
public class CertificateToken extends Token {
private static final Logger LOG = LoggerFactory.getLogger(CertificateToken.class);
public static final String DIGITAL_SIGNATURE = "digitalSignature";
public static final String NON_REPUDIATION = "nonRepudiation";
public static final String KEY_ENCIPHERMENT = "keyEncipherment";
public static final String DATA_ENCIPHERMENT = "dataEncipherment";
public static final String KEY_AGREEMENT = "keyAgreement";
public static final String KEY_CERT_SIGN = "keyCertSign";
public static final String CRL_SIGN = "cRLSign";
public static final String ENCIPHER_ONLY = "encipherOnly";
public static final String DECIPHER_ONLY = "decipherOnly";
/**
* Encapsulated X509 certificate.
*/
private X509Certificate x509Certificate;
/**
* This array contains the different sources for this certificate.
*/
private final List<CertificateSourceType> sources = new ArrayList<CertificateSourceType>();
/**
* If the certificate is part of the trusted list then the the serviceInfo represents the associated trusted service
* provider service. Same certificate can be a part of multiple services.
*/
private final List<ServiceInfo> associatedTSPS = new ArrayList<ServiceInfo>();
/**
* DSS unique id based on the issuer distinguish name and serial number of encapsulated X509Certificate.
*/
private int dssId;
/**
* The default algorithm used to compute the digest value of this certificate
*/
private DigestAlgorithm digestAlgorithm = DigestAlgorithm.SHA1;
/**
* Base 64 encoded digest value of this certificate computed for a given digest algorithm.
*/
private Map<DigestAlgorithm, String> digests;
/**
* OCSP or CRL revocation data for this token.
*/
private RevocationToken revocationToken;
/**
* Indicates if the certificate is self-signed. This attribute stays null till the first call to
* {@link #isSelfSigned()} function.
*/
private Boolean selfSigned;
/**
* Extra information collected during the validation process.
*/
protected CertificateTokenValidationExtraInfo extraInfo;
/**
* Normalized X500Principal (BMPString, TeletextString...)
*/
private X500Principal subjectX500PrincipalNormalized = null;
/**
* In the case of the XML signature this is the Id associated with the certificate if any.
*/
private String xmlId;
/**
* This method returns an instance of {@link eu.europa.ec.markt.dss.validation102853.CertificateToken}.
*
* @param cert <code>X509Certificate</code>
* @param id DSS unique certificate identifier
* @return
*/
static CertificateToken newInstance(X509Certificate cert, int id) {
return new CertificateToken(cert, id);
}
/**
* Creates a CertificateToken wrapping the provided X509Certificate. A certificate must come from a source like:
* trusted store, trusted list, signature...
*
* @param x509Certificate X509Certificate
* @param id DSS internal id (unique certificate's identifier)
*/
protected CertificateToken(X509Certificate x509Certificate, int id) {
this.dssId = id;
this.x509Certificate = x509Certificate;
this.issuerX500Principal = DSSUtils.getIssuerX500Principal(x509Certificate);
// The Algorithm OID is used and not the name {@code x509Certificate.getSigAlgName()}
final String sigAlgOID = x509Certificate.getSigAlgOID();
final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.forOID(sigAlgOID);
this.algorithmUsedToSignToken = signatureAlgorithm;
super.extraInfo = this.extraInfo = new CertificateTokenValidationExtraInfo();
}
/**
* This method adds the source type of the certificate (what is its origin). Each source is present only once.
*
* @param certSourceType
*/
public void addSourceType(final CertificateSourceType certSourceType) {
if (certSourceType != null) {
synchronized (sources) {
if (!sources.contains(certSourceType)) {
sources.add(certSourceType);
}
}
}
}
/**
* This method adds the associated trusted service information.
*
* @param serviceInfo
*/
public void addServiceInfo(final ServiceInfo serviceInfo) {
if (serviceInfo != null) {
synchronized (associatedTSPS) {
if (!associatedTSPS.contains(serviceInfo)) {
associatedTSPS.add(serviceInfo);
}
}
}
}
/**
* Returns a DSS unique certificate token identifier based on the issuer distinguish name and serial number.
*/
public int getDSSId() {
return dssId;
}
/**
* Returns a string representation of the unique DSS certificate token identifier.
*/
public String getDSSIdAsString() {
if (dssId == 0) {
return "[" + x509Certificate.getSubjectX500Principal().getName(X500Principal.CANONICAL) + "]";
}
return "[" + dssId + "]";
}
@Override
public String getAbbreviation() {
return getDSSIdAsString();
}
/**
* @param revocationToken This is the reference to the CertificateStatus. The object type is used because of the organisation
* of module.
*/
public void setRevocationToken(RevocationToken revocationToken) {
this.revocationToken = revocationToken;
}
/**
* Returns the certificate revocation revocationToken object.
*/
public RevocationToken getRevocationToken() {
return revocationToken;
}
/**
* Returns the public key associated with the certificate.<br>
* <p/>
* To get the encryption algorithm used with this public key call getAlgorithm() method.<br>
* RFC 2459:<br>
* 4.1.2.7 Subject Public Key Info
* <p/>
* This field is used to carry the public key and identify the algorithm with which the key is used. The algorithm is
* identified using the AlgorithmIdentifier structure specified in section 4.1.1.2. The object identifiers for the
* supported algorithms and the methods for encoding the public key materials (public key and parameters) are
* specified in section 7.3.
*
* @return
*/
public PublicKey getPublicKey() {
return x509Certificate.getPublicKey();
}
/**
* Returns .
*
* @return
*/
public Date getNotAfter() {
return x509Certificate.getNotAfter();
}
/**
* Returns .
*
* @return
*/
public Date getNotBefore() {
return x509Certificate.getNotBefore();
}
/**
* Checks if the certificate is expired on the given date.
*
* @param date
* @return
*/
public boolean isExpiredOn(final Date date) {
if (x509Certificate == null || date == null) {
return true;
}
return x509Certificate.getNotAfter().before(date);
}
/**
* Checks if the given date is in the validity period of the certificate.
*
* @param date
* @return
*/
public boolean isValidOn(final Date date) {
if (x509Certificate == null || date == null) {
return false;
}
try {
x509Certificate.checkValidity(date);
return true;
} catch (CertificateExpiredException e) {
return false;
} catch (CertificateNotYetValidException e) {
return false;
}
}
/**
* This method indicates if the encapsulated certificate is revoked.
*
* @return null if the revocation data cannot be checked, or true or false
*/
public Boolean isRevoked() {
if (isTrusted()) {
return false;
}
if (revocationToken == null) {
return null;
}
Boolean status = revocationToken.getStatus();
if (status == null) {
return null;
}
status = !status;
return status;
}
/**
* Checks if the certificate is provided by the trusted source.
*
* @return
*/
public boolean isTrusted() {
return sources.contains(CertificateSourceType.TRUSTED_LIST) || sources.contains(CertificateSourceType.TRUSTED_STORE);
}
/**
* Checks if the certificate is self-signed.
*
* @return
*/
public boolean isSelfSigned() {
if (selfSigned == null) {
final String n1 = x509Certificate.getSubjectX500Principal().getName(X500Principal.CANONICAL);
final String n2 = x509Certificate.getIssuerX500Principal().getName(X500Principal.CANONICAL);
selfSigned = n1.equals(n2);
}
return selfSigned;
}
/**
* Compares a given one-off id with this of the wrapped certificate.
*
* @param id The DSS validation process one-off certificate's id
* @return
*/
public boolean equals(final int id) {
return this.dssId == id;
}
@Override
public int hashCode() {
return dssId;
}
@Override
public boolean equals(final Object obj) {
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
return dssId == ((CertificateToken) obj).dssId;
}
/**
* Gets the enclosed X509 Certificate.
*
* @return
*/
public X509Certificate getCertificate() {
return x509Certificate;
}
/**
* Returns the encoded form of this certificate. X.509 certificates would be encoded as ASN.1 DER.
*
* @return the encoded form of this certificate
*/
@Override
public byte[] getEncoded() {
final byte[] bytes = DSSUtils.getEncoded(x509Certificate);
return bytes;
}
/**
* Gets information about the context in which this certificate token was created (TRUSTED_LIST, TRUSTED_STORE, ...).
* This method does not guarantee that the token is trusted or not.
*
* @return
*/
public List<CertificateSourceType> getSources() {
return Collections.unmodifiableList(sources);
}
/**
* Gets information about the trusted context of the certificate. See {@link eu.europa.ec.markt.dss.validation102853.condition.ServiceInfo} for more information.
*
* @return
*/
public List<ServiceInfo> getAssociatedTSPS() {
if (isTrusted()) {
return Collections.unmodifiableList(associatedTSPS);
}
return null;
}
/**
* Gets the serialNumber value from the encapsulated certificate. The serial number is an integer assigned by the
* certification authority to each certificate. It must be unique for each certificate issued by a given CA.
*
* @return
*/
public BigInteger getSerialNumber() {
return x509Certificate.getSerialNumber();
}
/**
* Returns the subject (subject distinguished name) value from the certificate as an X500Principal. If the subject
* value is empty, then the getName() method of the returned X500Principal object returns an empty string ("").
*
* @return
*/
public X500Principal getSubjectX500Principal() {
if (subjectX500PrincipalNormalized == null) {
subjectX500PrincipalNormalized = DSSUtils.getSubjectX500Principal(x509Certificate);
}
return subjectX500PrincipalNormalized;
}
@Override
public boolean isSignedBy(final CertificateToken issuerToken) {
signatureValid = false;
signatureInvalidityReason = "";
try {
final PublicKey publicKey = issuerToken.getCertificate().getPublicKey();
x509Certificate.verify(publicKey);
signatureValid = true;
if (!isSelfSigned()) {
this.issuerToken = issuerToken;
}
} catch (InvalidKeyException e) {
signatureInvalidityReason = "InvalidKeyException - on incorrect key.";
} catch (CertificateException e) {
signatureInvalidityReason = "CertificateException - on encoding errors.";
} catch (NoSuchAlgorithmException e) {
signatureInvalidityReason = "NoSuchAlgorithmException - on unsupported signature algorithms.";
} catch (SignatureException e) {
signatureInvalidityReason = "SignatureException - on signature errors.";
if (LOG.isDebugEnabled()) {
LOG.debug("ERROR: {} is not signed by {}: {}", new Object[]{getAbbreviation(), issuerToken.getAbbreviation(), e.getMessage()});
}
} catch (NoSuchProviderException e) { // if there's no default provider.
throw new DSSException(e);
}
return signatureValid;
}
/**
* Indicates that a X509Certificates corresponding private key is used by an authority to sign OCSP-Responses.<br>
* http://www.ietf.org/rfc/rfc3280.txt <br>
* http://tools.ietf.org/pdf/rfc6960.pdf 4.2.2.2<br>
* {iso(1) identified-organization(3) dod(6) internet(1) security(5) mechanisms(5) pkix(7) keyPurpose(3)
* ocspSigning(9)}<br>
* OID: 1.3.6.1.5.5.7.3.9
*
* @return
*/
public boolean isOCSPSigning() {
try {
List<String> keyPurposes = x509Certificate.getExtendedKeyUsage();
if (keyPurposes != null && keyPurposes.contains(OID.id_kp_OCSPSigning.getId())) {
return true;
}
} catch (CertificateParsingException e) {
LOG.warn(e.getMessage());
}
// Responder's certificate not valid for signing OCSP responses.
return false;
}
/**
* Indicates if the revocation data should be checked for an OCSP signing certificate.<br>
* http://www.ietf.org/rfc/rfc2560.txt?number=2560<br>
* A CA may specify that an OCSP client can trust a responder for the lifetime of the responder's certificate. The CA
* does so by including the extension id-pkix-ocsp-nocheck. This SHOULD be a non-critical extension. The value of the
* extension should be NULL.
*
* @return
*/
public boolean hasIdPkixOcspNoCheckExtension() {
final byte[] extensionValue = x509Certificate.getExtensionValue(OID.id_pkix_ocsp_no_check.getId());
if (extensionValue != null) {
try {
final ASN1Primitive derObject = DSSASN1Utils.toASN1Primitive(extensionValue);
if (derObject instanceof DEROctetString) {
final boolean derOctetStringNull = DSSASN1Utils.isDEROctetStringNull((DEROctetString) derObject);
return derOctetStringNull;
}
} catch (Exception e) {
LOG.debug("Exception when processing 'id_pkix_ocsp_no_check'", e);
}
}
return false;
}
/**
* Indicates if this certificate has an CRL extension expiredCertOnCRL.
*
* @return
*/
public boolean hasExpiredCertOnCRLExtension() {
final byte[] extensionValue = x509Certificate.getExtensionValue(OID.id_ce_expiredCertsOnCRL.getId());
if (extensionValue != null) {
try {
final ASN1Primitive derObject = DSSASN1Utils.toASN1Primitive(extensionValue);
if (derObject instanceof DEROctetString) {
final boolean derOctetStringNull = DSSASN1Utils.isDEROctetStringNull((DEROctetString) derObject);
return derOctetStringNull;
}
} catch (Exception e) {
LOG.debug("Exception when processing 'id_ce_expiredCertsOnCRL'", e);
}
}
return false;
}
/**
* Returns the object managing the validation extra info.
*
* @return
*/
public CertificateTokenValidationExtraInfo extraInfo() {
return extraInfo;
}
public DigestAlgorithm getDigestAlgorithm() {
return digestAlgorithm;
}
/**
* Returns the encoded base 64 digest value of the certificate for a given algorithm. Can return null if the
* algorithm is unknown.
*
* @param digestAlgorithm
* @return
*/
public String getDigestValue(final DigestAlgorithm digestAlgorithm) {
if (digests == null) {
digests = new HashMap<DigestAlgorithm, String>();
}
String encodedDigest = digests.get(digestAlgorithm);
if (encodedDigest == null) {
final byte[] digest = DSSUtils.digest(digestAlgorithm, DSSUtils.getEncoded(x509Certificate));
encodedDigest = DSSUtils.base64Encode(digest);
digests.put(digestAlgorithm, encodedDigest);
}
return encodedDigest;
}
/**
* Returns the trust anchor associated with the certificate. If it is the self-signed certificate then {@code this} is returned.
*
* @return
*/
public CertificateToken getTrustAnchor() {
if (isSelfSigned() && isTrusted()) {
return this;
}
CertificateToken issuerCertToken = getIssuerToken();
while (issuerCertToken != null) {
if (issuerCertToken.isTrusted()) {
return issuerCertToken;
}
issuerCertToken = issuerCertToken.getIssuerToken();
}
return null;
}
/**
* @return array of {@code byte}s representing the value of the CRL distribution point(s) extension of the wrapped certificate or {@code null} if it is not present.
*/
public byte[] getCRLDistributionPoints() {
final String id = Extension.cRLDistributionPoints.getId();
final byte[] extensionValue = x509Certificate.getExtensionValue(id);
return extensionValue;
}
/**
* @return true if the wrapped certificate has cRLSign key usage bit set
*/
public boolean hasCRLSignKeyUsage() {
final boolean[] keyUsage = x509Certificate.getKeyUsage();
final boolean crlSignKeyUsage = keyUsage != null || (keyUsage != null && keyUsage[6]);
return crlSignKeyUsage;
}
/**
* @return the size of the public key of the certificate
*/
public int getPublicKeyLength() {
final int publicKeySize = DSSPKUtils.getPublicKeySize(getPublicKey());
return publicKeySize;
}
/**
* This method checks if the certificate contains the given key usage bit.
*
* @param index the index of the key usage to be checked.
* @return true if contains
*/
public boolean checkKeyUsage(final int index) {
return x509Certificate.getKeyUsage()[index];
}
@Override
public String toString(String indentStr) {
try {
final StringBuffer out = new StringBuffer();
out.append(indentStr).append("CertificateToken[\n");
indentStr += "\t";
String issuerAsString = "";
if (issuerToken == null) {
if (isSelfSigned()) {
issuerAsString = "[SELF-SIGNED]";
} else {
issuerAsString = getIssuerX500Principal().toString();
}
} else {
issuerAsString = issuerToken.getDSSIdAsString();
}
String certSource = "UNKNOWN";
if (sources.size() > 0) {
for (final CertificateSourceType source : sources) {
final String name = source.name();
if ("UNKNOWN".equals(certSource)) {
certSource = name;
} else {
certSource += "/" + name;
}
}
}
out.append(indentStr).append(getDSSIdAsString()).append("<--").append(issuerAsString).append(", source=").append(certSource);
out.append(", serial=" + x509Certificate.getSerialNumber()).append('\n');
// Validity period
final String certStartDate = DSSUtils.formatInternal(x509Certificate.getNotBefore());
final String certEndDate = DSSUtils.formatInternal(x509Certificate.getNotAfter());
out.append(indentStr).append("Validity period : ").append(certStartDate).append(" - ").append(certEndDate).append('\n');
out.append(indentStr).append("Subject name : ").append(getSubjectX500Principal()).append('\n');
out.append(indentStr).append("Issuer subject name: ").append(getIssuerX500Principal()).append('\n');
if (sources.contains(CertificateSourceType.TRUSTED_LIST)) {
for (ServiceInfo si : associatedTSPS) {
out.append(indentStr).append("Service Info :\n");
indentStr += "\t";
out.append(si.toString(indentStr));
indentStr = indentStr.substring(1);
}
}
out.append(indentStr).append("Signature algorithm: ").append(algorithmUsedToSignToken == null ? "?" : algorithmUsedToSignToken).append('\n');
if (isTrusted()) {
out.append(indentStr).append("Signature validity : Signature verification is not needed: trusted certificate\n");
} else {
if (signatureValid) {
out.append(indentStr).append("Signature validity : VALID").append('\n');
} else {
if (!signatureInvalidityReason.isEmpty()) {
out.append(indentStr).append("Signature validity : INVALID").append(" - ").append(signatureInvalidityReason).append('\n');
}
}
}
if (revocationToken != null) {
out.append(indentStr).append("Revocation data[\n");
indentStr += "\t";
final CertificateToken revocationTokenIssuerToken = revocationToken.getIssuerToken();
out.append(indentStr).append("Status: ").append(revocationToken.getStatus()).append(" / ").append(revocationToken.getIssuingTime())
.append(" / issuer's certificate ").append(revocationTokenIssuerToken != null ? revocationTokenIssuerToken.getDSSIdAsString() : "null").append('\n');
indentStr = indentStr.substring(1);
out.append(indentStr).append("]\n");
} else {
if (isSelfSigned()) {
out.append(indentStr).append("Verification of revocation data is not necessary: self-signed certificate.\n");
} else if (isTrusted()) {
out.append(indentStr).append("Verification of revocation data is not necessary: trusted certificate.\n");
} else {
out.append(indentStr).append("There is no revocation data available!\n");
}
}
if (issuerToken != null) {
out.append(indentStr).append("Issuer certificate[\n");
indentStr += "\t";
if (issuerToken.isSelfSigned()) {
out.append(indentStr).append(issuerToken.getDSSIdAsString()).append(" SELF-SIGNED");
} else {
out.append(issuerToken.toString(indentStr));
}
out.append('\n');
indentStr = indentStr.substring(1);
out.append(indentStr).append("]\n");
}
for (String info : this.extraInfo.getValidationInfo()) {
out.append(indentStr).append("- ").append(info).append('\n');
}
indentStr = indentStr.substring(1);
out.append(indentStr).append("]");
return out.toString();
} catch (Exception e) {
return e.getMessage();
}
}
private List<String> policyIdentifiers = null;
public List<String> getPolicyIdentifiers() {
if (policyIdentifiers == null) {
policyIdentifiers = DSSUtils.getPolicyIdentifiers(x509Certificate);
}
return policyIdentifiers;
}
private List<String> qcStatementsIdList = null;
public List<String> getQCStatementsIdList() {
if (qcStatementsIdList == null) {
qcStatementsIdList = DSSUtils.getQCStatementsIdList(x509Certificate);
}
return qcStatementsIdList;
}
/**
* @return return the id associated with the certificate in case of an XML signature, or null
*/
public String getXmlId() {
return xmlId;
}
/**
* Sets the Id associated with the certificate in case of an XML signature.
*
* @param xmlId id
*/
public void setXmlId(final String xmlId) {
this.xmlId = xmlId;
}
/**
* This method returns a {@code String} representing the key usages of the certificate.
*
* @return {@code List} of {@code String}s of different certificate's key usages
*/
public List<String> getKeyUsageBits() {
boolean[] keyUsageArray = x509Certificate.getKeyUsage();
if (keyUsageArray == null) {
return null;
}
final List<String> keyUsageBits = new ArrayList<String>();
if (keyUsageArray[0]) {
keyUsageBits.add(DIGITAL_SIGNATURE);
}
if (keyUsageArray[1]) {
keyUsageBits.add(NON_REPUDIATION);
}
if (keyUsageArray[2]) {
keyUsageBits.add(KEY_ENCIPHERMENT);
}
if (keyUsageArray[3]) {
keyUsageBits.add(DATA_ENCIPHERMENT);
}
if (keyUsageArray[4]) {
keyUsageBits.add(KEY_AGREEMENT);
}
if (keyUsageArray[5]) {
keyUsageBits.add(KEY_CERT_SIGN);
}
if (keyUsageArray[6]) {
keyUsageBits.add(CRL_SIGN);
}
if (keyUsageArray[7]) {
keyUsageBits.add(ENCIPHER_ONLY);
}
if (keyUsageArray[8]) {
keyUsageBits.add(DECIPHER_ONLY);
}
return keyUsageBits;
}
/**
* Gets the DER-encoded OCTET string for the extension value identified by the passed-in {@code oid}
* The {@code oid} string is represented by a set of non-negative positive integer numbers separated by periods.
*
* @param oid {@code ASN1ObjectIdentifier} value of the extension
* @return the DER-encoded octet string of the extension value or null if it is not present
*/
public byte[] getExtensionValue(final ASN1ObjectIdentifier oid) {
final byte[] extensionValue = x509Certificate.getExtensionValue(oid.getId());
return extensionValue;
}
}