/* 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.impl.ddoc;
import static ee.sk.digidoc.DataFile.CONTENT_EMBEDDED_BASE64;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.digidoc4j.Configuration;
import org.digidoc4j.Container;
import org.digidoc4j.DataFile;
import org.digidoc4j.DigestAlgorithm;
import org.digidoc4j.Signature;
import org.digidoc4j.SignatureParameters;
import org.digidoc4j.SignatureProfile;
import org.digidoc4j.SignatureToken;
import org.digidoc4j.SignedInfo;
import org.digidoc4j.ValidationResult;
import org.digidoc4j.X509Cert;
import org.digidoc4j.exceptions.DigiDoc4JException;
import org.digidoc4j.exceptions.NotSupportedException;
import org.digidoc4j.impl.SignatureFinalizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ee.sk.digidoc.DigiDocException;
import ee.sk.digidoc.KeyInfo;
import ee.sk.digidoc.SignatureProductionPlace;
import ee.sk.digidoc.SignedDoc;
/**
* Offers functionality for handling data files and signatures in a container.
* <p>
* A container can contain several files and all those files can be signed using signing certificates.
* A container can only be signed if it contains data files.
* </p><p>
* Data files can be added and removed from a container only if the container is not signed.
* To modify the data list of a signed container by adding or removing datafiles you must first
* remove all the signatures.
* </p>
*/
public class DDocFacade implements SignatureFinalizer, Serializable {
private static final Logger logger = LoggerFactory.getLogger(DDocFacade.class);
SignedDoc ddoc;
private ArrayList<DigiDocException> openContainerExceptions = new ArrayList<>();
private SignatureProfile signatureProfile = SignatureProfile.LT_TM;
private SignatureParameters signatureParameters = new SignatureParameters();
protected ee.sk.digidoc.Signature ddocSignature;
private Configuration configuration;
static ConfigManagerInitializer configManagerInitializer = new ConfigManagerInitializer();
public DDocFacade() {
logger.debug("");
intConfiguration();
createDDOCContainer();
}
public DDocFacade(Configuration configuration) {
logger.debug("");
this.configuration = configuration;
initConfigManager(configuration);
createDDOCContainer();
}
DDocFacade(SignedDoc ddoc) {
logger.debug("");
intConfiguration();
this.ddoc = ddoc;
}
public SignedInfo prepareSigning(X509Certificate signerCert) {
logger.info("Preparing signing");
List<String> signerRoles = signatureParameters.getRoles();
org.digidoc4j.SignatureProductionPlace signatureProductionPlace = signatureParameters.getProductionPlace();
SignatureProductionPlace productionPlace = new SignatureProductionPlace(signatureProductionPlace.getCity(),
signatureProductionPlace.getStateOrProvince(), signatureProductionPlace.getCountry(),
signatureProductionPlace.getPostalCode());
if(signatureParameters.getDigestAlgorithm() == null) {
signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA1);
}
try {
ddocSignature = ddoc.prepareSignature(signerCert, signerRoles.toArray(new String[signerRoles.size()]),
productionPlace);
String signatureId = signatureParameters.getSignatureId();
if (signatureId != null) ddocSignature.setId(signatureId);
return new SignedInfo(ddocSignature.calculateSignedInfoXML(), signatureParameters);
} catch (DigiDocException e) {
logger.error(e.getMessage());
throw new DigiDoc4JException(e);
}
}
public String getSignatureProfile() {
String name = signatureProfile.name();
logger.debug("Signature profile: " + name);
return name;
}
public void setSignatureParameters(SignatureParameters signatureParameters) {
logger.debug("");
DigestAlgorithm algorithm = signatureParameters.getDigestAlgorithm();
if (algorithm == null) {
signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA1);
} else if (algorithm != DigestAlgorithm.SHA1) {
NotSupportedException exception = new NotSupportedException("DDOC 1.3 supports only SHA1 as digest "
+ "algorithm. Specified algorithm is " + algorithm);
logger.error(exception.toString());
throw exception;
}
addSignatureProfile(signatureParameters);
this.signatureParameters = signatureParameters.copy();
}
private void addSignatureProfile(SignatureParameters signatureParameters) {
if(signatureParameters.getSignatureProfile() != null) {
setSignatureProfile(signatureParameters.getSignatureProfile());
}
}
public DigestAlgorithm getDigestAlgorithm() {
DigestAlgorithm digestAlgorithm = signatureParameters.getDigestAlgorithm();
logger.debug("Digest algorithm: " + digestAlgorithm);
return digestAlgorithm;
}
private void intConfiguration() {
logger.debug("");
configuration = Configuration.getInstance();
initConfigManager(configuration);
}
private void createDDOCContainer() {
logger.debug("");
try {
ddoc = new SignedDoc("DIGIDOC-XML", "1.3");
signatureParameters.setDigestAlgorithm(DigestAlgorithm.SHA1);
logger.info("DDoc container created");
} catch (DigiDocException e) {
logger.error(e.getMessage());
throw new DigiDoc4JException(e.getNestedException());
}
}
public DataFile addDataFile(String path, String mimeType) {
logger.info("Adding data file: " + path + ", mime type " + mimeType);
try {
ddoc.addDataFile(new File(path), mimeType, CONTENT_EMBEDDED_BASE64);
return new DataFile(path, mimeType);
} catch (DigiDocException e) {
logger.error(e.getMessage());
throw new DigiDoc4JException(e.getNestedException());
}
}
public DataFile addDataFile(InputStream is, String fileName, String mimeType) {
logger.info("Adding data file: " + fileName + ", mime type: " + mimeType);
try {
ee.sk.digidoc.DataFile dataFile = new ee.sk.digidoc.DataFile(ddoc.getNewDataFileId(),
ee.sk.digidoc.DataFile.CONTENT_EMBEDDED_BASE64,
fileName, mimeType, ddoc);
byte[] data = IOUtils.toByteArray(is);
dataFile.setBody(data);
ddoc.addDataFile(dataFile);
return new DataFile(is, fileName, mimeType);
} catch (DigiDocException | IOException e) {
logger.error(e.getMessage());
throw new DigiDoc4JException(e);
}
}
public void addDataFile(DataFile dataFile) {
addDataFile(dataFile.getStream(), dataFile.getName(), dataFile.getMediaType());
}
public void addRawSignature(byte[] signatureBytes) {
logger.debug("");
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(signatureBytes);
addRawSignature(byteArrayInputStream);
IOUtils.closeQuietly(byteArrayInputStream);
}
public void addRawSignature(InputStream signatureStream) {
logger.info("Adding raw XAdES signature");
try {
ddoc.readSignature(signatureStream);
} catch (DigiDocException e) {
logger.error(e.getMessage());
throw new DigiDoc4JException(e.getNestedException());
}
}
public List<DataFile> getDataFiles() {
logger.debug("");
List<DataFile> dataFiles = new ArrayList<>();
ArrayList ddocDataFiles = ddoc.getDataFiles();
if (ddocDataFiles == null) return dataFiles;
for (Object ddocDataFile : ddocDataFiles) {
ee.sk.digidoc.DataFile dataFile = (ee.sk.digidoc.DataFile) ddocDataFile;
try {
if (dataFile.getBody() == null) {
DataFile dataFile1 = new DataFile(dataFile.getFileName(), dataFile.getMimeType());
dataFile1.setId(dataFile.getId());
dataFiles.add(dataFile1);
} else {
DataFile dataFile1 = new DataFile(dataFile.getBodyAsData(), dataFile.getFileName(), dataFile.getMimeType());
dataFile1.setId(dataFile.getId());
dataFiles.add(dataFile1);
}
} catch (DigiDocException e) {
logger.error(e.getMessage());
throw new DigiDoc4JException(e.getNestedException());
}
}
return dataFiles;
}
/**
* @deprecated will be removed in the future.
*/
@Deprecated
public DataFile getDataFile(int index) {
logger.debug("Get data file for index " + index);
return getDataFiles().get(index);
}
public int countDataFiles() {
logger.debug("Get the number of data files");
List<DataFile> dataFiles = getDataFiles();
return (dataFiles == null) ? 0 : dataFiles.size();
}
public void removeDataFile(String fileName) {
logger.debug("File name: " + fileName);
removeDataFile(new File(fileName));
}
private void removeDataFile(File file) {
logger.info("Removing data file: " + file.getName());
int index = -1;
ArrayList ddocDataFiles = ddoc.getDataFiles();
for (int i = 0; i < ddocDataFiles.size(); i++) {
ee.sk.digidoc.DataFile dataFile = (ee.sk.digidoc.DataFile) ddocDataFiles.get(i);
if (dataFile.getFileName().equalsIgnoreCase(file.getName())) index = i;
}
if (index == -1) {
DigiDoc4JException exception = new DigiDoc4JException("File not found");
logger.error(exception.toString());
throw exception;
}
try {
ddoc.removeDataFile(index);
} catch (DigiDocException e) {
logger.error(e.getMessage());
throw new DigiDoc4JException(e.getNestedException());
}
}
/**
* @deprecated will be removed in the future.
*/
public void removeSignature(int index) {
logger.info("Removing signature index: " + index);
try {
ddoc.removeSignature(index);
} catch (DigiDocException e) {
logger.error(e.getMessage());
throw new DigiDoc4JException(e.getNestedException());
}
}
public void save(String path) {
logger.info("Saving container to path: " + path);
try {
ddoc.writeToFile(new File(path));
} catch (DigiDocException e) {
logger.error(e.getMessage());
throw new DigiDoc4JException(e.getNestedException());
}
}
public void save(OutputStream out) {
logger.info("Saving container to stream");
try {
ddoc.writeToStream(out);
} catch (DigiDocException e) {
logger.error(e.getMessage());
throw new DigiDoc4JException(e.getNestedException());
}
}
public Signature sign(SignatureToken signer) {
logger.info("Signing DDoc container");
calculateSignature(signer);
try {
signRaw(signer.sign(getDigestAlgorithm(), ddocSignature.calculateSignedInfoXML()));
} catch (DigiDocException e) {
logger.error(e.getMessage());
throw new DigiDoc4JException(e.getNestedException());
}
return new DDocSignature(ddocSignature);
}
public Signature signRaw(byte[] rawSignature) {
logger.info("Finalizing DDoc signature");
try {
ddocSignature.setSignatureValue(rawSignature);
DDocSignature signature = new DDocSignature(ddocSignature);
if (signatureProfile == SignatureProfile.LT_TM) {
ddocSignature.getConfirmation();
}
signature.setIndexInArray(getSignatureIndexInArray());
logger.info("Signing DDoc successfully completed");
return signature;
} catch (DigiDocException e) {
logger.error(e.getMessage());
throw new DigiDoc4JException(e.getNestedException());
}
}
private int getSignatureIndexInArray() {
return ddoc.getSignatures().size() - 1;
}
public List<Signature> getSignatures() {
logger.debug("");
List<Signature> signatures = new ArrayList<>();
ArrayList dDocSignatures = ddoc.getSignatures();
if (dDocSignatures == null) {
return signatures;
}
int signatureIndexInArray = 0;
for (Object signature : dDocSignatures) {
DDocSignature finalSignature = mapJDigiDocSignatureToDigiDoc4J((ee.sk.digidoc.Signature) signature);
if (finalSignature != null) {
finalSignature.setIndexInArray(signatureIndexInArray);
signatures.add(finalSignature);
signatureIndexInArray++;
}
}
return signatures;
}
/**
* @deprecated will be removed in the future.
*/
public Signature getSignature(int index) {
logger.debug("Get signature for index " + index);
return getSignatures().get(index);
}
public int countSignatures() {
logger.debug("Get the number of signatures");
List<Signature> signatures = getSignatures();
return (signatures == null) ? 0 : signatures.size();
}
private DDocSignature mapJDigiDocSignatureToDigiDoc4J(ee.sk.digidoc.Signature signature) {
logger.debug("");
DDocSignature finalSignature = new DDocSignature(signature);
KeyInfo keyInfo = signature.getKeyInfo();
if (keyInfo == null) {
return null;
}
X509Certificate signersCertificate = keyInfo.getSignersCertificate();
finalSignature.setCertificate(new X509Cert(signersCertificate));
return finalSignature;
}
public Container.DocumentType getDocumentType() {
logger.debug("");
return Container.DocumentType.DDOC;
}
@SuppressWarnings("unchecked")
public ValidationResult validate() {
logger.info("Validating DDoc container");
ArrayList exceptions = ddoc.verify(true, true);
ArrayList containerExceptions = ddoc.validate(true);
containerExceptions.addAll(openContainerExceptions);
ValidationResultForDDoc result = new ValidationResultForDDoc(exceptions, containerExceptions);
logger.info("DDoc container is valid: " + result.isValid());
return result;
}
ee.sk.digidoc.Signature calculateSignature(SignatureToken signer) {
logger.debug("");
prepareSigning(signer.getCertificate());
return ddocSignature;
}
private void addConfirmation() {
logger.debug("");
for (Object signature : ddoc.getSignatures()) {
try {
((ee.sk.digidoc.Signature) signature).getConfirmation();
} catch (DigiDocException e) {
logger.error(e.getMessage());
throw new DigiDoc4JException(e.getNestedException());
}
}
}
public String getVersion() {
String version = ddoc.getVersion();
logger.debug("Version: " + version);
return version;
}
public void extendTo(SignatureProfile profile) {
logger.info("Extending signature profile to " + profile.name());
if (profile != SignatureProfile.LT_TM) {
String errorMessage = profile + " profile is not supported for DDOC extension";
logger.error(errorMessage);
throw new NotSupportedException(errorMessage);
}
addConfirmation();
}
public void setSignatureProfile(SignatureProfile profile) {
logger.debug("Adding signature profile " + profile);
if (profile != SignatureProfile.LT_TM && profile != SignatureProfile.B_BES) {
String errorMessage = profile + " profile is not supported for DDOC";
logger.error(errorMessage);
throw new NotSupportedException(errorMessage);
}
signatureProfile = profile;
}
/**
* Returns ddoc format
*
* @return format as string
*/
public String getFormat() {
String format = ddoc.getFormat();
logger.debug(format);
return format;
}
public Configuration getConfiguration() {
return configuration;
}
@Override
public Signature finalizeSignature(byte[] signatureValue) {
return signRaw(signatureValue);
}
private void initConfigManager(Configuration configuration) {
configManagerInitializer.initConfigManager(configuration);
}
protected void setSignedDoc(SignedDoc signedDoc) {
ddoc = signedDoc;
}
protected void setContainerOpeningExceptions(ArrayList<DigiDocException> openContainerExceptions) {
this.openContainerExceptions = openContainerExceptions;
}
}