Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jferard committed Apr 23, 2022
1 parent 8e46b79 commit 4a3d5e1
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 23 deletions.
11 changes: 11 additions & 0 deletions fastods-crypto/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@
<artifactId>easymock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.jferard</groupId>
<artifactId>fastods-testlib</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.jferard</groupId>
<artifactId>fastods-testlib</artifactId>
<version>0.8.2-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
* 19.698: "Producers should use http://www.w3.org/2000/09/xmldsig#sha256."
*/
public class ProtectionFactory {
/** Do not instantiate */
private ProtectionFactory() {}

/**
* Create a new protection. **Beware: for security reasons, this fills the password array
* with 0's**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

package com.github.jferard.fastods.crypto;

import com.github.jferard.fastods.util.CharsetUtil;
import org.bouncycastle.util.encoders.Base64;

import java.nio.ByteBuffer;
Expand All @@ -35,6 +34,9 @@
import java.util.Arrays;

public class Util {
/** Do not instantiate */
private Util() {}

/**
* Convert a char array to a byte array containing the SHA-256 digest. **Beware: for security
* reasons, this fills the password array with 0's**.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@

import com.github.jferard.fastods.annotation.Beta;
import com.github.jferard.fastods.odselement.OdsEntry;
import com.github.jferard.fastods.util.CharsetUtil;
import com.github.jferard.fastods.util.ZipUTF8Writer;
import com.github.jferard.fastods.util.ZipUTF8WriterBuilder;
import org.bouncycastle.util.encoders.Base64;
Expand All @@ -51,6 +50,7 @@
public class ZipUTF8CryptoWriter implements ZipUTF8Writer {
/**
* **Beware: for security reasons, this fills the password array with 0's**
*
* @param password the password to encrypt data
* @return a builder
* @throws NoSuchAlgorithmException won't happen since SHA-256 is pretty common
Expand All @@ -62,11 +62,11 @@ public static ZipUTF8WriterBuilder builder(final char[] password)

private final ZipUTF8Writer zipUTF8Writer;
private final StandardEncrypter encrypter;
private final byte[] hashedPassword;
private ByteArrayOutputStream out;
private Writer writer;
private OdsEntry curEntry;
private boolean toRegister;
private final byte[] hashedPassword;

public ZipUTF8CryptoWriter(final ZipUTF8Writer zipUTF8Writer,
final StandardEncrypter encrypter, final byte[] hashedPassword) {
Expand All @@ -81,7 +81,7 @@ public void setComment(final String comment) {
}

@Override
public void putAndRegisterNextEntry(final OdsEntry entry) throws IOException {
public void putAndRegisterNextEntry(final OdsEntry entry) {
this.toRegister = true;
this.putNextEntry(entry);
}
Expand Down Expand Up @@ -124,17 +124,9 @@ private EntryAndData getEncryptedEntryAndData(final byte[] plainTextBytes) throw
final EntryAndData entryAndData;
try {
entryAndData = this.getEncryptedEntryAndDataUnchecked(plainTextBytes);
} catch (final NoSuchAlgorithmException e) {
throw new IOException("Can't encrypt file", e);
} catch (final InvalidKeyException e) {
throw new IOException("Can't encrypt file", e);
} catch (final InvalidAlgorithmParameterException e) {
throw new IOException("Can't encrypt file", e);
} catch (final NoSuchPaddingException e) {
throw new IOException("Can't encrypt file", e);
} catch (final BadPaddingException e) {
throw new IOException("Can't encrypt file", e);
} catch (final IllegalBlockSizeException e) {
} catch (final NoSuchAlgorithmException | InvalidKeyException
| InvalidAlgorithmParameterException | NoSuchPaddingException
| BadPaddingException | IllegalBlockSizeException e) {
throw new IOException("Can't encrypt file", e);
}
return entryAndData;
Expand All @@ -154,7 +146,8 @@ private EntryAndData getEncryptedEntryAndDataUnchecked(final byte[] plainTextByt
final long crc32 = this.getCrc32(compressedThenEncryptedData);
final OdsEntry entry = this.curEntry.encryptParameters(
this.encrypter.buildParameters(
plainTextBytes.length, compressedThenEncryptedData.length, crc32, compressedCheckSum,
plainTextBytes.length, compressedThenEncryptedData.length, crc32,
compressedCheckSum,
Base64.toBase64String(salt), Base64.toBase64String(iv)));
return new EntryAndData(entry, compressedThenEncryptedData);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
* A builder for `ZipUTF8CryptoWriter`.
*/
public class ZipUTF8CryptoWriterBuilder implements ZipUTF8WriterBuilder {
public static final String AES_CBC_ISO_10126_PADDING = "AES/CBC/ISO10126Padding";
public static final String SHA_1_PRNG = "SHA1PRNG";

/**
* **Beware: for security reasons, this fills the password array with 0's**
*
Expand Down Expand Up @@ -76,15 +79,13 @@ public ZipUTF8CryptoWriterBuilder(final ZipUTF8WriterBuilderImpl writerBuilder,
}

@Override
public ZipUTF8Writer build(final OutputStream outputStream) {
public ZipUTF8CryptoWriter build(final OutputStream outputStream) {
try {
return new ZipUTF8CryptoWriter(this.writerBuilder.build(outputStream),
new StandardEncrypter(SecureRandom.getInstance("SHA1PRNG"),
Cipher.getInstance("AES/CBC/ISO10126Padding"), 100000, 32, 32, this.parametersBuilder
new StandardEncrypter(SecureRandom.getInstance(SHA_1_PRNG),
Cipher.getInstance(AES_CBC_ISO_10126_PADDING), 100000, 32, 32, this.parametersBuilder
), this.hashedPassword);
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (final NoSuchPaddingException e) {
} catch (final NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new RuntimeException(e);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* FastODS - A very fast and lightweight (no dependency) library for creating ODS
* (Open Document Spreadsheet, mainly for Calc) files in Java.
* It's a Martin Schulz's SimpleODS fork
* Copyright (C) 2016-2020 J. Férard <https://github.com/jferard>
* SimpleODS - A lightweight java library to create simple OpenOffice spreadsheets
* Copyright (C) 2008-2013 Martin Schulz <mtschulz at users.sourceforge.net>
*
* This file is part of FastODS.
*
* FastODS is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* FastODS 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 com.github.jferard.fastods.crypto;

import com.github.jferard.fastods.util.Protection;
import com.github.jferard.fastods.util.XMLUtil;
import org.junit.Assert;
import org.junit.Test;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;

public class ProtectionFactoryTest {
@Test
public void test() throws NoSuchAlgorithmException, IOException {
final Protection p = ProtectionFactory.createSha256("passwd".toCharArray());
final StringBuilder sb = new StringBuilder();
p.appendAttributes(XMLUtil.create(), sb);

Assert.assertEquals(
" table:protected=\"true\" table:protection-key=\"DWvmmyZHF/LdM2UuISsXMQS0pke3wRrnLpiF8RzTEvs=\" table:protection-key-digest-algorithm=\"http://www.w3.org/2000/09/xmldsig#sha256\"",
sb.toString());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@

package com.github.jferard.fastods.crypto;

import com.github.jferard.fastods.XMLConvertible;
import com.github.jferard.fastods.odselement.EncryptParameters;
import com.github.jferard.fastods.testlib.DomTester;
import com.github.jferard.fastods.util.CharsetUtil;
import com.github.jferard.fastods.util.XMLUtil;
import org.bouncycastle.util.encoders.Base64;
import org.easymock.EasyMock;
import org.junit.Assert;
Expand Down Expand Up @@ -112,4 +115,57 @@ public void testIVandSalt() throws NoSuchPaddingException, NoSuchAlgorithmExcept

PowerMock.verifyAll();
}

@Test
public void testBuildParameters()
throws NoSuchPaddingException, NoSuchAlgorithmException, IOException {
final SecureRandom sr = PowerMock.createMock(SecureRandom.class);
final StandardEncrypter encrypter = new StandardEncrypter(
sr, Cipher.getInstance("AES/CBC/ISO10126Padding"),
100000, 32, 32, EncryptParameters.builder()
);

PowerMock.resetAll();

PowerMock.replayAll();
final EncryptParameters parameters =
encrypter.buildParameters(100, 10, 40L, "ccs", "salt", "vec");

PowerMock.verifyAll();
Assert.assertEquals(100, parameters.getPlainDataSize());
Assert.assertEquals(10, parameters.getCompressedThenEncryptedDataSize());
Assert.assertEquals(40L, parameters.getCrc32());

StandardEncrypterTest.assertXMLEquals("<manifest:encryption-data " +
"manifest:checksum-type=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0#sha256-1k\" " +
"manifest:checksum=\"ccs\">" +
"<manifest:algorithm " +
"manifest:algorithm-name=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\" " +
"manifest:initialisation-vector=\"vec\"/>" +
"<manifest:start-key-generation " +
"manifest:start-key-generation-name=\"http://www.w3.org/2000/09/xmldsig#sha256\" " +
"manifest:key-size=\"32\"/>" +
"<manifest:key-derivation manifest:key-derivation-name=\"PBKDF2\" " +
"manifest:key-size=\"32\" manifest:iteration-count=\"100000\" " +
"manifest:salt=\"salt\"/>" +
"</manifest:encryption-data>", parameters);
}

public static String toXML(final XMLConvertible o) throws IOException {
final StringBuilder sb = new StringBuilder();
o.appendXMLContent(XMLUtil.create(), sb);
return sb.toString();
}

/**
* Beware: sensitive to spaces (spaces are children)
*
* @param xml the xml string
* @param o an object convertible to XML
* @throws IOException if an I/O error occurs
*/
public static void assertXMLEquals(final String xml, final XMLConvertible o)
throws IOException {
DomTester.assertEquals(xml, StandardEncrypterTest.toXML(o));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package com.github.jferard.fastods.crypto;

import com.github.jferard.fastods.odselement.EncryptParameters;
import com.github.jferard.fastods.odselement.EncryptParametersBuilder;
import com.github.jferard.fastods.odselement.StandardOdsEntry;
import com.github.jferard.fastods.odselement.UnregisteredOdsEntry;
import com.github.jferard.fastods.odselement.UnregisteredStoredEntry;
Expand Down Expand Up @@ -314,4 +315,74 @@ public void testAppend() throws IOException, NoSuchPaddingException, InvalidKeyE
// EOCD
'P', 'K', 5, 6, 0, 0, 0, 0, 2, 0, 2, 0, 117, 0, 0, 0, 6, 1, 0, 0, 0, 0}, bytes);
}
}

@Test
public void testBuilder()
throws NoSuchAlgorithmException {
final ZipUTF8WriterBuilderImpl writerBuilder =
PowerMock.createMock(ZipUTF8WriterBuilderImpl.class);
final EncryptParametersBuilder parametersBuilder =
PowerMock.createMock(EncryptParametersBuilder.class);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();

PowerMock.resetAll();
EasyMock.expect(writerBuilder.build(bos)).andReturn(null);

PowerMock.replayAll();
final ZipUTF8CryptoWriterBuilder builder =
new ZipUTF8CryptoWriterBuilder(writerBuilder, parametersBuilder,
"passwd".toCharArray());
builder.build(bos);

PowerMock.verifyAll();
}

@Test
public void testExc()
throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException,
IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException,
InvalidKeyException {
final StandardEncrypter encrypter = PowerMock.createMock(StandardEncrypter.class);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final ZipUTF8Writer writer = PowerMock.createMock(ZipUTF8Writer.class);
final byte[] salt = "foo".getBytes(StandardCharsets.UTF_8);
final byte[] iv = "bar".getBytes(StandardCharsets.UTF_8);
final byte[] data = "baz".getBytes(StandardCharsets.UTF_8);
final byte[] password = "passwd".getBytes(StandardCharsets.UTF_8);

PowerMock.resetAll();
EasyMock.expect(encrypter.generateSalt()).andReturn(salt);
EasyMock.expect(encrypter.generateIV()).andReturn(iv);
EasyMock.expect(encrypter.compress(new byte[0])).andReturn(data);
EasyMock.expect(encrypter.encrypt(data, password, salt, iv))
.andThrow(new NoSuchAlgorithmException());

PowerMock.replayAll();
final ZipUTF8CryptoWriter cryptoWriter =
new ZipUTF8CryptoWriter(writer, encrypter, password);
cryptoWriter.putNextEntry(new UnregisteredOdsEntry("path"));
Assert.assertThrows(IOException.class, () -> cryptoWriter.closeEntry());

PowerMock.verifyAll();
}

@Test
public void testSetComment() {
final StandardEncrypter encrypter = PowerMock.createMock(StandardEncrypter.class);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final ZipUTF8Writer writer = PowerMock.createMock(ZipUTF8Writer.class);
final byte[] password = "passwd".getBytes(StandardCharsets.UTF_8);
final StandardOdsEntry entry = new StandardOdsEntry("path", "mt", "v");

PowerMock.resetAll();
writer.setComment("Comment");
writer.registerEntry(entry);

PowerMock.replayAll();
final ZipUTF8CryptoWriter cryptoWriter =
new ZipUTF8CryptoWriter(writer, encrypter, password);
cryptoWriter.setComment("Comment");
cryptoWriter.registerEntry(entry);

PowerMock.verifyAll();
}}

0 comments on commit 4a3d5e1

Please sign in to comment.