/* DigiDoc4J library
*
* This software is released under either the GNU Library General Public
* License (see LICENSE.LGPL).
*
* Note that the only valid version of the LGPL license as far as this
* project is concerned is the original GNU Library General Public License
* Version 2.1, February 1999
*/
package org.digidoc4j;
import org.apache.commons.lang.StringUtils;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.*;
/**
* Wrapper for java.security.cert.X509Certificate object.
*/
public class X509Cert implements Serializable {
private static final Logger logger = LoggerFactory.getLogger(X509Cert.class);
private X509Certificate originalCert;
private Map<String, String> issuerPartMap;
private Map<String, String> subjectNamePartMap;
/**
* Key usage.
*/
public enum KeyUsage {
DIGITAL_SIGNATURE,
/**
* Used for signing certificate selection in the current library
*/
NON_REPUDIATION,
KEY_ENCIPHERMENT,
DATA_ENCIPHERMENT,
KEY_AGREEMENT,
KEY_CERTIFICATESIGN,
CRL_SIGN,
ENCIPHER_ONLY,
DECIPHER_ONLY
}
/**
* Issuer parts.
*/
public enum Issuer {
EMAILADDRESS,
C,
O,
CN
}
/**
* Subject Name parts.
*/
public enum SubjectName {
SERIALNUMBER,
GIVENNAME,
SURNAME,
CN,
OU,
O,
C
}
/**
* Creates a copy of the X509Certificate.
*
* @param cert X509 certificate to be wrapped
*/
public X509Cert(X509Certificate cert) {
logger.debug("");
originalCert = cert;
}
/**
* Creates an X509 certificate from a path.
*
* @param path X509 certificate path
* @throws Exception throws an exception if the X509 certificate parsing fails
*/
X509Cert(String path) throws CertificateException, IOException {
logger.debug("");
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
try (FileInputStream inStream = new FileInputStream(new File(path))) {
originalCert = (X509Certificate) certificateFactory.generateCertificate(inStream);
}
logger.debug("Certificate created from path: " + path);
}
/**
* Returns current certificate policies or null if no policies was found.
*
* @return list of policies
* @throws IOException when policy parsing fails
*/
public List<String> getCertificatePolicies() throws IOException {
logger.debug("");
byte[] extensionValue = originalCert.getExtensionValue("2.5.29.32");
List<String> policies = new ArrayList<>();
byte[] octets = ((DEROctetString) DEROctetString.fromByteArray(extensionValue)).getOctets();
ASN1Sequence sequence = (ASN1Sequence) ASN1Sequence.fromByteArray(octets);
Enumeration sequenceObjects = sequence.getObjects();
while (sequenceObjects.hasMoreElements()) {
DLSequence next = (DLSequence) sequenceObjects.nextElement();
Object objectAt = next.getObjectAt(0);
if (objectAt != null) {
policies.add(objectAt.toString());
}
}
return policies;
}
/**
* Returns the internal X509 Certificate of the certificate.
*
* @return X509Certificate
*/
public X509Certificate getX509Certificate() {
logger.debug("");
return originalCert;
}
/**
* Retrieves part of the issuer name (for example if set to CN it returns the Common Name part).
*
* @param part sets part of issuer name to return
* @return part of issuer name
*/
public String issuerName(Issuer part) {
logger.debug("Part: " + part);
if (issuerPartMap == null) {
loadIssuerParts();
}
String issuerName = issuerPartMap.get(part.name());
logger.debug("Issuer name: " + issuerName);
return issuerName;
}
private void loadIssuerParts() {
logger.debug("");
String[] parts = StringUtils.split(issuerName(), ',');
issuerPartMap = new HashMap<>();
for (String part : parts) {
String[] strings = StringUtils.split(part, "=");
String key = strings[0].trim();
String value = strings[1].trim();
issuerPartMap.put(key, value);
logger.debug("Subject name part key: " + key + " value: " + value);
}
}
/**
* Reads the the whole issuer name from the X.509 certificate.
*
* @return issuer name
*/
public String issuerName() {
logger.debug("");
String name = originalCert.getIssuerDN().getName();
logger.debug("Issuer name: " + name);
return name;
}
/**
* Validates if the certificate is in a valid time slot.
*
* @param date sets date to compare
* @return boolean indicating if the certificate is in a valid time slot
*/
public boolean isValid(Date date) {
logger.debug("Date: " + date);
try {
originalCert.checkValidity(date);
} catch (CertificateExpiredException e) {
logger.debug("Date " + date + " is not valid");
return false;
} catch (CertificateNotYetValidException e) {
logger.debug("Date " + date + " is not valid");
return false;
}
logger.debug("Date " + date + " is valid");
return true;
}
/**
* Validates if the current time is between the certificate's validity start date and expiration date.
*
* @return boolean indicating if the current time is between the certificate's validity start and expiration date
*/
public boolean isValid() {
logger.debug("");
return (isValid(new Date()));
}
/**
* Returns the current certificate key usage.
*
* @return list of key usages
*/
public List<KeyUsage> getKeyUsages() {
logger.debug("");
List<KeyUsage> keyUsages = new ArrayList<>();
boolean[] keyUsagesBits = originalCert.getKeyUsage();
for (int i = 0; i < keyUsagesBits.length; i++) {
if (keyUsagesBits[i]) {
keyUsages.add(KeyUsage.values()[i]);
}
}
logger.debug("Returning " + keyUsages.size() + "key usages:");
for (KeyUsage keyUsage : keyUsages) {
logger.debug("\t" + keyUsage.toString());
}
return keyUsages;
}
/**
* Reads serial number from X.509 certificate.
*
* @return serial number of the X.509 certificate
*/
public String getSerial() {
logger.debug("");
String serial = Hex.toHexString(originalCert.getSerialNumber().toByteArray());
logger.debug("Serial number: " + serial);
return serial;
}
/**
* Returns part of the subject name (for example if set to CN it returns the Common Name part).
*
* @param part sets part of subject name to return
* @return subject name
*/
public String getSubjectName(SubjectName part) {
logger.debug("Part: " + part);
if (subjectNamePartMap == null) {
loadSubjectNameParts();
}
String subjectName = subjectNamePartMap.get(part.name());
logger.debug("Subject name: " + subjectName);
return subjectName;
}
private void loadSubjectNameParts() {
logger.debug("");
String[] parts = getSubjectName().split(",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
subjectNamePartMap = new HashMap<>();
for (String part : parts) {
String[] strings = part.split("=(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
String key = strings[0].trim();
String value = strings[1].trim();
subjectNamePartMap.put(key, value);
logger.debug("Subject name part key: " + key + " value: " + value);
}
}
/**
* Returns the whole subject name.
*
* @return subject name
*/
public String getSubjectName() {
logger.debug("");
String subjectName = originalCert.getSubjectX500Principal().toString();
logger.debug("Subject name: " + subjectName);
return subjectName;
}
}