/* 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 org.digidoc4j.ContainerBuilder.DDOC_CONTAINER_TYPE;
import static org.digidoc4j.DigestAlgorithm.SHA1;
import static org.digidoc4j.DigestAlgorithm.SHA224;
import static org.digidoc4j.DigestAlgorithm.SHA256;
import static org.digidoc4j.SignatureProfile.B_BES;
import static org.digidoc4j.SignatureProfile.LT;
import static org.digidoc4j.SignatureProfile.LTA;
import static org.digidoc4j.SignatureProfile.LT_TM;
import static org.digidoc4j.testutils.TestSigningHelper.getSigningCert;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import org.digidoc4j.Configuration;
import org.digidoc4j.Container;
import org.digidoc4j.ContainerBuilder;
import org.digidoc4j.ContainerOpener;
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.exceptions.ConfigurationException;
import org.digidoc4j.exceptions.DigiDoc4JException;
import org.digidoc4j.exceptions.NotSupportedException;
import org.digidoc4j.signers.PKCS12SignatureToken;
import org.digidoc4j.testutils.TestSigningHelper;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import ee.sk.digidoc.DataFile;
import ee.sk.digidoc.DigiDocException;
import ee.sk.digidoc.SignedDoc;
public class DDocFacadeTest {
public static final String TEXT_MIME_TYPE = "text/plain";
private PKCS12SignatureToken PKCS12_SIGNER;
@BeforeClass
public static void setTestMode() {
System.setProperty("digidoc4j.mode", "TEST");
}
@Before
public void setUp() throws Exception {
PKCS12_SIGNER = new PKCS12SignatureToken("testFiles/signout.p12", "test".toCharArray());
}
@AfterClass
public static void deleteTemporaryFiles() {
try {
DirectoryStream<Path> directoryStream = Files.newDirectoryStream(Paths.get("."));
for (Path item : directoryStream) {
String fileName = item.getFileName().toString();
if (fileName.endsWith("ddoc") && fileName.startsWith("test")) Files.deleteIfExists(item);
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Test(expected = DigiDoc4JException.class)
public void testSaveThrowsException() throws Exception {
DDocFacade container = new DDocFacade();
container.save("/not/existing/path/testSaveThrowsException.ddoc");
}
@Test
public void testGetDataFileSize() {
DDocFacade container = openDocFacade("testFiles/ddoc_for_testing.ddoc");
org.digidoc4j.DataFile dataFile = container.getDataFile(0);
assertEquals(16, dataFile.getFileSize());
}
@Test
public void testSetDigestAlgorithmSHA1() throws Exception {
DDocFacade container = new DDocFacade();
SignatureParameters signatureParameters = new SignatureParameters();
signatureParameters.setDigestAlgorithm(SHA1);
container.setSignatureParameters(signatureParameters);
}
@Test(expected = NotSupportedException.class)
public void testSetDigestAlgorithmOtherThenSHA1() throws Exception {
DDocFacade container = new DDocFacade();
SignatureParameters signatureParameters = new SignatureParameters();
signatureParameters.setDigestAlgorithm(SHA224);
container.setSignatureParameters(signatureParameters);
}
@Test
public void testCanAddTwoDataFilesWithSameName() throws Exception {
DDocFacade dDocContainer = new DDocFacade();
dDocContainer.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
dDocContainer.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
dDocContainer.save("test_ddoc_file.ddoc");
Container container = ContainerOpener.open("test_ddoc_file.ddoc");
List<org.digidoc4j.DataFile> dataFiles = container.getDataFiles();
assertEquals(2, dataFiles.size());
assertEquals("test.txt", dataFiles.get(0).getName());
assertEquals("test.txt", dataFiles.get(1).getName());
}
@Test
public void testGetFileId() {
DDocFacade container = new DDocFacade();
container.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
container.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
List<org.digidoc4j.DataFile> dataFiles = container.getDataFiles();
assertEquals("D0", dataFiles.get(0).getId());
assertEquals("D1", dataFiles.get(1).getId());
assertEquals("test.txt", dataFiles.get(0).getName());
assertEquals("test.txt", dataFiles.get(1).getName());
}
@Test
public void testAddEmptyFile() throws Exception {
DDocFacade dDocContainer = new DDocFacade();
//noinspection ResultOfMethodCallIgnored
new File("test_empty.txt").createNewFile();
dDocContainer.addDataFile("test_empty.txt", TEXT_MIME_TYPE);
dDocContainer.save("test_empty.ddoc");
Container container = ContainerOpener.open("test_empty.ddoc");
List<org.digidoc4j.DataFile> dataFiles = container.getDataFiles();
assertEquals(1, dataFiles.size());
assertEquals(0, dataFiles.get(0).getFileSize());
Files.deleteIfExists(Paths.get("test_empty.txt"));
}
@Test
public void getDataFileByIndex() {
DDocFacade container = new DDocFacade();
container.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
container.addDataFile("testFiles/test.xml", TEXT_MIME_TYPE);
assertEquals("D0", container.getDataFile(0).getId());
assertEquals("D1", container.getDataFile(1).getId());
assertEquals("test.txt", container.getDataFile(0).getName());
assertEquals("test.xml", container.getDataFile(1).getName());
}
@Test(expected = DigiDoc4JException.class)
public void testAddFileFromStreamToDDocThrowsException() throws DigiDocException, IOException {
SignedDoc ddoc = mock(SignedDoc.class);
when(ddoc.getNewDataFileId()).thenReturn("A");
when(ddoc.getFormat()).thenReturn("SignedDoc.FORMAT_DDOC");
doThrow(new DigiDocException(100, "testException", new Throwable("test Exception"))).
when(ddoc).addDataFile(any(ee.sk.digidoc.DataFile.class));
DDocFacade container = new DDocFacade(ddoc);
try(ByteArrayInputStream is = new ByteArrayInputStream(new byte[]{0x42})) {
container.addDataFile(is, "testFromStream.txt", TEXT_MIME_TYPE);
}
}
@Test(expected = DigiDoc4JException.class)
public void testAddDataFileThrowsException() throws Exception {
SignedDoc ddoc = mock(SignedDoc.class);
doThrow(new DigiDocException(100, "testException", new Throwable("test Exception"))).
when(ddoc).addDataFile(any(File.class), any(String.class), any(String.class));
DDocFacade container = new DDocFacade(ddoc);
container.addDataFile("testFiles/test.txt", "");
}
@Test(expected = DigiDoc4JException.class)
public void testGetDataFileThrowsException() throws Exception {
SignedDoc ddoc = spy(new SignedDoc("DIGIDOC-XML", "1.3"));
ee.sk.digidoc.DataFile dataFile = mock(ee.sk.digidoc.DataFile.class);
doThrow(new DigiDocException(100, "testException", new Throwable("test Exception"))).
when(dataFile).getBody();
ArrayList<ee.sk.digidoc.DataFile> mockedDataFiles = new ArrayList<>();
mockedDataFiles.add(dataFile);
doReturn(mockedDataFiles).when(ddoc).getDataFiles();
DDocFacade container = new DDocFacade(ddoc);
container.addDataFile("testFiles/test.txt", "text/plain");
container.getDataFiles();
}
@Test
public void testGetDataFilesWhenNoDataFileExists() {
DDocFacade container = new DDocFacade();
assertTrue(container.getDataFiles().isEmpty());
}
@Test(expected = DigiDoc4JException.class)
public void removeDataFileWhenNotFound() throws Exception {
DDocFacade dDocContainer = new DDocFacade();
dDocContainer.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
dDocContainer.removeDataFile("NotThere.txt");
}
@Test(expected = DigiDoc4JException.class)
public void removeDataFileThrowsException() throws Exception {
SignedDoc ddoc = mock(SignedDoc.class);
ArrayList<ee.sk.digidoc.DataFile> mockedDataFiles = new ArrayList<>();
DataFile dataFile = mock(DataFile.class);
when(dataFile.getFileName()).thenReturn("test.txt");
mockedDataFiles.add(dataFile);
doReturn(mockedDataFiles).when(ddoc).getDataFiles();
doThrow(new DigiDocException(100, "testException", new Throwable("test Exception"))).
when(ddoc).removeDataFile(anyInt());
DDocFacade container = new DDocFacade(ddoc);
container.addDataFile("testFiles/test.txt", "text/plain");
container.removeDataFile("test.txt");
}
@Test(expected = DigiDoc4JException.class)
public void containerWithFileNameThrowsException() throws Exception {
openDocFacade("file_not_exists");
}
@Test
public void setsSignatureId() throws Exception {
DDocFacade container = new DDocFacade();
container.addDataFile("testFiles/test.txt", "text/plain");
SignatureParameters signatureParameters = new SignatureParameters();
signatureParameters.setSignatureId("SIGNATURE-1");
container.setSignatureParameters(signatureParameters);
container.sign(PKCS12_SIGNER);
signatureParameters.setSignatureId("SIGNATURE-2");
container.setSignatureParameters(signatureParameters);
container.sign(PKCS12_SIGNER);
container.save("setsSignatureId.ddoc");
container = openDocFacade("setsSignatureId.ddoc");
assertEquals("SIGNATURE-1", container.getSignature(0).getId());
assertEquals("SIGNATURE-2", container.getSignature(1).getId());
}
@Test
public void setsDefaultSignatureId() throws Exception {
DDocFacade container = new DDocFacade();
container.addDataFile("testFiles/test.txt", "text/plain");
container.sign(PKCS12_SIGNER);
container.sign(PKCS12_SIGNER);
container.save("testSetsDefaultSignatureId.ddoc");
container = openDocFacade("testSetsDefaultSignatureId.ddoc");
assertEquals("S0", container.getSignature(0).getId());
assertEquals("S1", container.getSignature(1).getId());
}
@Test
public void setsSignatureIdWithoutOCSP() throws Exception {
DDocFacade container = new DDocFacade();
container.setSignatureProfile(B_BES);
container.addDataFile("testFiles/test.txt", "text/plain");
SignatureParameters signatureParameters = new SignatureParameters();
signatureParameters.setSignatureId("SIGNATURE-1");
container.setSignatureParameters(signatureParameters);
container.sign(PKCS12_SIGNER);
signatureParameters.setSignatureId("SIGNATURE-2");
container.setSignatureParameters(signatureParameters);
container.sign(PKCS12_SIGNER);
container.save("testSetsSignatureId.ddoc");
container = openDocFacade("testSetsSignatureId.ddoc");
assertEquals("SIGNATURE-1", container.getSignature(0).getId());
assertEquals("SIGNATURE-2", container.getSignature(1).getId());
}
@Test
public void setsDefaultSignatureIdWithoutOCSP() throws Exception {
DDocFacade container = new DDocFacade();
container.addDataFile("testFiles/test.txt", "text/plain");
container.setSignatureProfile(B_BES);
container.sign(PKCS12_SIGNER);
container.sign(PKCS12_SIGNER);
container.save("testSetsDefaultSignatureId.ddoc");
container = openDocFacade("testSetsDefaultSignatureId.ddoc");
assertEquals("S0", container.getSignature(0).getId());
assertEquals("S1", container.getSignature(1).getId());
}
@Test
public void savesToStream() throws IOException {
DDocFacade container = new DDocFacade();
container.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
container.sign(PKCS12_SIGNER);
try(ByteArrayOutputStream out = new ByteArrayOutputStream()) {
container.save(out);
assertTrue(out.size() != 0);
}
}
@Test(expected = DigiDoc4JException.class)
public void savesToStreamThrowsException() throws Exception {
SignedDoc ddoc = mock(SignedDoc.class);
DigiDocException testException = new DigiDocException(100, "testException", new Throwable("test Exception"));
doThrow(testException).when(ddoc).writeToStream(any(OutputStream.class));
DDocFacade container = new DDocFacade(ddoc);
try(ByteArrayOutputStream out = new ByteArrayOutputStream()) {
container.save(out);
}
}
@Test(expected = DigiDoc4JException.class)
public void openFromStreamThrowsException() throws IOException {
FileInputStream stream = new FileInputStream(new File("testFiles/test.txt"));
stream.close();
new DDocOpener().open(stream);
}
@Test
public void getSignatureByIndex() throws CertificateEncodingException {
DDocFacade container = new DDocFacade();
container.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
container.sign(PKCS12_SIGNER);
container.sign(PKCS12_SIGNER);
assertEquals("530be41bbc597c44570e2b7c13bcfa0c", container.getSignature(1).getSigningCertificate().getSerial());
}
@Test(expected = DigiDoc4JException.class)
public void addDataFileAfterSigning() {
DDocFacade container = new DDocFacade();
container.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
container.sign(PKCS12_SIGNER);
container.addDataFile("testFiles/test.xml", TEXT_MIME_TYPE);
}
@Test(expected = DigiDoc4JException.class)
public void removeDataFileAfterSigning() {
DDocFacade container = new DDocFacade();
container.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
container.sign(PKCS12_SIGNER);
container.removeDataFile("testFiles/test.txt");
}
@Test
public void getSignatureWhenNotSigned() {
DDocFacade container = new DDocFacade();
assertTrue(container.getSignatures().isEmpty());
}
@Test(expected = NotSupportedException.class)
public void timeStampProfileIsNotSupported() throws Exception {
DDocFacade container = new DDocFacade();
container.setSignatureProfile(LT);
}
@Test(expected = NotSupportedException.class)
public void TSAProfileIsNotSupported() throws Exception {
DDocFacade container = new DDocFacade();
container.setSignatureProfile(LTA);
}
@Test(expected = NotSupportedException.class)
public void timeStampProfileIsNotSupportedForExtension() throws Exception {
DDocFacade container = new DDocFacade();
container.setSignatureProfile(B_BES);
container.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
container.sign(PKCS12_SIGNER);
container.extendTo(LT);
}
@Test
public void extendToTM() throws Exception {
DDocFacade container = new DDocFacade();
container.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
container.setSignatureProfile(B_BES);
container.sign(PKCS12_SIGNER);
container.save("testAddConfirmation.ddoc");
container = open("testAddConfirmation.ddoc");
assertNull(container.getSignature(0).getOCSPCertificate());
container.extendTo(LT_TM);
container.save("testAddedConfirmation.ddoc");
container = open("testAddedConfirmation.ddoc");
assertNotNull(container.getSignature(0).getOCSPCertificate());
}
@Test(expected = DigiDoc4JException.class)
public void extendToThrowsExceptionForGetConfirmation() throws Exception {
MockDDocFacade container = new MockDDocFacade();
container.setSignatureProfile(B_BES);
container.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
container.sign(PKCS12_SIGNER);
container.extendTo(LT_TM);
}
@Test
public void getVersion() {
DDocFacade container = new DDocFacade();
assertEquals("1.3", container.getVersion());
}
@Test(expected = DigiDoc4JException.class)
public void signThrowsException() throws Exception {
MockDDocFacade container = new MockDDocFacade();
container.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
container.sign(PKCS12_SIGNER);
container.extendTo(LT_TM);
}
@Test
public void twoStepSigning() {
Container container = createDDoc();
container.addDataFile("testFiles/test.txt", "text/plain");
X509Certificate signerCert = getSigningCert();
SignedInfo signedInfo = container.prepareSigning(signerCert);
byte[] signature = getExternalSignature(signedInfo, SHA256);
container.signRaw(signature);
container.save("test.ddoc");
container = ContainerOpener.open("test.ddoc");
assertEquals(1, container.getSignatures().size());
}
@Test (expected = DigiDoc4JException.class)
public void prepareSigningThrowsException() {
Container container = createDDoc();
container.addDataFile("testFiles/test.txt", "text/plain");
container.prepareSigning(null);
}
@Test (expected = DigiDoc4JException.class)
public void signRawThrowsException() {
Container container = createDDoc();
container.addDataFile("testFiles/test.txt", "text/plain");
X509Certificate signerCert = getSigningCert();
container.prepareSigning(signerCert);
container.signRaw(null);
}
@Test
public void signExistingContainer() throws Exception {
DDocFacade container = openDocFacade("testFiles/ddoc_for_testing.ddoc");
container.sign(PKCS12_SIGNER);
assertEquals(2, container.getSignatures().size());
}
@Test
public void signRawWithLT_TMSignatureProfileAddsOCSP() {
String dDocFileName = "testOCSPAddedWithRawSignature.ddoc";
signRawDDocContainer(LT_TM).saveAsFile(dDocFileName);
assertNotNull(ContainerOpener.open(dDocFileName).getSignatures().get(0).getOCSPCertificate());
}
@Test
public void signRawWithNoSignatureProfileDoesNotAddOCSP() {
String dDocFileName = "testOCSPNotAddedWithRawSignatureWhenNoProfile.ddoc";
signRawDDocContainer(B_BES).saveAsFile(dDocFileName);
assertNull(ContainerOpener.open(dDocFileName).getSignatures().get(0).getOCSPCertificate());
}
@Test
public void configManagerShouldBeInitializedOnlyOnce() throws Exception {
DDocFacade.configManagerInitializer = new ConfigManagerInitializerSpy();
assertFalse(ConfigManagerInitializer.isConfigManagerInitialized());
assertEquals(0, ConfigManagerInitializerSpy.configManagerCallCount);
DDocFacade container1 = new DDocFacade();
assertTrue(ConfigManagerInitializer.isConfigManagerInitialized());
assertEquals(1, ConfigManagerInitializerSpy.configManagerCallCount);
DDocFacade container2 = new DDocFacade();
assertTrue(ConfigManagerInitializer.isConfigManagerInitialized());
assertEquals(1, ConfigManagerInitializerSpy.configManagerCallCount);
String path = "testFiles/ddoc_for_testing.ddoc";
DDocFacade container3 = openDocFacade(path);
assertTrue(ConfigManagerInitializer.isConfigManagerInitialized());
assertEquals(1, ConfigManagerInitializerSpy.configManagerCallCount);
}
@Test (expected = ConfigurationException.class)
public void openingDDoc_withoutCAConfiguration_shouldThrowException() throws Exception {
Configuration configuration = new Configuration(Configuration.Mode.TEST);
configuration.loadConfiguration("testFiles/digidoc_test_conf_no_ca.yaml");
ConfigManagerInitializer.forceInitConfigManager(configuration);
ContainerOpener.open("testFiles/ddoc_for_testing.ddoc", configuration);
}
private DDocFacade openDocFacade(String path) {
return new DDocOpener().open(path).getJDigiDocFacade();
}
private Container signRawDDocContainer(SignatureProfile signatureProfile) {
Container container = createDDoc();
container.setSignatureProfile(signatureProfile);
container.addDataFile("testFiles/test.txt", TEXT_MIME_TYPE);
X509Certificate signerCert = getSigningCert();
SignedInfo signedInfo = container.prepareSigning(signerCert);
byte[] signature = getExternalSignature(signedInfo, SHA256);
container.signRaw(signature);
return container;
}
private byte[] getExternalSignature(SignedInfo signedInfo, DigestAlgorithm digestAlgorithm) {
return TestSigningHelper.sign(signedInfo.getDigestToSign(), digestAlgorithm);
}
private Container createDDoc() {
return ContainerBuilder.
aContainer(DDOC_CONTAINER_TYPE).
build();
}
private DDocFacade open(String path) {
DDocContainer container = (DDocContainer)ContainerOpener.open(path);
return container.getJDigiDocFacade();
}
private class MockDDocFacade extends DDocFacade {
ee.sk.digidoc.Signature signature = spy(new ee.sk.digidoc.Signature(new SignedDoc()));
@Override
public void extendTo(SignatureProfile profile) {
super.ddoc = spy(new SignedDoc());
getConfirmationThrowsException();
doReturnSignatureList();
super.extendTo(profile);
}
@Override
ee.sk.digidoc.Signature calculateSignature(SignatureToken signatureToken) {
return signature;
}
@Override
public Signature sign(SignatureToken signatureToken) {
super.ddoc = spy(new SignedDoc());
ddocSignature = mock(ee.sk.digidoc.Signature.class);
doReturnSignatureList();
try {
doReturn("A".getBytes()).when(ddocSignature).calculateSignedInfoXML();
} catch (DigiDocException ignored) {}
getConfirmationThrowsException();
return super.sign(signatureToken);
}
private void getConfirmationThrowsException() {
try {
doThrow(new DigiDocException(1, "test", new Throwable())).when(signature).getConfirmation();
} catch (DigiDocException e) {
e.printStackTrace();
}
}
private void doReturnSignatureList() {
ArrayList<ee.sk.digidoc.Signature> signatures = new ArrayList<>();
signatures.add(signature);
doReturn(signatures).when(ddoc).getSignatures();
}
}
private static class ConfigManagerInitializerSpy extends ConfigManagerInitializer {
static int configManagerCallCount = 0;
static {
configManagerInitialized = false;
}
@Override
void initializeJDigidocConfigManager(Configuration configuration) {
super.initializeJDigidocConfigManager(configuration);
configManagerCallCount++;
}
}
}