/*
* Copyright 2016 The Simple File Server Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sfs.encryption;
import com.google.common.io.ByteStreams;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.file.AsyncFile;
import io.vertx.core.file.OpenOptions;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import org.junit.Test;
import org.sfs.integration.java.BaseTestVerticle;
import org.sfs.io.AsyncFileEndableWriteStream;
import org.sfs.io.AsyncIO;
import org.sfs.io.BufferEndableWriteStream;
import org.sfs.io.BufferWriteEndableWriteStream;
import org.sfs.io.CipherEndableWriteStream;
import org.sfs.io.CipherReadStream;
import org.sfs.io.DigestEndableWriteStream;
import org.sfs.io.NullEndableWriteStream;
import org.sfs.rx.Holder1;
import org.sfs.rx.ObservableFuture;
import org.sfs.rx.RxHelper;
import org.sfs.util.Buffers;
import org.sfs.util.MessageDigestFactory;
import org.sfs.util.PrngRandom;
import org.sfs.util.VertxAssert;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Func1;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.DigestInputStream;
import java.security.DigestOutputStream;
public class AlgorithmTest extends BaseTestVerticle {
@Test
public void testBuffered(TestContext context) {
final byte[] salt = new byte[64];
final byte[] secret = new byte[64];
final byte[] data = new byte[1024];
PrngRandom random = PrngRandom.getCurrentInstance();
random.nextBytesBlocking(salt);
random.nextBytesBlocking(secret);
random.nextBytesBlocking(data);
final CipherWriteStreamValidation cipherWriteStreamValidation = new CipherWriteStreamValidation(secret, salt);
final byte[] expectedCipherBytes = cipherWriteStreamValidation.encrypt(data);
final byte[] actualClearBytes = cipherWriteStreamValidation.decrypt(expectedCipherBytes);
VertxAssert.assertArrayEquals(context, data, actualClearBytes);
final Holder1<Integer> count = new Holder1<>();
count.value = 0;
Async async = context.async();
Observable.from(AlgorithmDef.values())
.map(new Func1<AlgorithmDef, Algorithm>() {
@Override
public Algorithm call(AlgorithmDef algorithmDef) {
count.value++;
return algorithmDef.create(secret, salt);
}
})
.map(new Func1<Algorithm, byte[]>() {
@Override
public byte[] call(final Algorithm algorithm) {
byte[] encrypted = algorithm.encrypt(data);
VertxAssert.assertArrayEquals(context, expectedCipherBytes, encrypted);
return algorithm.decrypt(encrypted);
}
})
.subscribe(new Subscriber<byte[]>() {
@Override
public void onCompleted() {
async.complete();
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
context.fail(e);
}
@Override
public void onNext(byte[] bytes) {
VertxAssert.assertArrayEquals(context, data, bytes);
VertxAssert.assertEquals(context, AlgorithmDef.values().length, count.value.intValue());
}
});
}
@Test
public void testWriteStream(TestContext context) {
final int bufferSize = 10;
final byte[] salt = new byte[64];
final byte[] secret = new byte[64];
final byte[] data = new byte[bufferSize];
PrngRandom random = PrngRandom.getCurrentInstance();
random.nextBytesBlocking(salt);
random.nextBytesBlocking(secret);
random.nextBytesBlocking(data);
CipherWriteStreamValidation cipherWriteStreamValidation = new CipherWriteStreamValidation(secret, salt);
final byte[] expectedCipherBytes = cipherWriteStreamValidation.encrypt(data);
final byte[] actualClearBytes = cipherWriteStreamValidation.decrypt(expectedCipherBytes);
VertxAssert.assertArrayEquals(context, data, actualClearBytes);
final Holder1<Integer> count = new Holder1<>();
count.value = 0;
Async async = context.async();
Observable.from(AlgorithmDef.values())
.map(new Func1<AlgorithmDef, Algorithm>() {
@Override
public Algorithm call(AlgorithmDef algorithmDef) {
count.value++;
return algorithmDef.create(secret, salt);
}
})
.flatMap(new Func1<Algorithm, Observable<byte[]>>() {
@Override
public Observable<byte[]> call(final Algorithm algorithm) {
final BufferWriteEndableWriteStream bufferWriteStream = new BufferWriteEndableWriteStream();
final BufferEndableWriteStream encryptedWriteStream = algorithm.encrypt(bufferWriteStream);
ObservableFuture<Void> handler = RxHelper.observableFuture();
encryptedWriteStream.endHandler(handler::complete);
for (Buffer partition : Buffers.partition(Buffer.buffer(data), bufferSize)) {
encryptedWriteStream.write(partition);
}
encryptedWriteStream.end();
return handler
.map(new Func1<Void, byte[]>() {
@Override
public byte[] call(Void aVoid) {
return bufferWriteStream.toBuffer().getBytes();
}
}).map(new Func1<byte[], byte[]>() {
@Override
public byte[] call(byte[] encrypted) {
VertxAssert.assertArrayEquals(context, expectedCipherBytes, encrypted);
return encrypted;
}
})
.flatMap(new Func1<byte[], Observable<byte[]>>() {
@Override
public Observable<byte[]> call(byte[] encrypted) {
final BufferWriteEndableWriteStream bufferWriteStream = new BufferWriteEndableWriteStream();
final BufferEndableWriteStream decryptedWriteStream = algorithm.decrypt(bufferWriteStream);
ObservableFuture<Void> handler = RxHelper.observableFuture();
decryptedWriteStream.endHandler(handler::complete);
for (Buffer partition : Buffers.partition(Buffer.buffer(encrypted), bufferSize)) {
decryptedWriteStream.write(partition);
}
decryptedWriteStream.end();
return handler
.map(new Func1<Void, byte[]>() {
@Override
public byte[] call(Void aVoid) {
return bufferWriteStream.toBuffer().getBytes();
}
})
.map(new Func1<byte[], byte[]>() {
@Override
public byte[] call(byte[] decrypted) {
VertxAssert.assertArrayEquals(context, data, decrypted);
return decrypted;
}
});
}
});
}
})
.subscribe(new Subscriber<byte[]>() {
@Override
public void onCompleted() {
async.complete();
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
context.fail(e);
}
@Override
public void onNext(byte[] bytes) {
VertxAssert.assertEquals(context, AlgorithmDef.values().length, count.value.intValue());
}
});
}
@Test
public void testFileStream(TestContext context) throws IOException {
final byte[] salt = new byte[64];
final byte[] secret = new byte[64];
final byte[] dataBuffer = new byte[64 * 1024 * 1024];
PrngRandom random = PrngRandom.getCurrentInstance();
random.nextBytesBlocking(salt);
random.nextBytesBlocking(secret);
random.nextBytesBlocking(dataBuffer);
CipherWriteStreamValidation cipherWriteStreamValidation = new CipherWriteStreamValidation(secret, salt);
final Path encryptedTmpFile = Files.createTempFile(tmpDir, "", "");
final byte[] expectedClearDigest;
final byte[] expectedEncryptedDigest;
DigestOutputStream encryptedDigestOutputStream = null;
DigestOutputStream clearDigestOutputStream = null;
try {
encryptedDigestOutputStream = new DigestOutputStream(Files.newOutputStream(encryptedTmpFile), MessageDigestFactory.SHA512.instance());
clearDigestOutputStream = new DigestOutputStream(cipherWriteStreamValidation.encrypt(encryptedDigestOutputStream), MessageDigestFactory.SHA512.instance());
clearDigestOutputStream.write(dataBuffer);
} finally {
if (clearDigestOutputStream != null) {
clearDigestOutputStream.close();
}
}
expectedClearDigest = clearDigestOutputStream.getMessageDigest().digest();
expectedEncryptedDigest = encryptedDigestOutputStream.getMessageDigest().digest();
final byte[] actualClearDigest;
byte[] actualEncryptedDigest;
DigestInputStream encryptedDigestInputStream = null;
DigestInputStream clearDigestInputStream = null;
try {
encryptedDigestInputStream = new DigestInputStream(Files.newInputStream(encryptedTmpFile), MessageDigestFactory.SHA512.instance());
clearDigestInputStream = new DigestInputStream(cipherWriteStreamValidation.decrypt(encryptedDigestInputStream), MessageDigestFactory.SHA512.instance());
OutputStream blackHole = ByteStreams.nullOutputStream();
ByteStreams.copy(clearDigestInputStream, blackHole);
} finally {
if (clearDigestInputStream != null) {
clearDigestInputStream.close();
}
}
actualClearDigest = clearDigestInputStream.getMessageDigest().digest();
actualEncryptedDigest = encryptedDigestInputStream.getMessageDigest().digest();
VertxAssert.assertArrayEquals(context, expectedClearDigest, actualClearDigest);
VertxAssert.assertArrayEquals(context, expectedEncryptedDigest, actualEncryptedDigest);
final Path encryptedTmpFileVertx = Files.createTempFile(tmpDir, "", "");
Async async = context.async();
ObservableFuture<AsyncFile> rh = RxHelper.observableFuture();
OpenOptions openOptions = new OpenOptions();
openOptions.setCreate(true)
.setRead(true)
.setWrite(true);
vertx.fileSystem()
.open(encryptedTmpFileVertx.toString(), openOptions, rh.toHandler());
rh
.flatMap(new Func1<AsyncFile, Observable<Void>>() {
@Override
public Observable<Void> call(AsyncFile asyncFile) {
BufferEndableWriteStream endable = new AsyncFileEndableWriteStream(asyncFile);
final DigestEndableWriteStream encryptedDigestWriteStream = new DigestEndableWriteStream(endable, MessageDigestFactory.SHA512);
Algorithm algorithm = AlgorithmDef.SALTED_AES256_V01.create(secret, salt);
CipherEndableWriteStream cipherWriteStream = algorithm.encrypt(encryptedDigestWriteStream);
final DigestEndableWriteStream clearDigestWriteStream = new DigestEndableWriteStream(cipherWriteStream, MessageDigestFactory.SHA512);
ObservableFuture<Void> handler = RxHelper.observableFuture();
clearDigestWriteStream.endHandler(handler::complete);
for (Buffer buffer : Buffers.partition(Buffer.buffer(dataBuffer), 8192)) {
clearDigestWriteStream.write(buffer);
}
clearDigestWriteStream.end();
return handler
.map(new Func1<Void, Void>() {
@Override
public Void call(Void aVoid) {
VertxAssert.assertArrayEquals(context, expectedEncryptedDigest, encryptedDigestWriteStream.getDigest(MessageDigestFactory.SHA512).get());
VertxAssert.assertArrayEquals(context, expectedClearDigest, clearDigestWriteStream.getDigest(MessageDigestFactory.SHA512).get());
return null;
}
});
}
})
.flatMap(new Func1<Void, Observable<AsyncFile>>() {
@Override
public Observable<AsyncFile> call(Void aVoid) {
ObservableFuture<AsyncFile> rh = RxHelper.observableFuture();
OpenOptions openOptions = new OpenOptions();
openOptions.setCreate(true)
.setRead(true)
.setWrite(true);
vertx.fileSystem()
.open(encryptedTmpFileVertx.toString(), openOptions, rh.toHandler());
return rh;
}
})
.flatMap(new Func1<AsyncFile, Observable<Void>>() {
@Override
public Observable<Void> call(AsyncFile asyncFile) {
final DigestEndableWriteStream clearDigestWriteStream = new DigestEndableWriteStream(new NullEndableWriteStream(), MessageDigestFactory.SHA512);
Algorithm algorithm = AlgorithmDef.SALTED_AES256_V01.create(secret, salt);
CipherEndableWriteStream cipherWriteStream = algorithm.decrypt(clearDigestWriteStream);
final DigestEndableWriteStream encryptedDigestWriteStream = new DigestEndableWriteStream(cipherWriteStream, MessageDigestFactory.SHA512);
return AsyncIO.pump(asyncFile, encryptedDigestWriteStream)
.map(new Func1<Void, Void>() {
@Override
public Void call(Void aVoid) {
VertxAssert.assertArrayEquals(context, expectedEncryptedDigest, encryptedDigestWriteStream.getDigest(MessageDigestFactory.SHA512).get());
VertxAssert.assertArrayEquals(context, expectedClearDigest, clearDigestWriteStream.getDigest(MessageDigestFactory.SHA512).get());
return null;
}
});
}
})
.flatMap(new Func1<Void, Observable<AsyncFile>>() {
@Override
public Observable<AsyncFile> call(Void aVoid) {
ObservableFuture<AsyncFile> rh = RxHelper.observableFuture();
OpenOptions openOptions = new OpenOptions();
openOptions.setCreate(true)
.setRead(true)
.setWrite(true);
vertx.fileSystem()
.open(encryptedTmpFileVertx.toString(), openOptions, rh.toHandler());
return rh;
}
})
.flatMap(new Func1<AsyncFile, Observable<Void>>() {
@Override
public Observable<Void> call(AsyncFile asyncFile) {
final DigestEndableWriteStream clearDigestWriteStream = new DigestEndableWriteStream(new NullEndableWriteStream(), MessageDigestFactory.SHA512);
Algorithm algorithm = AlgorithmDef.SALTED_AES256_V01.create(secret, salt);
CipherReadStream readStream = algorithm.decrypt(asyncFile);
return AsyncIO.pump(readStream, clearDigestWriteStream)
.map(new Func1<Void, Void>() {
@Override
public Void call(Void aVoid) {
VertxAssert.assertArrayEquals(context, expectedClearDigest, clearDigestWriteStream.getDigest(MessageDigestFactory.SHA512).get());
return null;
}
});
}
})
.subscribe(new Subscriber<Void>() {
@Override
public void onCompleted() {
async.complete();
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
context.fail(e);
}
@Override
public void onNext(Void aVoid) {
}
});
}
}