/*
* 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.base.Optional;
import io.vertx.core.http.HttpClientResponse;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import org.junit.Test;
import org.sfs.TestSubscriber;
import org.sfs.elasticsearch.account.LoadAccount;
import org.sfs.elasticsearch.container.LoadContainer;
import org.sfs.elasticsearch.containerkey.GetNewestContainerKey;
import org.sfs.elasticsearch.containerkey.UpdateContainerKey;
import org.sfs.integration.java.BaseTestVerticle;
import org.sfs.integration.java.func.AssertHttpClientResponseStatusCode;
import org.sfs.integration.java.func.PostAccount;
import org.sfs.integration.java.func.PostContainer;
import org.sfs.integration.java.func.PutContainer;
import org.sfs.integration.java.func.RefreshIndex;
import org.sfs.rx.Holder2;
import org.sfs.rx.ToVoid;
import org.sfs.util.HttpClientResponseHeaderLogger;
import rx.Observable;
import java.util.Arrays;
import java.util.Calendar;
import java.util.concurrent.atomic.AtomicReference;
import static com.google.common.base.Charsets.UTF_8;
import static java.lang.String.valueOf;
import static java.net.HttpURLConnection.HTTP_CREATED;
import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
import static java.util.Calendar.getInstance;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.sfs.integration.java.help.AuthorizationFactory.Producer;
import static org.sfs.integration.java.help.AuthorizationFactory.httpBasic;
import static org.sfs.util.KnownMetadataKeys.X_MAX_OBJECT_REVISIONS;
import static org.sfs.util.SfsHttpHeaders.X_ADD_CONTAINER_META_PREFIX;
import static org.sfs.util.VertxAssert.assertArrayEquals;
import static org.sfs.util.VertxAssert.assertEquals;
import static org.sfs.util.VertxAssert.assertFalse;
import static org.sfs.vo.ObjectPath.fromPaths;
import static rx.Observable.just;
public class ContainerKeysTest extends BaseTestVerticle {
private final String accountName = "testaccount";
private final String containerName = "testcontainer";
private final String objectName = "testobject";
private Producer authAdmin = httpBasic("admin", "admin");
private Producer authNonAdmin = httpBasic("user", "user");
protected Observable<Void> prepareContainer(TestContext context) {
return just((Void) null)
.flatMap(new PostAccount(httpClient, accountName, authAdmin))
.map(new HttpClientResponseHeaderLogger())
.map(new AssertHttpClientResponseStatusCode(context, HTTP_NO_CONTENT))
.map(new ToVoid<HttpClientResponse>())
.flatMap(new PutContainer(httpClient, accountName, containerName, authNonAdmin))
.map(new HttpClientResponseHeaderLogger())
.map(new AssertHttpClientResponseStatusCode(context, HTTP_CREATED))
.map(new ToVoid<HttpClientResponse>())
.flatMap(new PostContainer(httpClient, accountName, containerName, authNonAdmin)
.setHeader(X_ADD_CONTAINER_META_PREFIX + X_MAX_OBJECT_REVISIONS, valueOf(3)))
.map(new HttpClientResponseHeaderLogger())
.map(new AssertHttpClientResponseStatusCode(context, HTTP_NO_CONTENT))
.map(new ToVoid<HttpClientResponse>());
}
@Test
public void testKeyNoRotate(TestContext context) {
ContainerKeys containerKeys = vertxContext.verticle().containerKeys();
Async async = context.async();
prepareContainer(context)
.map(aVoid -> fromPaths(accountName).accountPath().get())
.flatMap(new LoadAccount(vertxContext))
.map(Optional::get)
.flatMap(persistentAccount ->
just(fromPaths(accountName, containerName).containerPath().get())
.flatMap(new LoadContainer(vertxContext, persistentAccount))
.map(Optional::get))
.flatMap(persistentContainer -> {
AtomicReference<byte[]> existingArray = new AtomicReference<>();
return containerKeys.preferredAlgorithm(vertxContext, persistentContainer)
.map(new ToVoid<>())
.flatMap(new RefreshIndex(httpClient, authAdmin))
.map(aVoid -> persistentContainer)
.flatMap(new GetNewestContainerKey(vertxContext))
.map(Optional::get)
.map(persistentContainerKey -> {
existingArray.set(persistentContainerKey.getEncryptedKey().get());
return persistentContainerKey;
})
.flatMap(pck -> containerKeys.rotateIfRequired(vertxContext, pck))
.map(persistentContainerKey -> {
assertEquals(context, "/testaccount/testcontainer/0000000000000000000", persistentContainerKey.getId());
assertArrayEquals(context, existingArray.get(), persistentContainerKey.getEncryptedKey().get());
return persistentContainer;
});
})
.map(new ToVoid<>())
.subscribe(new TestSubscriber(context, async));
}
@Test
public void testKeyRotate(TestContext context) {
ContainerKeys containerKeys = vertxContext.verticle().containerKeys();
Async async = context.async();
prepareContainer(context)
.map(aVoid -> fromPaths(accountName).accountPath().get())
.flatMap(new LoadAccount(vertxContext))
.map(Optional::get)
.flatMap(persistentAccount ->
just(fromPaths(accountName, containerName).containerPath().get())
.flatMap(new LoadContainer(vertxContext, persistentAccount))
.map(Optional::get))
.flatMap(persistentContainer -> {
AtomicReference<byte[]> existingArray = new AtomicReference<>();
AtomicReference<String> existingId = new AtomicReference<>();
return containerKeys.preferredAlgorithm(vertxContext, persistentContainer)
.map(new ToVoid<>())
.flatMap(new RefreshIndex(httpClient, authAdmin))
.map(aVoid -> persistentContainer)
.flatMap(new GetNewestContainerKey(vertxContext))
.map(Optional::get)
.map(persistentContainerKey -> {
assertEquals(context, "/testaccount/testcontainer/0000000000000000000", persistentContainerKey.getId());
existingArray.set(persistentContainerKey.getEncryptedKey().get());
existingId.set(persistentContainerKey.getId());
return persistentContainerKey;
})
.map(persistentContainerKey -> {
Calendar now = getInstance();
long thePast = DAYS.toMillis(365);
now.setTimeInMillis(thePast);
persistentContainerKey.setCreateTs(now);
return persistentContainerKey;
})
.flatMap(new UpdateContainerKey(vertxContext))
.map(Holder2::value1)
.map(Optional::get)
.flatMap(pck -> containerKeys.rotateIfRequired(vertxContext, pck))
.map(persistentContainerKey -> {
assertEquals(context, "/testaccount/testcontainer/0000000000000000001", persistentContainerKey.getId());
assertFalse(context, Arrays.equals(existingArray.get(), persistentContainerKey.getEncryptedKey().get()));
assertFalse(context, existingId.get().equals(persistentContainerKey.getId()));
return persistentContainer;
});
})
.map(new ToVoid<>())
.subscribe(new TestSubscriber(context, async));
}
@Test
public void testReEncrypt(TestContext context) {
ContainerKeys containerKeys = vertxContext.verticle().containerKeys();
Async async = context.async();
prepareContainer(context)
.map(aVoid -> fromPaths(accountName).accountPath().get())
.flatMap(new LoadAccount(vertxContext))
.map(Optional::get)
.flatMap(persistentAccount ->
just(fromPaths(accountName, containerName).containerPath().get())
.flatMap(new LoadContainer(vertxContext, persistentAccount))
.map(Optional::get))
.flatMap(persistentContainer -> {
AtomicReference<byte[]> existingArray = new AtomicReference<>();
AtomicReference<String> existingId = new AtomicReference<>();
return containerKeys.preferredAlgorithm(vertxContext, persistentContainer)
.map(new ToVoid<>())
.flatMap(new RefreshIndex(httpClient, authAdmin))
.map(aVoid -> persistentContainer)
.flatMap(new GetNewestContainerKey(vertxContext))
.map(Optional::get)
.map(persistentContainerKey -> {
assertEquals(context, "/testaccount/testcontainer/0000000000000000000", persistentContainerKey.getId());
existingArray.set(persistentContainerKey.getEncryptedKey().get());
existingId.set(persistentContainerKey.getId());
return persistentContainerKey;
})
.map(persistentContainerKey -> {
Calendar now = getInstance();
long thePast = DAYS.toMillis(365);
now.setTimeInMillis(thePast);
persistentContainerKey.setReEncryptTs(now);
return persistentContainerKey;
})
.flatMap(new UpdateContainerKey(vertxContext))
.map(new ToVoid<>())
.flatMap(new RefreshIndex(httpClient, authAdmin))
.flatMap(aVoid -> containerKeys.maintain(vertxContext))
.flatMap(new RefreshIndex(httpClient, authAdmin))
.map(aVoid -> persistentContainer)
.flatMap(new GetNewestContainerKey(vertxContext))
.map(Optional::get)
.map(persistentContainerKey -> {
assertEquals(context, "/testaccount/testcontainer/0000000000000000000", persistentContainerKey.getId());
assertFalse(context, Arrays.equals(existingArray.get(), persistentContainerKey.getEncryptedKey().get()));
return persistentContainer;
});
})
.map(new ToVoid<>())
.subscribe(new TestSubscriber(context, async));
}
@Test
public void testNoReEncrypt(TestContext context) {
ContainerKeys containerKeys = vertxContext.verticle().containerKeys();
Async async = context.async();
prepareContainer(context)
.map(aVoid -> fromPaths(accountName).accountPath().get())
.flatMap(new LoadAccount(vertxContext))
.map(Optional::get)
.flatMap(persistentAccount ->
just(fromPaths(accountName, containerName).containerPath().get())
.flatMap(new LoadContainer(vertxContext, persistentAccount))
.map(Optional::get))
.flatMap(persistentContainer -> {
AtomicReference<byte[]> existingArray = new AtomicReference<>();
AtomicReference<String> existingId = new AtomicReference<>();
return containerKeys.preferredAlgorithm(vertxContext, persistentContainer)
.map(new ToVoid<>())
.flatMap(new RefreshIndex(httpClient, authAdmin))
.map(aVoid -> persistentContainer)
.flatMap(new GetNewestContainerKey(vertxContext))
.map(Optional::get)
.map(persistentContainerKey -> {
assertEquals(context, "/testaccount/testcontainer/0000000000000000000", persistentContainerKey.getId());
existingArray.set(persistentContainerKey.getEncryptedKey().get());
existingId.set(persistentContainerKey.getId());
return persistentContainerKey;
})
.flatMap(new UpdateContainerKey(vertxContext))
.map(new ToVoid<>())
.flatMap(new RefreshIndex(httpClient, authAdmin))
.flatMap(aVoid -> containerKeys.maintain(vertxContext))
.flatMap(new RefreshIndex(httpClient, authAdmin))
.map(aVoid -> persistentContainer)
.flatMap(new GetNewestContainerKey(vertxContext))
.map(Optional::get)
.map(persistentContainerKey -> {
assertEquals(context, "/testaccount/testcontainer/0000000000000000000", persistentContainerKey.getId());
assertArrayEquals(context, existingArray.get(), persistentContainerKey.getEncryptedKey().get());
return persistentContainer;
});
})
.map(new ToVoid<>())
.subscribe(new TestSubscriber(context, async));
}
@Test
public void testEncryptDecrypt(TestContext context) {
byte[] data = "this is a test 111".getBytes(UTF_8);
ContainerKeys containerKeys = vertxContext.verticle().containerKeys();
Async async = context.async();
prepareContainer(context)
.map(aVoid -> fromPaths(accountName).accountPath().get())
.flatMap(new LoadAccount(vertxContext))
.map(Optional::get)
.flatMap(persistentAccount ->
just(fromPaths(accountName, containerName).containerPath().get())
.flatMap(new LoadContainer(vertxContext, persistentAccount))
.map(Optional::get))
.flatMap(persistentContainer ->
containerKeys.preferredAlgorithm(vertxContext, persistentContainer)
.flatMap(keyResponse -> {
Algorithm algorithm = keyResponse.getData();
byte[] encryptedData = algorithm.encrypt(data);
return containerKeys.algorithm(vertxContext, persistentContainer, keyResponse.getKeyId(), keyResponse.getSalt())
.map(keyResponse1 -> {
Algorithm algorithm1 = keyResponse1.getData();
return algorithm1.decrypt(encryptedData);
});
}))
.map(decryptedBytes -> {
assertArrayEquals(context, data, decryptedBytes);
return decryptedBytes;
})
.map(new ToVoid<>())
.subscribe(new TestSubscriber(context, async));
}
}